diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml new file mode 100644 index 00000000..99c6ef14 --- /dev/null +++ b/.github/dependabot.yaml @@ -0,0 +1,28 @@ +--- +version: 2 +updates: + - package-ecosystem: 'bundler' + directory: '/' + schedule: + interval: 'weekly' + commit-message: + prefix: 'chore(deps)' + groups: + dependencies: + applies-to: version-updates + update-types: + - 'minor' + - 'patch' + - package-ecosystem: 'github-actions' + directory: '/' + schedule: + interval: 'weekly' + commit-message: + prefix: 'chore(deps)' + groups: + dependencies: + applies-to: version-updates + update-types: + - 'minor' + - 'patch' + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..09c02196 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,62 @@ +name: CI +on: [push, pull_request] + +env: + JRUBY_OPTS: -Xcext.enabled=true + +permissions: + contents: read + +jobs: + build: + name: "Test / Ruby ${{ matrix.ruby }}" + runs-on: ubuntu-latest + strategy: + matrix: + ruby: + - "3.3" + - "3.4" + - "4.0" + fail-fast: false + + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 10 + + - uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1 + with: + ruby-version: ${{ matrix.ruby }} + bundler-cache: true + + - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + # Required by lib/github/commands/rest2html (RST rendering) + python-version: "3.x" + + - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip + + - name: Install Perl dependencies + run: | + curl -1sLf \ + 'https://dl.cloudsmith.io/public/nxadm-pkgs/rakudo-pkg/setup.deb.sh' \ + | sudo -E bash + sudo apt-get update -qq + sudo apt-get install perl rakudo-pkg + + curl -fsSL https://cpanmin.us | perl - --sudo App::cpanminus + sudo cpanm --installdeps --notest Pod::Simple + + - name: Install Python dependencies + run: | + echo 'docutils==0.22.4 --hash=sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de' > /tmp/requirements.txt + python -m pip install --require-hashes --no-deps -r /tmp/requirements.txt + + - name: Run rake + run: | + export PATH=$PATH:/.perl6/bin:/opt/rakudo-pkg/bin + bundle exec rake diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 00000000..1b97b737 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,27 @@ +name: Mark stale issues and pull requests + +on: + schedule: + - cron: "0 12 * * *" + +jobs: + stale: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + + steps: + - uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11.0.0 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + stale-issue-message: > + This issue has been automatically marked as stale because it has not + had recent activity. It will be closed if no further activity occurs. + Thank you for your contributions. + stale-pr-message: > + This pull request has been automatically marked as stale because it has not + had recent activity. It will be closed if no further activity occurs. + Thank you for your contributions. + exempt-issue-labels: keep + exempt-pr-labels: keep diff --git a/.gitignore b/.gitignore index a08b381b..8e4e8392 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,11 @@ *.pyc pkg/ .bundle -Gemfile.lock +.project +.buildpath +*~ vendor/ +.DS_Store +.venv +venv +coverage/ diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 0a44f56e..00000000 --- a/.travis.yml +++ /dev/null @@ -1,11 +0,0 @@ -language: ruby -before_install: sudo pip install docutils -rvm: - - 1.9.3 - - 2.0.0 - - 2.1.1 - - jruby-19mode -jdk: - - oraclejdk8 -notifications: - email: false diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..16e77760 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [opensource@github.com](mailto:opensource@github.com). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [https://contributor-covenant.org/version/1/4][version] + +[homepage]: https://contributor-covenant.org +[version]: https://contributor-covenant.org/version/1/4/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 57ba6e97..2c83e31e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,65 +1,40 @@ # Contributing -Want to contribute? Great! +Contributions to this project are [released](https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license) to the public under the [project's open source license](LICENSE). -1. Fork it. -2. Create a branch (`git checkout -b my_markup`) -3. Commit your changes (`git commit -am "Added Snarkdown"`) -4. Push to the branch (`git push origin my_markup`) -5. Open a [Pull Request][1] -6. Enjoy a refreshing Diet Coke and wait - - -There are two ways to add markups. - -### Commands - -If your markup is in a language other than Ruby, drop a translator -script in `lib/github/commands` which accepts input on STDIN and -returns HTML on STDOUT. See [rest2html][r2h] for an example. +This project adheres to a [Code of Conduct][code-of-conduct]. By participating, you are expected to honor this code. -Once your script is in place, edit `lib/github/markups.rb` and tell -GitHub Markup about it. Again we look to [rest2html][r2hc] for -guidance: +[code-of-conduct]: CODE_OF_CONDUCT.md - command(:rest2html, /re?st(.txt)?/) +This library's only job is to decide which markup format to use and call out to an external library to convert the markup to HTML (see the [README](README.md) for more information on how markup is rendered on GitHub.com). -Here we're telling GitHub Markup of the existence of a `rest2html` -command which should be used for any file ending in `rest`, -`rst`, `rest.txt` or `rst.txt`. Any regular expression will do. +If you are having an issue with: -Finally add your [tests](#testing). +* **Syntax highlighting** - see [github/linguist](https://github.com/github/linguist/blob/master/CONTRIBUTING.md#fixing-syntax-highlighting) +* **Markdown on GitHub** - contact [GitHub Support](https://support.github.com/) +* **Styling issues on GitHub** - see [primer-markdown](https://github.com/primer/primer-css/tree/master/modules/primer-markdown) module in the [primer/primer-css](https://github.com/primer/primer-css) repository -### Classes +Anything else - [search open issues](https://github.com/github/markup/issues) or create an issue and and we'll help point you in the right direction. -If your markup can be translated using a Ruby library, that's -great. Check out `lib/github/markups.rb` for some -examples. Let's look at Markdown: +## Submitting a Pull Request - markup(:markdown, /md|mkdn?|markdown/) do |content| - Markdown.new(content).to_html - end - -We give the `markup` method three bits of information: the name of the -file to `require`, a regular expression for extensions to match, and a -block to run with unformatted markup which should return HTML. +1. Fork it. +2. Create a branch (`git checkout -b my_markup`) +3. Commit your changes (`git commit -am "Added Snarkdown"`) +4. Push to the branch (`git push origin my_markup`) +5. Open a [Pull Request][1] +6. Enjoy a refreshing Diet Coke and wait -If you need to monkeypatch a RubyGem or something, check out the -included RDoc example. +**dependencies** -Finally add your [tests](#testing). +You can run `script/bootstrap.contrib` to fetch them all. -### Testing +## Testing To run the tests: $ rake -When adding support for a new markup library, create a `README.extension` in `test/markups` along with a `README.extension.html`. As you may imagine, the `README.extension` should be your known input and the -`README.extension.html` should be the desired output. - -Now run the tests: `rake` - If nothing complains, congratulations! ## Releasing a new version diff --git a/Gemfile b/Gemfile index 2e980fcc..6bb9fcb8 100644 --- a/Gemfile +++ b/Gemfile @@ -1,13 +1,14 @@ -source "http://rubygems.org" +source "https://rubygems.org" gemspec -gem "posix-spawn", :platforms => :ruby gem "redcarpet", :platforms => :ruby gem "kramdown", :platforms => :jruby gem "RedCloth" -gem "rdoc", "~>3.6" -gem "org-ruby", "= 0.9.9" -gem "creole", "~>0.3.6" -gem "wikicloth", "=0.8.1", :platforms => :ruby -gem "asciidoctor", "= 0.1.4" -gem "rake" \ No newline at end of file +gem "commonmarker", "~> 2.8.2" +gem "rdoc", "~> 7.2.0" +gem "org-ruby", "0.9.12" +gem "creole", "~>0.5.0" +gem "wikicloth", "=0.8.3" +gem "twitter-text", "~> 1.14" +gem "asciidoctor", "~> 2.0.26" +gem "rexml" diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 00000000..0d39fc8f --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,161 @@ +PATH + remote: . + specs: + github-markup (6.0.0) + +GEM + remote: https://rubygems.org/ + specs: + RedCloth (4.3.4) + activesupport (8.1.3) + base64 + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + json + logger (>= 1.4.2) + minitest (>= 5.1) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + uri (>= 0.13.1) + asciidoctor (2.0.26) + base64 (0.3.0) + bigdecimal (4.1.2) + builder (3.3.0) + cgi (0.5.1) + charlock_holmes (0.7.9) + commonmarker (2.8.2) + rb_sys (~> 0.9) + commonmarker (2.8.2-aarch64-linux) + commonmarker (2.8.2-arm-linux) + commonmarker (2.8.2-arm64-darwin) + commonmarker (2.8.2-x86_64-darwin) + commonmarker (2.8.2-x86_64-linux) + concurrent-ruby (1.3.6) + connection_pool (3.0.2) + creole (0.5.0) + date (3.5.1) + docile (1.4.1) + drb (2.2.3) + erb (6.0.4) + expression_parser (0.9.0) + github-linguist (9.5.0) + cgi + charlock_holmes (~> 0.7.7) + mini_mime (~> 1.0) + rugged (~> 1.0) + html-pipeline (3.2.4) + selma (~> 0.4) + zeitwerk (~> 2.5) + htmlentities (4.3.4) + i18n (1.14.8) + concurrent-ruby (~> 1.0) + json (2.19.5) + logger (1.7.0) + mini_mime (1.1.5) + mini_portile2 (2.8.9) + minitest (6.0.6) + drb (~> 2.0) + prism (~> 1.5) + nokogiri (1.19.3) + mini_portile2 (~> 2.8.2) + racc (~> 1.4) + nokogiri (1.19.3-aarch64-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.3-arm-linux-gnu) + racc (~> 1.4) + nokogiri (1.19.3-arm64-darwin) + racc (~> 1.4) + nokogiri (1.19.3-x86_64-darwin) + racc (~> 1.4) + nokogiri (1.19.3-x86_64-linux-gnu) + racc (~> 1.4) + nokogiri-diff (0.3.0) + nokogiri (~> 1.5) + tdiff (~> 0.4) + org-ruby (0.9.12) + rubypants (~> 0.2) + prism (1.9.0) + psych (5.3.1) + date + stringio + racc (1.8.1) + rake (13.4.2) + rake-compiler-dock (1.12.0) + rb_sys (0.9.128) + rake-compiler-dock (= 1.12.0) + rdoc (7.2.0) + erb + psych (>= 4.0.0) + tsort + redcarpet (3.6.1) + rexml (3.4.4) + rubypants (0.7.1) + rugged (1.9.0) + securerandom (0.4.1) + selma (0.5.0) + rb_sys (~> 0.9) + selma (0.5.0-aarch64-linux) + selma (0.5.0-arm-linux) + selma (0.5.0-arm64-darwin) + selma (0.5.0-x86_64-darwin) + selma (0.5.0-x86_64-linux) + simplecov (0.22.0) + docile (~> 1.1) + simplecov-html (~> 0.11) + simplecov_json_formatter (~> 0.1) + simplecov-html (0.13.2) + simplecov_json_formatter (0.1.4) + stringio (3.2.0) + tdiff (0.4.0) + tsort (0.2.0) + twitter-text (1.14.7) + unf (~> 0.1.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + unf (0.1.4) + unf_ext + unf_ext (0.0.9.1) + uri (1.1.1) + wikicloth (0.8.3) + builder + expression_parser + htmlentities + nokogiri + twitter-text + zeitwerk (2.8.2) + +PLATFORMS + aarch64-linux + arm-linux + arm64-darwin + x86-linux + x86_64-darwin + x86_64-linux + +DEPENDENCIES + RedCloth + activesupport (~> 8.1.3) + asciidoctor (~> 2.0.26) + commonmarker (~> 2.8.2) + creole (~> 0.5.0) + github-linguist (>= 7.1.3) + github-markup! + html-pipeline (~> 3.2) + kramdown + minitest (~> 6.0, >= 5.4.3) + nokogiri (~> 1.19.2) + nokogiri-diff (~> 0.3.0) + org-ruby (= 0.9.12) + rake (~> 13) + rdoc (~> 7.2.0) + redcarpet + rexml + simplecov (~> 0.22) + twitter-text (~> 1.14) + wikicloth (= 0.8.3) + +BUNDLED WITH + 2.3.26 diff --git a/HISTORY.md b/HISTORY.md index 2c2ca4e2..33c1ea2e 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,3 +1,157 @@ +## 6.0.0 - 2026-05-04 + +### Breaking + +* Drop support for Ruby < 3.3 (Ruby 3.1 EOL March 2025, Ruby 3.2 EOL March 2026) + +### Security + +* Use HTTPS for cpanminus download in CI to prevent MITM attacks [#2050](https://github.com/github/markup/pull/2050) +* Bump nokogiri to >= 1.19.1 to fix GHSA-wx95-c6cv-8532 +* Harden CI pip install with `--require-hashes --no-deps` [#2048](https://github.com/github/markup/pull/2048) +* Use HTTPS for RubyGems source URL in Gemfile [#1854](https://github.com/github/markup/pull/1854) +* Add explicit `contents: read` permissions to CI workflow [#1855](https://github.com/github/markup/pull/1855) +* Bump rexml from 3.3.0 to 3.3.9 (multiple security fixes) + +### Bug Fixes + +* Fix RST section ID rendering to iterate all anchors instead of only the first [040f91d](https://github.com/github/markup/commit/040f91d) + +### Documentation + +* Fix spelling errors in codebase [#1479](https://github.com/github/markup/pull/1479) +* Fix broken Textile language link in README [#1703](https://github.com/github/markup/pull/1703) + +### Infrastructure + +* Remove legacy Dockerfile and .dockerignore (Ubuntu Trusty, non-functional) [#2048](https://github.com/github/markup/pull/2048) +* Update CI test matrix: drop Ruby 3.2, add Ruby 4.0 (now testing 3.3, 3.4, 4.0) +* Bump nokogiri to 1.19.2, activesupport to 7.2.3.1 +* Bump github-linguist from 7.30.0 to 9.1.0, then to 9.3.0 +* Pin all GitHub Actions to commit SHAs for supply chain hardening +* Add Dependabot configuration for automated dependency updates +* Various Dependabot bumps for CI actions (ruby/setup-ruby, actions/checkout, actions/cache, actions/setup-python, actions/stale) + +## 5.0.1 - 2024-06-17 +* Bump activesupport from 4.0 to 7.1.3.4 + +## 5.0.0 - 2024-06-17 +* Drop support for Ruby versions < 3 +* Bump nokogiri from 1.8.1 to 1.16.5 +* Bump nokogiri-diff from 0.2.0 to 0.3.0 +* Bump rdoc from 3.6 to 6.7.0 +* Update CommandImplementation to better support large files (affecting RST and POD6 rendering) + +## 4.0.2 - 2023-10-10 +* Add support for .mdx files in markdown + +## 4.0.1 - 2022-03-07 +* Update to commonmarker 0.18.3; There isn't a version on RubyGems for this, so this is pointing to a [tag version on GitHub](https://github.com/gjtorikian/commonmarker/blob/v0.18.3/commonmarker.gemspec) + +## 4.0.0 - 2021-03-31 + +* Drop support for Python 2 in RST rendering [#1456](https://github.com/github/markup/pull/1456) + +## 3.0.5 - 2020-11-12 + +* Add commonmarker_exts to commonmarker options [#1268](https://github.com/github/markup/pull/1268) +* Check whether filename is set when rendering Asciidoc. [#1290](https://github.com/github/markup/pull/1290) + +## 3.0.4 - 2019-04-03 + +* Expose options in #render_s [#1249](https://github.com/github/markup/pull/1249) +* Upgrade to Asciidoctor 2.0.x [#1264](https://github.com/github/markup/pull/1264) + +## 3.0.3 - 2018-12-17 + +* Temporarily remove support for POD6 [#1248](https://github.com/github/markup/pull/1248) + +## 3.0.2 - 2018-12-12 + +* Add support for POD6 [#1173](https://github.com/github/markup/pull/1173) + +## 3.0.1 - 2018-10-19 + +* Remove linguist-detected RMarkdown files from the Markdown renderer [#1237](https://github.com/github/markup/pull/1237) + +## 3.0.0 - 2018-10-18 + +* Allow passing options through to CommonMarker [#1236](https://github.com/github/markup/pull/1236) +* Symlink option is now a keyword arg [#1236](https://github.com/github/markup/pull/1236) + +## 2.0.2 - 2018-10-15 + +* Don't render rmd files as Markdown [#1235](https://github.com/github/markup/pull/1235) + +## 2.0.1 - 2018-06-29 + +* Create anchor for every =item directive in POD files [#1165](https://github.com/github/markup/pull/1165) + +## 2.0.0 - 2018-01-31 + +* Remove filesystem access [#1157](https://github.com/github/markup/pull/1157) + +## 1.7.0 - 2018-01-30 + +### Changed + +* Updates for Linguist v6 [#1139](https://github.com/github/markup/pull/1139) +* Update to Nokogiri ~> 1.8; drop support for Ruby 2.0 [#1156](https://github.com/github/markup/pull/1156) + +## 1.6.2 - 2017-11-27 + +### Changed + +* Only report basename in usage [#1131](https://github.com/github/markup/pull/1131) +* rest2html parameter signature fix [#1082](https://github.com/github/markup/pull/1082) + +## 1.6.1 - 2017-07-25 + +### Changed + +* Added support for highlight directive in rST [#925](https://github.com/github/markup/pull/925) +* Fixes to documentation and code style [#1009](https://github.com/github/markup/pull/1009) [#1071](https://github.com/github/markup/pull/1071) [#1087](https://github.com/github/markup/pull/1087) +* Test against newer Ruby versions [#1086](https://github.com/github/markup/pull/1086) +* Upgrade to Asciidoctor 1.5.6.1 [#1088](https://github.com/github/markup/pull/1088) + +## 1.6.0 - 2017-04-03 + +### Changed + +* Added filename argument to all renderers for additional context +* Removed superfluous `rinku` dependency [#1035](https://github.com/github/markup/pull/1035) +* Enable source-to-source navigation for `.adoc` AsciiDoc files, plus additional attributes passed through [#1039](https://github.com/github/markup/pull/1039) and [#1041](https://github.com/github/markup/pull/1041) + +## 1.5.0 - 2017-03-27 + +### Added + +* Re-introduce [#537](https://github.com/github/markup/pull/537) to detect language of markup document + However `github-linguist` is optional and this gem will fallback to extensions for detection. + +[Full changelog](https://github.com/github/markup/compare/v1.4.9...v1.5.0) + +## 1.4.9 - 2017-03-27 + +### Changed + +* Reverted [#537](https://github.com/github/markup/pull/537) to avoid extra dependencies + +[Full changelog](https://github.com/github/markup/compare/v1.4.8...v1.4.9) + +## 1.3.3 (2015-02-17) + +* Address a slight typo with `POSIX` [#456](https://github.com/github/markup/pull/456) + +[Full changelog](https://github.com/github/markup/compare/v1.3.2...v1.3.3) + +## 1.3.2 (2015-02-17) + +* RST: Output code instead of tt for inline literals [#370](https://github.com/github/markup/pull/370) +* RST: Add IDs to headers so that `.. contents` works with `.. sectnum` [#391](https://github.com/github/markup/pull/391) + +[Full changelog](https://github.com/github/markup/compare/v1.3.1...v1.3.2) + ## 1.3.1 (2014-11-13) * Fix name error when trying to use newer versions of RedCarpet [#387](https://github.com/github/markup/pull/387) diff --git a/README.md b/README.md index 2b8f0911..12188dcb 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,17 @@ GitHub Markup ============= -We use this library on GitHub when rendering your README or any other -rich text file. The generated HTML is then run through filters in the [html-pipeline](https://github.com/jch/html-pipeline) to perform things like [sanitization](#html-sanitization) and [syntax highlighting](https://github.com/jch/html-pipeline/blob/master/lib/html/pipeline/syntax_highlight_filter.rb). +This library is the **first step** of a journey that every markup file in a repository goes on before it is rendered on GitHub.com: + +1. `github-markup` selects an _underlying library_ to convert the raw markup to HTML. See the list of [supported markup formats](#markups) below. +1. The HTML is sanitized, aggressively removing things that could harm you and your kin—such as `script` tags, inline-styles, and `class` or `id` attributes. +1. Syntax highlighting is performed on code blocks. See [github/linguist](https://github.com/github/linguist#syntax-highlighting) for more information about syntax highlighting. +1. The HTML is passed through other filters that add special sauce, such as emoji, task lists, named anchors, CDN caching for images, and autolinking. +1. The resulting HTML is rendered on GitHub.com. + +Please note that **only the first step** is covered by this gem — the rest happens on GitHub.com. In particular, `markup` itself does no sanitization of the resulting HTML, as it expects that to be covered by whatever pipeline is consuming the HTML. + +Please see our [contributing guidelines](CONTRIBUTING.md) before reporting an issue. Markups ------- @@ -10,63 +19,70 @@ Markups The following markups are supported. The dependencies listed are required if you wish to run the library. You can also run `script/bootstrap` to fetch them all. -* [.markdown, .mdown, .mkdn, .md](http://daringfireball.net/projects/markdown/) -- `gem install redcarpet` (https://github.com/vmg/redcarpet) -* [.textile](http://www.textism.com/tools/textile/) -- `gem install RedCloth` -* [.rdoc](http://rdoc.sourceforge.net/) -- `gem install rdoc -v 3.6.1` -* [.org](http://orgmode.org/) -- `gem install org-ruby` -* [.creole](http://wikicreole.org/) -- `gem install creole` -* [.mediawiki, .wiki](http://www.mediawiki.org/wiki/Help:Formatting) -- `gem install wikicloth` -* [.rst](http://docutils.sourceforge.net/rst.html) -- `easy_install docutils` +* [.markdown, .mdown, .mkdn, .md](http://daringfireball.net/projects/markdown/) -- `gem install commonmarker` (https://github.com/gjtorikian/commonmarker) +* [.textile](https://textile-lang.com/) -- `gem install RedCloth` (https://github.com/jgarber/redcloth) +* [.rdoc](https://ruby.github.io/rdoc/) -- `gem install rdoc -v 3.6.1` +* [.org](http://orgmode.org/) -- `gem install org-ruby` (https://github.com/wallyqs/org-ruby) +* [.creole](http://wikicreole.org/) -- `gem install creole` (https://github.com/larsch/creole) +* [.mediawiki, .wiki](http://www.mediawiki.org/wiki/Help:Formatting) -- `gem install wikicloth` (https://github.com/nricciar/wikicloth) +* [.rst](http://docutils.sourceforge.net/rst.html) -- `pip install docutils` * [.asciidoc, .adoc, .asc](http://asciidoc.org/) -- `gem install asciidoctor` (http://asciidoctor.org) -* [.pod](http://search.cpan.org/dist/perl/pod/perlpod.pod) -- `Pod::Simple::HTML` - comes with Perl >= 5.10. Lower versions should install [Pod::Simple](http://search.cpan.org/~dwheeler/Pod-Simple-3.28/lib/Pod/Simple.pod) from CPAN. +* [.pod](http://search.cpan.org/dist/perl/pod/perlpod.pod) -- `Pod::Simple::XHTML` + comes with Perl >= 5.10. Lower versions should install Pod::Simple from CPAN. Installation ----------- - gem install github-markup +``` +gem install github-markup +``` + +or + +``` +bundle install +``` + +from this directory. Usage ----- - require 'github/markup' - GitHub::Markup.render('README.markdown', "* One\n* Two") +Basic form: -Or, more realistically: +```ruby +require 'github/markup' - require 'github/markup' - GitHub::Markup.render(file, File.read(file)) +GitHub::Markup.render('README.markdown', "* One\n* Two") +``` -Contributing ------------- +More realistic form: -See [Contributing](CONTRIBUTING.md) +```ruby +require 'github/markup' -HTML sanitization ------------------ +GitHub::Markup.render(file, File.read(file)) +``` + +And a convenience form: -HTML rendered by the various markup language processors gets passed through an [HTML sanitization filter](https://github.com/jch/html-pipeline/blob/master/lib/html/pipeline/sanitization_filter.rb) for security reasons. HTML elements not in the whitelist are removed. HTML attributes not in the whitelist are removed from the preserved elements. +```ruby +require 'github/markup' -The following HTML elements, organized by category, are whitelisted: +GitHub::Markup.render_s(GitHub::Markups::MARKUP_MARKDOWN, "* One\n* Two") +``` -|Type | Elements -|------|---------- -|Headings | `h1`, `h2`, `h3`, `h4`, `h5`, `h6`, `h7`, `h8` -|Prose | `p`, `div`, `blockquote` -|Formatted | `pre` -| Inline | `b`, `i`, `strong`, `em`, `tt`, `code`, `ins`, `del`, `sup`, `sub`, `kbd`, `samp`, `q`, `var` -| Lists | `ol`, `ul`, `li`, `dl`, `dt`, `dd` -| Tables | `table`, `thead`, `tbody`, `tfoot`, `tr`, `td`, `th` -| Breaks | `br`, `hr` -| Ruby (East Asian) | `ruby`, `rt`, `rp` +Local Development +----------------- -The following attributes, organized by element, are whitelisted: +```sh +python3 -m venv .venv +source .venv/bin/activate +cd script +./bootstrap +``` -|Element | Attributes -|------|---------- -| `a` | `href` (`http://`, `https://`, `mailto://`, `github-windows://`, and `github-mac://` URI schemes and relative paths only) -| `img` | `src` (`http://` and `https://` URI schemes and relative paths only) -| `div` | `itemscope`, `itemtype` -| All | `abbr`, `accept`, `accept-charset`, `accesskey`, `action`, `align`, `alt`, `axis`, `border`, `cellpadding`, `cellspacing`, `char`, `charoff`, `charset`, `checked`, `cite`, `clear`, `cols`, `colspan`, `color`, `compact`, `coords`, `datetime`, `dir`, `disabled`, `enctype`, `for`, `frame`, `headers`, `height`, `hreflang`, `hspace`, `ismap`, `label`, `lang`, `longdesc`, `maxlength`, `media`, `method`, `multiple`, `name`, `nohref`, `noshade`, `nowrap`, `prompt`, `readonly`, `rel`, `rev`, `rows`, `rowspan`, `rules`, `scope`, `selected`, `shape`, `size`, `span`, `start`, `summary`, `tabindex`, `target`, `title`, `type`, `usemap`, `valign`, `value`, `vspace`, `width`, `itemprop` +Contributing +------------ -Note that the `id` attribute is *not* whitelisted. +See [Contributing](CONTRIBUTING.md). diff --git a/bin/github-markup b/bin/github-markup index 76e5e369..ec967359 100755 --- a/bin/github-markup +++ b/bin/github-markup @@ -1,10 +1,28 @@ #!/usr/bin/env ruby -$LOAD_PATH.unshift File.dirname(__FILE__) + "/../lib" +$LOAD_PATH.unshift File.dirname(File.realpath(__FILE__)) + "/../lib" require 'github/markup' -if ARGV[0] && File.exists?(file = ARGV[0]) - puts GitHub::Markup.render(file) -else - puts "usage: #$0 FILE" +if ARGV.size < 1 + print "usage: #{File.basename($0)} FILE [ FILES ... ]\n" + exit 1 end + +sources = [] + +ARGV.each { |s| + begin + file = File.open( s, "r" ) + sources.push [ s, file ] + rescue Exception => e + $stderr.print "error: #{e.message}\n" + exit 1 + ensure + end +} + +sources.each { |name, file| + print GitHub::Markup.render( name, file.read ) + file.close +} + diff --git a/github-markup.gemspec b/github-markup.gemspec index 62e4770b..45d4a0a6 100644 --- a/github-markup.gemspec +++ b/github-markup.gemspec @@ -1,24 +1,34 @@ -require File.expand_path("../lib/github-markup", __FILE__) +version_file = File.expand_path("../lib/github-markup.rb", __FILE__) +version_match = File.read(version_file).match(/VERSION = ['"]([^'"]+)['"]/) +raise "Could not find VERSION in #{version_file}" unless version_match +version = version_match[1] Gem::Specification.new do |s| s.name = "github-markup" - s.version = GitHub::Markup::VERSION + s.version = version s.summary = "The code GitHub uses to render README.markup" - s.description = "This gem is used by GitHub to render any fancy markup such " + - "as Markdown, Textile, Org-Mode, etc. Fork it and add your own!" + s.description = <<~DESC + This gem is used by GitHub to render any fancy markup such as Markdown, + Textile, Org-Mode, etc. Fork it and add your own! + DESC s.authors = ["Chris Wanstrath"] s.email = "chris@ozmm.org" s.homepage = "https://github.com/github/markup" s.license = "MIT" + s.required_ruby_version = '>= 3.3.0' + s.files = `git ls-files`.split($\) s.executables = s.files.grep(%r{^bin/}).map{ |f| File.basename(f) } s.test_files = s.files.grep(%r{^(test|spec|features)/}) s.require_paths = %w[lib] - s.add_development_dependency 'minitest', '~> 5.4.3' - s.add_development_dependency 'html-pipeline', '~> 1.0' - s.add_development_dependency 'sanitize', '~> 2.1.0' - s.add_development_dependency 'nokogiri', '~> 1.6.1' - s.add_development_dependency 'nokogiri-diff', '~> 0.2.0' + s.add_development_dependency 'rake', '~> 13' + s.add_development_dependency 'activesupport', '~> 8.1.3' + s.add_development_dependency 'minitest', '>= 5.4.3', '~> 6.0' + s.add_development_dependency 'html-pipeline', '~> 3.2' + s.add_development_dependency 'nokogiri', '~> 1.19.2' + s.add_development_dependency 'nokogiri-diff', '~> 0.3.0' + s.add_development_dependency "github-linguist", ">= 7.1.3" + s.add_development_dependency 'simplecov', '~> 0.22' end diff --git a/lib/github-markup.rb b/lib/github-markup.rb index 2f98a768..bd470da1 100644 --- a/lib/github-markup.rb +++ b/lib/github-markup.rb @@ -1,6 +1,6 @@ module GitHub module Markup - VERSION = '1.3.1' + VERSION = '6.0.0' Version = VERSION end end diff --git a/lib/github/commands/pod2html b/lib/github/commands/pod2html new file mode 100755 index 00000000..aaed6022 --- /dev/null +++ b/lib/github/commands/pod2html @@ -0,0 +1,16 @@ +#!/usr/bin/env perl + +use strict; +use Pod::Simple::XHTML 3.11; + +my $p = Pod::Simple::XHTML->new; +$p->html_header(''); +$p->html_footer(''); +$p->perldoc_url_prefix('https://metacpan.org/pod/'); +$p->anchor_items(1); +$p->strip_verbatim_indent(sub { + my $lines = shift; + (my $indent = $lines->[0]) =~ s/\S.*//; + return $indent; +}); +$p->parse_from_file(shift); diff --git a/lib/github/commands/pod62html b/lib/github/commands/pod62html new file mode 100755 index 00000000..b2d27764 --- /dev/null +++ b/lib/github/commands/pod62html @@ -0,0 +1,5 @@ +#!/usr/bin/env perl6 + +use v6; + +slurp.put; diff --git a/lib/github/commands/rest2html b/lib/github/commands/rest2html index ca9f26ff..0c79bbb4 100755 --- a/lib/github/commands/rest2html +++ b/lib/github/commands/rest2html @@ -31,9 +31,11 @@ import sys import os # This fixes docutils failing with unicode parameters to CSV-Table. The -S -# switch and the following 2 lines can be removed after upgrading to python 3. -reload(sys) -sys.setdefaultencoding('utf-8') +# switch and the following 3 lines can be removed after upgrading to python 3. +if sys.version_info[0] < 3: + reload(sys) + sys.setdefaultencoding('utf-8') + import site try: @@ -43,35 +45,163 @@ except: pass import codecs +import io +from docutils import nodes, utils +from docutils.parsers.rst import directives, roles +from docutils.parsers.rst.directives.body import CodeBlock, Directive from docutils.core import publish_parts from docutils.writers.html4css1 import Writer, HTMLTranslator +from docutils.parsers.rst.states import Body +from docutils import nodes + +# By default, docutils provides two choices for unknown directives: +# - Show errors if the reporting level is high enough +# - Silently do not display them otherwise +# Comments are not displayed, either. + +# This code monkey-patches docutils to show preformatted lines +# in both these cases. + +# Recommended practice for repositories with rst files: +# - If github is the preferred viewer for the rst files (e.g. README.rst), +# then design the files to properly display for github. E.g., do not +# include unknown directives or restructuredText comments. +# - If github is NOT the preferred viewer for the rst files (e.g. +# the files are designed to be used with sphinx extensions at readthedocs) +# then include a restructuredText comment at the top of the file +# explaining that the document will display much more nicely with its +# preferred viewer, e.g. at readthedocs. + +original_behavior = False # Documents original docutils behavior +github_display = True + +def extract_extension_options(field_list, option_spec): + """ + Overrides `utils.extract_extension_options` and inlines + `utils.assemble_option_dict` to make it ignore unknown options passed to + directives (i.e. ``:caption:`` for ``.. code-block:``). + """ + + dropped = set() + options = {} + for name, value in utils.extract_options(field_list): + convertor = option_spec.get(name) + if name in options or name in dropped: + raise utils.DuplicateOptionError('duplicate option "%s"' % name) + + # silently drop unknown options as long as they are not duplicates + if convertor is None: + dropped.add(name) + continue + + # continue as before + try: + options[name] = convertor(value) + except (ValueError, TypeError) as detail: + raise detail.__class__('(option: "%s"; value: %r)\n%s' + % (name, value, ' '.join(detail.args))) + return options + +utils.extract_extension_options = extract_extension_options + +def unknown_directive(self, type_name): + lineno = self.state_machine.abs_line_number() + indented, indent, offset, blank_finish = \ + self.state_machine.get_first_known_indented(0, strip_indent=False) + text = '\n'.join(indented) + if original_behavior: + error = self.reporter.error( + 'Unknown directive type "%s".' % type_name, + nodes.literal_block(text, text), line=lineno) + return [error], blank_finish + elif github_display: + cls = ['unknown_directive'] + result = [nodes.literal_block(text, text, classes=cls)] + return result, blank_finish + else: + return [nodes.comment(text, text)], blank_finish + +def comment(self, match): + if not match.string[match.end():].strip() \ + and self.state_machine.is_next_line_blank(): # an empty comment? + return [nodes.comment()], 1 # "A tiny but practical wart." + indented, indent, offset, blank_finish = \ + self.state_machine.get_first_known_indented(match.end()) + while indented and not indented[-1].strip(): + indented.trim_end() + if not original_behavior: + firstline = ''.join(indented[:1]).split() + if ' '.join(firstline[:2]) == 'github display': + if len(firstline) == 3 and firstline[2] in ('on', 'off'): + global github_display + github_display = firstline[2] == 'on' + if len(indented) == 1: + return [nodes.comment()], 1 + text = '\n'.join(indented[1:]) + cls = ['github_comment'] + result = [nodes.literal_block(text, text, classes=cls)] + return result, blank_finish + text = '\n'.join(indented) + return [nodes.comment(text, text)], blank_finish + +Body.comment = comment +Body.unknown_directive = unknown_directive + SETTINGS = { - 'cloak_email_addresses': True, + 'cloak_email_addresses': False, 'file_insertion_enabled': False, - 'raw_enabled': False, + 'raw_enabled': True, 'strip_comments': True, 'doctitle_xform': True, - 'sectsubtitle_xform': True, 'initial_header_level': 2, 'report_level': 5, - 'syntax_highlight' : 'none', - 'math_output' : 'latex', + 'syntax_highlight': 'none', + 'math_output': 'latex', 'field_name_limit': 50, + 'auto_id_prefix': 'id', } +default_highlight_language = None + +class HighlightDirective(Directive): + required_arguments = 1 + optional_arguments = 1 + option_spec = {} + def run(self): + """Track the default syntax highlighting language + """ + global default_highlight_language + default_highlight_language = self.arguments[0] + return [] + +class DoctestDirective(CodeBlock): + """Render Sphinx 'doctest:: [group]' blocks as 'code:: python' + """ + + def run(self): + """Discard any doctest group argument, render contents as python code + """ + self.arguments = ['python'] + return super(DoctestDirective, self).run() + + class GitHubHTMLTranslator(HTMLTranslator): + # removes the
, not
# this also avoids the generation of superfluous tags
def visit_literal(self, node):
@@ -97,32 +232,37 @@ class GitHubHTMLTranslator(HTMLTranslator):
def visit_table(self, node):
classes = ' '.join(['docutils', self.settings.table_style]).strip()
self.body.append(
- self.starttag(node, 'table', CLASS=classes))
+ self.starttag(node, 'table', CLASS=classes))
def depart_table(self, node):
self.body.append('\n')
def depart_image(self, node):
- uri = node['uri']
- ext = os.path.splitext(uri)[1].lower()
- # we need to swap RST's use of `object` with `img` tags
- # see http://git.io/5me3dA
- if ext == ".svg":
- # preserve essential attributes
- atts = {}
- for attribute, value in node.attributes.items():
- # we have no time for empty values
- if value:
- if attribute == "uri":
- atts['src'] = value
- else:
- atts[attribute] = value
-
- # toss off `object` tag
- self.body.pop()
- # add on `img` with attributes
- self.body.append(self.starttag(node, 'img', **atts))
- self.body.append(self.context.pop())
+ uri = node['uri']
+ ext = os.path.splitext(uri)[1].lower()
+ # we need to swap RST's use of `object` with `img` tags
+ # see http://git.io/5me3dA
+ if ext == ".svg":
+ # preserve essential attributes
+ atts = {}
+ for attribute, value in node.attributes.items():
+ # we have no time for empty values
+ if value:
+ if attribute == "uri":
+ atts['src'] = value
+ else:
+ atts[attribute] = value
+
+ # toss off `object` tag
+ self.body.pop()
+ # add on `img` with attributes
+ self.body.append(self.starttag(node, 'img', **atts))
+ HTMLTranslator.depart_image(self, node)
+
+
+def kbd(name, rawtext, text, lineno, inliner, options={}, content=[]):
+ return [nodes.raw('', '%s' % text, format='html')], []
+
def main():
"""
@@ -134,14 +274,26 @@ def main():
"""
try:
text = codecs.open(sys.argv[1], 'r', 'utf-8').read()
- except IOError: # given filename could not be found
+ except IOError: # given filename could not be found
return ''
- except IndexError: # no filename given
- text = sys.stdin.read()
+ except IndexError: # no filename given
+ if sys.version_info[0] < 3: # python 2.x
+ text = sys.stdin.read()
+ else: # python 3
+ input_stream = io.TextIOWrapper(sys.stdin.buffer, encoding='utf-8')
+ text = input_stream.read()
writer = Writer()
writer.translator_class = GitHubHTMLTranslator
+ roles.register_canonical_role('kbd', kbd)
+
+ # Render source code in Sphinx doctest blocks
+ directives.register_directive('doctest', DoctestDirective)
+
+ # Set default highlight language
+ directives.register_directive('highlight', HighlightDirective)
+
parts = publish_parts(text, writer=writer, settings_overrides=SETTINGS)
if 'html_body' in parts:
html = parts['html_body']
@@ -155,5 +307,9 @@ def main():
return ''
if __name__ == '__main__':
- sys.stdout.write("%s%s" % (main(), "\n"))
+ if sys.version_info[0] < 3: # python 2.x
+ sys.stdout.write("%s%s" % (main(), "\n"))
+ else: # python 3
+ output_stream = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
+ output_stream.write("%s%s" % (main(), "\n"))
sys.stdout.flush()
diff --git a/lib/github/markup.rb b/lib/github/markup.rb
index 82de5172..a7ba9863 100644
--- a/lib/github/markup.rb
+++ b/lib/github/markup.rb
@@ -1,55 +1,102 @@
+begin
+ require "linguist"
+rescue LoadError
+ # Rely on extensions instead.
+end
+
require "github/markup/command_implementation"
require "github/markup/gem_implementation"
module GitHub
+ module Markups
+ # all of supported markups:
+ MARKUP_ASCIIDOC = :asciidoc
+ MARKUP_CREOLE = :creole
+ MARKUP_MARKDOWN = :markdown
+ MARKUP_MEDIAWIKI = :mediawiki
+ MARKUP_ORG = :org
+ MARKUP_POD = :pod
+ MARKUP_RDOC = :rdoc
+ MARKUP_RST = :rst
+ MARKUP_TEXTILE = :textile
+ MARKUP_POD6 = :pod6
+ end
+
module Markup
extend self
- @@markups = []
+
+ @@markups = {}
def markups
@@markups
end
+ def markup_impls
+ markups.values
+ end
+
def preload!
- markups.each do |markup|
- markup.load
+ markup_impls.each(&:load)
+ end
+
+ def render(filename, content, symlink: false, options: {})
+ if (impl = renderer(filename, content, symlink: symlink))
+ impl.render(filename, content, options: options)
+ else
+ content
end
end
- def render(filename, content = nil)
- content ||= File.read(filename)
+ def render_s(symbol, content, options: {})
+ raise ArgumentError, 'Can not render a nil.' if content.nil?
- if impl = renderer(filename)
- impl.render(content)
+ if markups.key?(symbol)
+ markups[symbol].render(nil, content, options: options)
else
content
end
end
- def markup(file, pattern, opts = {}, &block)
- markups << GemImplementation.new(pattern, file, &block)
+ def markup(symbol, gem_name, regexp, languages, opts = {}, &block)
+ impl = GemImplementation.new(regexp, languages, gem_name, &block)
+ markup_impl(symbol, impl)
+ end
+
+ def markup_impl(symbol, impl)
+ if markups.key?(symbol)
+ raise ArgumentError, "The '#{symbol}' symbol is already defined."
+ end
+ markups[symbol] = impl
end
- def command(command, regexp, &block)
+ def command(symbol, command, regexp, languages, name, &block)
if File.exist?(file = File.dirname(__FILE__) + "/commands/#{command}")
command = file
end
- markups << CommandImplementation.new(regexp, command, &block)
+ impl = CommandImplementation.new(regexp, languages, command, name, &block)
+ markup_impl(symbol, impl)
end
- def can_render?(filename)
- !!renderer(filename)
+ def can_render?(filename, content, symlink: false)
+ renderer(filename, content, symlink: symlink) != nil
end
- def renderer(filename)
- markups.find { |impl|
- impl.match?(filename)
- }
+ def renderer(filename, content, symlink: false)
+ language = language(filename, content, symlink: symlink)
+ markup_impls.find do |impl|
+ impl.match?(filename, language)
+ end
+ end
+
+ def language(filename, content, symlink: false)
+ return unless defined?(::Linguist)
+
+ blob = Linguist::Blob.new(filename, content, symlink: symlink)
+ Linguist.detect(blob, allow_empty: true)
end
# Define markups
- markups_rb = File.dirname(__FILE__) + '/markups.rb'
- instance_eval File.read(markups_rb), markups_rb
+ require_relative 'markups'
end
end
diff --git a/lib/github/markup/command_implementation.rb b/lib/github/markup/command_implementation.rb
index 1b43dd71..b55f1735 100644
--- a/lib/github/markup/command_implementation.rb
+++ b/lib/github/markup/command_implementation.rb
@@ -1,26 +1,23 @@
-begin
- require "posix-spawn"
-rescue LoadError
- require "open3"
-end
-
+require "open3"
require "github/markup/implementation"
+
module GitHub
module Markup
class CommandError < RuntimeError
end
class CommandImplementation < Implementation
- attr_reader :command, :block
+ attr_reader :command, :block, :name
- def initialize(regexp, command, &block)
- super regexp
+ def initialize(regexp, languages, command, name, &block)
+ super(regexp, languages)
@command = command.to_s
@block = block
+ @name = name
end
- def render(content)
+ def render(filename, content, options: {})
rendered = execute(command, content)
rendered = rendered.to_s.empty? ? content : rendered
call_block(rendered, content)
@@ -36,35 +33,20 @@ def call_block(rendered, content)
rendered
end
end
-
- if defined?(Posix::Spawn)
- def execute(command, target)
- spawn = POSIX::Spawn::Child.new(*command, :input => target)
- if spawn.status.success?
- sanitize(spawn.out, target.encoding)
- else
- raise CommandError.new(spawn.err.strip)
- end
- end
- else
- def execute(command, target)
- output = Open3.popen3(*command) do |stdin, stdout, stderr, wait_thr|
- stdin.puts target
- stdin.close
- if wait_thr.value.success?
- stdout.readlines
- else
- raise CommandError.new(stderr.readlines.join('').strip)
- end
- end
- sanitize(output.join(''), target.encoding)
- end
+
+ def execute(command, target)
+ # capture3 blocks until both buffers are written to and the process terminates, but
+ # it won't allow either buffer to fill up
+ stdout, stderr, status = Open3.capture3(*command, stdin_data: target)
+
+ raise CommandError.new(stderr) unless status.success?
+ sanitize(stdout, target.encoding)
end
-
+
def sanitize(input, encoding)
input.gsub("\r", '').force_encoding(encoding)
end
-
+
end
end
end
diff --git a/lib/github/markup/gem_implementation.rb b/lib/github/markup/gem_implementation.rb
index a9f80b75..843717e6 100644
--- a/lib/github/markup/gem_implementation.rb
+++ b/lib/github/markup/gem_implementation.rb
@@ -5,21 +5,25 @@ module Markup
class GemImplementation < Implementation
attr_reader :gem_name, :renderer
- def initialize(regexp, gem_name, &renderer)
- super regexp
+ def initialize(regexp, languages, gem_name, &renderer)
+ super(regexp, languages)
@gem_name = gem_name.to_s
@renderer = renderer
end
def load
- return if @loaded
+ return if defined?(@loaded) && @loaded
require gem_name
@loaded = true
end
- def render(content)
+ def render(filename, content, options: {})
load
- renderer.call(content)
+ renderer.call(filename, content, options: options)
+ end
+
+ def name
+ gem_name
end
end
end
diff --git a/lib/github/markup/implementation.rb b/lib/github/markup/implementation.rb
index 463a39d4..458ab0d8 100644
--- a/lib/github/markup/implementation.rb
+++ b/lib/github/markup/implementation.rb
@@ -2,24 +2,38 @@ module GitHub
module Markup
class Implementation
attr_reader :regexp
+ attr_reader :languages
- def initialize(regexp)
+ def initialize(regexp, languages)
@regexp = regexp
+
+ if defined?(::Linguist)
+ @languages = languages.map do |l|
+ lang = Linguist::Language[l]
+ raise "no match for language #{l.inspect}" if lang.nil?
+ lang
+ end
+ end
end
def load
# no-op by default
end
- def render(content)
+ def render(filename, content, options: {})
raise NotImplementedError, "subclasses of GitHub::Markup::Implementation must define #render"
end
- def match?(filename)
- file_ext_regexp =~ filename
+ def match?(filename, language)
+ if defined?(::Linguist)
+ languages.include? language
+ else
+ file_ext_regexp =~ filename
+ end
end
- private
+ private
+
def file_ext_regexp
@file_ext_regexp ||= /\.(#{regexp})\z/
end
diff --git a/lib/github/markup/markdown.rb b/lib/github/markup/markdown.rb
index f2744ca3..5b17afb7 100644
--- a/lib/github/markup/markdown.rb
+++ b/lib/github/markup/markdown.rb
@@ -4,28 +4,116 @@ module GitHub
module Markup
class Markdown < Implementation
MARKDOWN_GEMS = {
- "github/markdown" => proc { |content|
+ "commonmarker" => proc { |content, options: {}|
+ legacy_opts = options.fetch(:commonmarker_opts, [])
+ legacy_exts = options.fetch(
+ :commonmarker_exts,
+ [:tagfilter, :autolink, :table, :strikethrough],
+ )
+
+ parse_options = {}
+ # commonmarker 2.x changes several render defaults that diverge from cmark-gfm 0.x:
+ # - hardbreaks defaults to true in 2.x but was false in 0.x.
+ # - escaped_char_spans defaults to true in 2.x and wraps backslash-escaped chars in
+ # ; 0.x emitted bare characters.
+ # - gfm_quirks defaults to false in 2.x; 0.x (cmark-gfm) always had the quirk on,
+ # which collapses ****foo**** to foo instead of nesting.
+ # - github_pre_lang defaults to true in 2.x; set explicitly to match the legacy contract.
+ render_options = {
+ github_pre_lang: true,
+ hardbreaks: false,
+ escaped_char_spans: false,
+ gfm_quirks: true,
+ }
+ extension_options = {}
+
+ legacy_opts.each do |opt|
+ case opt
+ when :DEFAULT then nil
+ when :SOURCEPOS then render_options[:sourcepos] = true
+ when :HARDBREAKS then render_options[:hardbreaks] = true
+ when :NOBREAKS then render_options[:hardbreaks] = false
+ when :SMART then parse_options[:smart] = true
+ when :GITHUB_PRE_LANG then render_options[:github_pre_lang] = true
+ when :UNSAFE then render_options[:unsafe] = true
+ when :FOOTNOTES then extension_options[:footnotes] = true
+ when :FULL_INFO_STRING then render_options[:full_info_string] = true
+ # The legacy options below existed in cmark-gfm 0.x but have no direct commonmarker
+ # 2.x equivalent. Accept them so existing callers don't break, but they have no effect:
+ # :VALIDATE_UTF8 / :LIBERAL_HTML_TAG - enforced at the Rust type layer in 2.x.
+ # :TABLE_PREFER_STYLE_ATTRIBUTES - no 2.x render knob for inline table styles.
+ # :STRIKETHROUGH_DOUBLE_TILDE - 2.x always accepts both single and double tilde.
+ when :VALIDATE_UTF8, :LIBERAL_HTML_TAG,
+ :TABLE_PREFER_STYLE_ATTRIBUTES, :STRIKETHROUGH_DOUBLE_TILDE
+ nil
+ else
+ raise ArgumentError, "unknown commonmarker option: #{opt.inspect}"
+ end
+ end
+
+ legacy_exts.each do |ext|
+ case ext
+ when :strikethrough, :tagfilter, :autolink, :table, :tasklist,
+ :shortcodes, :footnotes, :multiline_block_quotes,
+ :math_dollars, :math_code, :wikilinks_title_after_pipe,
+ :wikilinks_title_before_pipe, :underline, :subscript, :spoiler,
+ :greentext, :alerts, :description_lists
+ extension_options[ext] = true
+ when :header_ids
+ # header_ids takes a string prefix in 2.x rather than a boolean. The legacy contract
+ # only passed it as a symbol, so use an empty prefix to enable anchor generation.
+ extension_options[:header_ids] = ""
+ else
+ raise ArgumentError, "unknown commonmarker extension: #{ext.inspect}"
+ end
+ end
+
+ # Several extensions (tagfilter, autolink, table, strikethrough, tasklist, shortcodes)
+ # are enabled by default in commonmarker 2.x but were strictly opt-in in 0.x. Explicitly
+ # disable any extension the caller did not request so behavior matches the legacy contract.
+ [:strikethrough, :tagfilter, :autolink, :table, :tasklist, :shortcodes].each do |ext|
+ extension_options[ext] = false unless extension_options[ext]
+ end
+
+ # header_ids is enabled by default in commonmarker 2.x (it injects anchor tags inside
+ # every heading). The legacy 0.x wrapper never enabled it implicitly, so disable it
+ # unless the caller explicitly requested it.
+ extension_options[:header_ids] = nil unless extension_options.key?(:header_ids)
+
+ Commonmarker.to_html(
+ content,
+ options: {
+ parse: parse_options,
+ render: render_options,
+ extension: extension_options,
+ },
+ plugins: {syntax_highlighter: nil},
+ )
+ },
+ "github/markdown" => proc { |content, options: {}|
GitHub::Markdown.render(content)
},
- "redcarpet" => proc { |content|
+ "redcarpet" => proc { |content, options: {}|
Redcarpet::Markdown.new(Redcarpet::Render::HTML).render(content)
},
- "rdiscount" => proc { |content|
+ "rdiscount" => proc { |content, options: {}|
RDiscount.new(content).to_html
},
- "maruku" => proc { |content|
+ "maruku" => proc { |content, options: {}|
Maruku.new(content).to_html
},
- "kramdown" => proc { |content|
+ "kramdown" => proc { |content, options: {}|
Kramdown::Document.new(content).to_html
},
- "bluecloth" => proc { |content|
+ "bluecloth" => proc { |content, options: {}|
BlueCloth.new(content).to_html
},
}
def initialize
- super(/md|rmd|mkdn?|mdwn|mdown|markdown|litcoffee/i)
+ super(
+ /md|mkdn?|mdwn|mdown|markdown|mdx|litcoffee/i,
+ ["Markdown", "MDX", "Literate CoffeeScript"])
end
def load
@@ -39,9 +127,13 @@ def load
raise LoadError, "no suitable markdown gem found"
end
- def render(content)
+ def render(filename, content, options: {})
load
- @renderer.call(content)
+ @renderer.call(content, options: options)
+ end
+
+ def name
+ "markdown"
end
private
diff --git a/lib/github/markup/rdoc.rb b/lib/github/markup/rdoc.rb
index 5bf14639..3ac25151 100644
--- a/lib/github/markup/rdoc.rb
+++ b/lib/github/markup/rdoc.rb
@@ -1,20 +1,27 @@
+require "github/markup/implementation"
require "rdoc"
require "rdoc/markup/to_html"
module GitHub
module Markup
- class RDoc
- def initialize(content)
- @content = content
+ class RDoc < Implementation
+ def initialize
+ super(/rdoc/, ["RDoc"])
end
- def to_html
+ def render(filename, content, options: {})
if ::RDoc::VERSION.to_i >= 4
h = ::RDoc::Markup::ToHtml.new(::RDoc::Options.new)
else
+ # :nocov: RDoc < 4 has been unsupported since Ruby 2.4 (2016); modern RDoc requires Options.
h = ::RDoc::Markup::ToHtml.new
+ # :nocov:
end
- h.convert(@content)
+ h.convert(content)
+ end
+
+ def name
+ "rdoc"
end
end
end
diff --git a/lib/github/markups.rb b/lib/github/markups.rb
index eb562909..4b2f8e0e 100644
--- a/lib/github/markups.rb
+++ b/lib/github/markups.rb
@@ -1,44 +1,59 @@
require "github/markup/markdown"
+require "github/markup/rdoc"
require "shellwords"
-markups << GitHub::Markup::Markdown.new
+GitHub::Markup.markup_impl(::GitHub::Markups::MARKUP_MARKDOWN, ::GitHub::Markup::Markdown.new)
-markup(:redcloth, /textile/) do |content|
+GitHub::Markup.markup(::GitHub::Markups::MARKUP_TEXTILE, :redcloth, /textile/, ["Textile"]) do |filename, content, options: {}|
RedCloth.new(content).to_html
end
-markup('github/markup/rdoc', /rdoc/) do |content|
- GitHub::Markup::RDoc.new(content).to_html
-end
+GitHub::Markup.markup_impl(::GitHub::Markups::MARKUP_RDOC, GitHub::Markup::RDoc.new)
-markup('org-ruby', /org/) do |content|
- Orgmode::Parser.new(content, {
- :allow_include_files => false,
+GitHub::Markup.markup(::GitHub::Markups::MARKUP_ORG, 'org-ruby', /org/, ["Org"]) do |filename, content, options: {}|
+ Orgmode::Parser.new(content, {
+ :allow_include_files => false,
:skip_syntax_highlight => true
}).to_html
end
-markup(:creole, /creole/) do |content|
+GitHub::Markup.markup(::GitHub::Markups::MARKUP_CREOLE, :creole, /creole/, ["Creole"]) do |filename, content, options: {}|
Creole.creolize(content)
end
-markup(:wikicloth, /mediawiki|wiki/) do |content|
- WikiCloth::WikiCloth.new(:data => content).to_html(:noedit => true)
+GitHub::Markup.markup(::GitHub::Markups::MARKUP_MEDIAWIKI, :wikicloth, /mediawiki|wiki/, ["MediaWiki"]) do |filename, content, options: {}|
+ wikicloth = WikiCloth::WikiCloth.new(:data => content)
+ WikiCloth::WikiBuffer::HTMLElement::ESCAPED_TAGS << 'tt' unless WikiCloth::WikiBuffer::HTMLElement::ESCAPED_TAGS.include?('tt')
+ wikicloth.to_html(:noedit => true)
end
-markup(:asciidoctor, /adoc|asc(iidoc)?/) do |content|
- Asciidoctor.render(content, :safe => :secure, :attributes => %w(showtitle idprefix idseparator=- env=github env-github source-highlighter=html-pipeline))
+GitHub::Markup.markup(::GitHub::Markups::MARKUP_ASCIIDOC, :asciidoctor, /adoc|asc(iidoc)?/, ["AsciiDoc"]) do |filename, content, options: {}|
+ attributes = {
+ 'showtitle' => '@',
+ 'idprefix' => '',
+ 'idseparator' => '-',
+ 'sectanchors' => nil,
+ 'env' => 'github',
+ 'env-github' => '',
+ 'source-highlighter' => 'html-pipeline'
+ }
+ if filename
+ attributes['docname'] = File.basename(filename, (extname = File.extname(filename)))
+ attributes['docfilesuffix'] = attributes['outfilesuffix'] = extname
+ else
+ attributes['outfilesuffix'] = '.adoc'
+ end
+ Asciidoctor::Compliance.unique_id_start_index = 1
+ Asciidoctor.convert(content, :safe => :secure, :attributes => attributes)
end
-command("python2 -S #{Shellwords.escape(File.dirname(__FILE__))}/commands/rest2html", /re?st(\.txt)?/)
+GitHub::Markup.command(
+ ::GitHub::Markups::MARKUP_RST,
+ "python3 #{Shellwords.escape(File.dirname(__FILE__))}/commands/rest2html",
+ /re?st(\.txt)?/,
+ ["reStructuredText"],
+ "restructuredtext"
+)
-# pod2html is nice enough to generate a full-on HTML document for us,
-# so we return the favor by ripping out the good parts.
-#
-# Any block passed to `command` will be handed the command's STDOUT for
-# post processing.
-command('/usr/bin/env perl -MPod::Simple::HTML -e Pod::Simple::HTML::go', /pod/) do |rendered|
- if rendered =~ /\s*(.+)\s*/mi
- $1
- end
-end
+GitHub::Markup.command(::GitHub::Markups::MARKUP_POD6, :pod62html, /pod6/, ["Pod 6"], "pod6")
+GitHub::Markup.command(::GitHub::Markups::MARKUP_POD, :pod2html, /pod/, ["Pod"], "pod")
diff --git a/script/bootstrap b/script/bootstrap
index 8092d517..f89f3181 100755
--- a/script/bootstrap
+++ b/script/bootstrap
@@ -5,4 +5,4 @@ set -e
cd $(dirname "$0")/..
bundle install
-easy_install docutils
+pip3 install docutils
diff --git a/script/bootstrap.contrib b/script/bootstrap.contrib
new file mode 100755
index 00000000..834b6b61
--- /dev/null
+++ b/script/bootstrap.contrib
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+set -e
+
+cd $(dirname "$0")/..
+
+bundle install --path vendor/bundle
+virtualenv vendor/python && source vendor/python/bin/activate
+pip install docutils
+
+echo ""
+echo "*** DONE ***"
+echo ""
+echo "activate python environment with 'source vendor/python/bin/activate'"
+echo "run tests with 'bundle exec rake'"
diff --git a/test/coverage_test.rb b/test/coverage_test.rb
new file mode 100644
index 00000000..f5043b5d
--- /dev/null
+++ b/test/coverage_test.rb
@@ -0,0 +1,385 @@
+# encoding: utf-8
+
+# Exercises code paths the original markup_test.rb does not reach:
+# - The six fallback markdown gem procs (github/markdown, redcarpet,
+# rdiscount, maruku, kramdown, bluecloth) executed against stubbed constants
+# - The LoadError ("no suitable markdown gem found") raised when no gem is available
+# - try_require's rescue clause
+# - Implementation#render's NotImplementedError default
+# - Implementation#match? without Linguist (and the lazy regexp memoization)
+# - GitHub::Markup.markup_impl's duplicate-symbol guard
+# - GitHub::Markup.render falling through to the raw content
+# - GitHub::Markup.render_s with an unknown symbol and with nil content
+# - GitHub::Markup.preload!
+# - GitHub::Markup.language returning nil without Linguist
+# - CommandImplementation block-arity branches (arity 1 vs 2)
+
+$LOAD_PATH.unshift File.dirname(__FILE__) + "/../lib"
+
+require_relative 'test_helper'
+require 'github-markup'
+require 'github/markup'
+require 'minitest/autorun'
+
+class CoverageTest < Minitest::Test
+ def test_version_constant_is_defined
+ assert_kind_of String, GitHub::Markup::VERSION
+ assert_equal GitHub::Markup::VERSION, GitHub::Markup::Version
+ end
+
+ # --- markdown.rb fallback procs ----------------------------------------
+
+ def test_github_markdown_proc_uses_github_markdown_render
+ with_stub_const("GitHub::Markdown", fake_renderer_module(:render)) do
+ result = GitHub::Markup::Markdown::MARKDOWN_GEMS.fetch("github/markdown").call("hi")
+ assert_equal "github_markdown:hi", result
+ end
+ end
+
+ def test_redcarpet_proc_renders_via_redcarpet
+ fake_html = Class.new
+ fake_md = Class.new do
+ def initialize(*); end
+ def render(content); "redcarpet:#{content}"; end
+ end
+ fake_module = Module.new
+ fake_module.const_set(:Render, Module.new.tap { |m| m.const_set(:HTML, fake_html) })
+ fake_module.const_set(:Markdown, fake_md)
+ with_stub_const("Redcarpet", fake_module) do
+ result = GitHub::Markup::Markdown::MARKDOWN_GEMS.fetch("redcarpet").call("hi")
+ assert_equal "redcarpet:hi", result
+ end
+ end
+
+ def test_rdiscount_proc_renders_via_rdiscount
+ with_stub_const("RDiscount", instance_renderer_class(:to_html, prefix: "rdiscount")) do
+ result = GitHub::Markup::Markdown::MARKDOWN_GEMS.fetch("rdiscount").call("hi")
+ assert_equal "rdiscount:hi", result
+ end
+ end
+
+ def test_maruku_proc_renders_via_maruku
+ with_stub_const("Maruku", instance_renderer_class(:to_html, prefix: "maruku")) do
+ result = GitHub::Markup::Markdown::MARKDOWN_GEMS.fetch("maruku").call("hi")
+ assert_equal "maruku:hi", result
+ end
+ end
+
+ def test_kramdown_proc_renders_via_kramdown_document
+ fake_doc = instance_renderer_class(:to_html, prefix: "kramdown")
+ fake_module = Module.new.tap { |m| m.const_set(:Document, fake_doc) }
+ with_stub_const("Kramdown", fake_module) do
+ result = GitHub::Markup::Markdown::MARKDOWN_GEMS.fetch("kramdown").call("hi")
+ assert_equal "kramdown:hi", result
+ end
+ end
+
+ def test_bluecloth_proc_renders_via_bluecloth
+ with_stub_const("BlueCloth", instance_renderer_class(:to_html, prefix: "bluecloth")) do
+ result = GitHub::Markup::Markdown::MARKDOWN_GEMS.fetch("bluecloth").call("hi")
+ assert_equal "bluecloth:hi", result
+ end
+ end
+
+ # --- markdown.rb load failure and try_require rescue --------------------
+
+ def test_markdown_load_raises_loaderror_when_no_gem_is_available
+ md = GitHub::Markup::Markdown.new
+ def md.try_require(_); false; end
+ assert_raises(LoadError) { md.load }
+ end
+
+ def test_try_require_returns_false_for_missing_gem
+ md = GitHub::Markup::Markdown.new
+ refute md.send(:try_require, "github_markup_definitely_not_a_real_gem_#{Time.now.to_i}")
+ end
+
+ # --- rdoc.rb legacy version branch --------------------------------------
+ # The `RDoc::VERSION < 4` branch in rdoc.rb is marked :nocov: in source
+ # because the modern RDoc::Markup::ToHtml constructor requires Options
+ # and the legacy zero-arg form has been broken since RDoc 4 (2013).
+
+ # --- implementation.rb default render and Linguist-less match? ---------
+
+ def test_base_implementation_render_raises_not_implemented_error
+ impl = GitHub::Markup::Implementation.new(/foo/, [])
+ assert_raises(NotImplementedError) { impl.render("README.foo", "anything") }
+ end
+
+ def test_match_uses_filename_extension_when_linguist_is_absent
+ impl_class = Class.new(GitHub::Markup::Implementation) do
+ def initialize; super(/md|markdown/, []); end
+ end
+ impl = impl_class.new
+ without_linguist do
+ assert impl.match?("README.md", nil)
+ # call again to cover the memoization branch in file_ext_regexp
+ assert impl.match?("README.markdown", nil)
+ refute impl.match?("README.txt", nil)
+ end
+ end
+
+ # --- markup.rb registration guard, render fallthroughs, preload! -------
+
+ def test_markup_impl_raises_when_symbol_already_registered
+ err = assert_raises(ArgumentError) do
+ GitHub::Markup.markup_impl(
+ ::GitHub::Markups::MARKUP_MARKDOWN,
+ GitHub::Markup::Markdown.new
+ )
+ end
+ assert_match(/already defined/, err.message)
+ end
+
+ def test_render_returns_content_when_no_implementation_matches
+ raw = "no extension match here"
+ assert_equal raw, GitHub::Markup.render("README.unknown_ext_xyz", raw)
+ end
+
+ def test_render_s_returns_content_when_symbol_is_unknown
+ raw = "passthrough body"
+ assert_equal raw, GitHub::Markup.render_s(:not_a_real_markup_symbol, raw)
+ end
+
+ def test_render_s_raises_on_nil_content
+ assert_raises(ArgumentError) do
+ GitHub::Markup.render_s(::GitHub::Markups::MARKUP_MARKDOWN, nil)
+ end
+ end
+
+ def test_preload_calls_load_on_every_implementation
+ GitHub::Markup.preload!
+ # If preload! succeeded, every markup_impl reports a non-nil renderer or completes its load step.
+ GitHub::Markup.markup_impls.each do |impl|
+ assert_respond_to impl, :load
+ end
+ end
+
+ def test_language_returns_nil_without_linguist
+ without_linguist do
+ assert_nil GitHub::Markup.language("README.md", "anything")
+ end
+ end
+
+ # --- implementation.rb: Linguist-absent constructor + invalid-language raise
+
+ def test_implementation_initializes_without_linguist
+ without_linguist do
+ # Forces the else branch of `if defined?(::Linguist)` in initialize
+ impl = GitHub::Markup::Implementation.new(/foo/, ["AnythingGoesWithoutLinguist"])
+ assert_nil impl.languages
+ refute impl.match?("README.bar", nil)
+ end
+ end
+
+ def test_implementation_raises_for_unknown_linguist_language
+ err = assert_raises(RuntimeError) do
+ GitHub::Markup::Implementation.new(/foo/, ["DefinitelyNotALinguistLanguage"])
+ end
+ assert_match(/no match for language/, err.message)
+ end
+
+ # --- markups.rb wikicloth idempotent ESCAPED_TAGS<<'tt' both branches ---
+
+ def test_mediawiki_render_is_idempotent_for_escaped_tags
+ body = "==Hello==\nworld"
+ # First render adds 'tt'; second render hits the `else` branch of `unless include?('tt')`.
+ GitHub::Markup.render("README.mediawiki", body)
+ count_after_first = WikiCloth::WikiBuffer::HTMLElement::ESCAPED_TAGS.count('tt')
+ GitHub::Markup.render("README.mediawiki", body)
+ count_after_second = WikiCloth::WikiBuffer::HTMLElement::ESCAPED_TAGS.count('tt')
+ assert_equal 1, count_after_first, "first render should leave exactly one 'tt' entry"
+ assert_equal 1, count_after_second, "second render must not append a duplicate 'tt'"
+ end
+
+ # --- command_implementation.rb block arity branches --------------------
+
+ def test_command_block_with_arity_two_receives_rendered_and_content
+ captured = nil
+ impl = GitHub::Markup::CommandImplementation.new(
+ /covarity2/, ['Text'], 'test/fixtures/cat.sh', 'covarity2'
+ ) do |rendered, content|
+ captured = [rendered, content]
+ "two:#{rendered.strip}:#{content}"
+ end
+ out = impl.render('README.covarity2', 'payload')
+ assert_equal ['payload', 'payload'], captured.map(&:strip)
+ assert_equal 'two:payload:payload', out
+ end
+
+ def test_command_block_with_arity_one_receives_only_rendered
+ captured = nil
+ impl = GitHub::Markup::CommandImplementation.new(
+ /covarity1/, ['Text'], 'test/fixtures/cat.sh', 'covarity1'
+ ) do |rendered|
+ captured = rendered
+ "one:#{rendered.strip}"
+ end
+ out = impl.render('README.covarity1', 'payload')
+ assert_equal 'payload', captured.strip
+ assert_equal 'one:payload', out
+ end
+
+ def test_command_with_no_block_returns_rendered_output
+ impl = GitHub::Markup::CommandImplementation.new(
+ /covnoblock/, ['Text'], 'test/fixtures/cat.sh', 'covnoblock'
+ )
+ assert_equal 'hello', impl.render('README.covnoblock', 'hello').strip
+ end
+
+ def test_command_render_falls_back_to_content_when_command_returns_empty
+ impl = GitHub::Markup::CommandImplementation.new(
+ /covempty/, ['Text'], 'test/fixtures/empty.sh', 'covempty'
+ )
+ assert_equal 'fallback-body', impl.render('README.covempty', 'fallback-body')
+ end
+
+ def test_command_raises_when_subprocess_exits_non_zero
+ impl = GitHub::Markup::CommandImplementation.new(
+ /covfail/, ['Text'], 'test/fixtures/fail.sh', 'covfail'
+ )
+ assert_raises(GitHub::Markup::CommandError) { impl.render('README.covfail', 'payload') }
+ end
+
+ # --- commonmarker proc legacy option/extension mapping -----------------
+
+ def test_commonmarker_default_option_is_a_noop
+ capture = capture_commonmarker_call(commonmarker_opts: [:DEFAULT])
+ refute capture[:render].key?(:sourcepos)
+ refute capture[:parse].key?(:smart)
+ refute capture[:extension].key?(:footnotes)
+ end
+
+ def test_commonmarker_sourcepos_option_sets_render_sourcepos
+ capture = capture_commonmarker_call(commonmarker_opts: [:SOURCEPOS])
+ assert_equal true, capture[:render][:sourcepos]
+ end
+
+ def test_commonmarker_hardbreaks_option_enables_render_hardbreaks
+ capture = capture_commonmarker_call(commonmarker_opts: [:HARDBREAKS])
+ assert_equal true, capture[:render][:hardbreaks]
+ end
+
+ def test_commonmarker_nobreaks_option_disables_render_hardbreaks
+ # Combine with :HARDBREAKS so the assertion observes a transition true -> false
+ capture = capture_commonmarker_call(commonmarker_opts: [:HARDBREAKS, :NOBREAKS])
+ assert_equal false, capture[:render][:hardbreaks]
+ end
+
+ def test_commonmarker_smart_option_sets_parse_smart
+ capture = capture_commonmarker_call(commonmarker_opts: [:SMART])
+ assert_equal true, capture[:parse][:smart]
+ end
+
+ def test_commonmarker_github_pre_lang_option_sets_render_github_pre_lang
+ capture = capture_commonmarker_call(commonmarker_opts: [:GITHUB_PRE_LANG])
+ assert_equal true, capture[:render][:github_pre_lang]
+ end
+
+ def test_commonmarker_unsafe_option_enables_render_unsafe
+ capture = capture_commonmarker_call(commonmarker_opts: [:UNSAFE])
+ assert_equal true, capture[:render][:unsafe]
+ end
+
+ def test_commonmarker_footnotes_option_enables_extension_footnotes
+ capture = capture_commonmarker_call(commonmarker_opts: [:FOOTNOTES])
+ assert_equal true, capture[:extension][:footnotes]
+ end
+
+ def test_commonmarker_full_info_string_option_sets_render_full_info_string
+ capture = capture_commonmarker_call(commonmarker_opts: [:FULL_INFO_STRING])
+ assert_equal true, capture[:render][:full_info_string]
+ end
+
+ def test_commonmarker_accepts_legacy_no_op_options_without_raising
+ [
+ :VALIDATE_UTF8,
+ :LIBERAL_HTML_TAG,
+ :TABLE_PREFER_STYLE_ATTRIBUTES,
+ :STRIKETHROUGH_DOUBLE_TILDE,
+ ].each do |opt|
+ capture = capture_commonmarker_call(commonmarker_opts: [opt])
+ refute capture[:render].key?(:sourcepos), "#{opt} should not alter render options"
+ refute capture[:parse].key?(:smart), "#{opt} should not alter parse options"
+ end
+ end
+
+ def test_commonmarker_unknown_option_raises_argument_error
+ err = assert_raises(ArgumentError) do
+ capture_commonmarker_call(commonmarker_opts: [:TOTALLY_FAKE_OPT])
+ end
+ assert_match(/unknown commonmarker option:.*TOTALLY_FAKE_OPT/, err.message)
+ end
+
+ def test_commonmarker_header_ids_extension_sets_empty_string_prefix
+ capture = capture_commonmarker_call(commonmarker_exts: [:header_ids])
+ assert_equal "", capture[:extension][:header_ids]
+ end
+
+ def test_commonmarker_unknown_extension_raises_argument_error
+ err = assert_raises(ArgumentError) do
+ capture_commonmarker_call(commonmarker_exts: [:not_a_real_ext])
+ end
+ assert_match(/unknown commonmarker extension:.*not_a_real_ext/, err.message)
+ end
+
+ private
+
+ # Invokes the commonmarker MARKDOWN_GEMS proc against a stubbed Commonmarker
+ # constant and returns the parse/render/extension options the proc passed to
+ # Commonmarker.to_html.
+ def capture_commonmarker_call(options)
+ captured = {}
+ fake = Module.new
+ fake.define_singleton_method(:to_html) do |content, **kwargs|
+ captured[:content] = content
+ opts = kwargs.fetch(:options, {})
+ captured[:parse] = opts[:parse] || {}
+ captured[:render] = opts[:render] || {}
+ captured[:extension] = opts[:extension] || {}
+ "stub-html"
+ end
+ with_stub_const("Commonmarker", fake) do
+ GitHub::Markup::Markdown::MARKDOWN_GEMS
+ .fetch("commonmarker")
+ .call("payload", options: options)
+ end
+ captured
+ end
+
+ def with_stub_const(path, value)
+ parts = path.split("::")
+ name = parts.pop
+ parent = parts.inject(Object) { |mod, part| mod.const_get(part) }
+ had_const = parent.const_defined?(name, false)
+ original = parent.const_get(name) if had_const
+ parent.send(:remove_const, name) if had_const
+ parent.const_set(name, value)
+ yield
+ ensure
+ parent.send(:remove_const, name) if parent.const_defined?(name, false)
+ parent.const_set(name, original) if had_const
+ end
+
+ def without_linguist
+ had_const = Object.const_defined?(:Linguist, false)
+ original = Object.const_get(:Linguist) if had_const
+ Object.send(:remove_const, :Linguist) if had_const
+ yield
+ ensure
+ Object.const_set(:Linguist, original) if had_const
+ end
+
+ def fake_renderer_module(method_name)
+ Module.new do
+ define_singleton_method(method_name) { |content| "github_markdown:#{content}" }
+ end
+ end
+
+ def instance_renderer_class(method_name, prefix:)
+ Class.new do
+ define_method(:initialize) { |content| @__coverage_content = "#{prefix}:#{content}" }
+ define_method(method_name) { @__coverage_content }
+ end
+ end
+end
diff --git a/test/fixtures/cat.sh b/test/fixtures/cat.sh
new file mode 100755
index 00000000..72d4cb29
--- /dev/null
+++ b/test/fixtures/cat.sh
@@ -0,0 +1,2 @@
+#!/usr/bin/env bash
+cat
diff --git a/test/fixtures/empty.sh b/test/fixtures/empty.sh
new file mode 100755
index 00000000..4251e745
--- /dev/null
+++ b/test/fixtures/empty.sh
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+# Always succeeds with empty stdout - used to exercise the
+# CommandImplementation fallback branch when a renderer returns no output.
diff --git a/test/markup_test.rb b/test/markup_test.rb
index 3d24b7a6..baa1dec2 100644
--- a/test/markup_test.rb
+++ b/test/markup_test.rb
@@ -1,19 +1,20 @@
-# encoding: UTF-8
+# encoding: utf-8
$LOAD_PATH.unshift File.dirname(__FILE__) + "/../lib"
+require_relative 'test_helper'
+require 'github-markup'
require 'github/markup'
require 'minitest/autorun'
-require 'html/pipeline'
+require 'html_pipeline'
require 'nokogiri'
require 'nokogiri/diff'
def normalize_html(text)
- text.strip!
- text.gsub!(/\s\s+/,' ')
- text.gsub!(/\p{Pi}|\p{Pf}|"/u,'"')
- text.gsub!("\u2026",'...')
- text
+ text.strip
+ .gsub(/\s\s+/,' ')
+ .gsub(/\p{Pi}|\p{Pf}|"/u,'"')
+ .gsub("\u2026",'...')
end
def assert_html_equal(expected, actual, msg = nil)
@@ -35,17 +36,17 @@ def assert_html_equal(expected, actual, msg = nil)
end
class MarkupTest < Minitest::Test
- class MarkupFilter < HTML::Pipeline::Filter
- def call
- filename = context[:filename]
+ class MarkupFilter < HTMLPipeline::ConvertFilter
+ def call(_text, context: {})
+ filename = context[:filename] || @context[:filename]
GitHub::Markup.render(filename, File.read(filename)).strip.force_encoding("utf-8")
end
end
- Pipeline = HTML::Pipeline.new [
- MarkupFilter,
- HTML::Pipeline::SanitizationFilter
- ]
+ Pipeline = HTMLPipeline.new(
+ convert_filter: MarkupFilter.new,
+ sanitization_config: HTMLPipeline::SanitizationFilter::DEFAULT_CONFIG
+ )
Dir['test/markups/README.*'].each do |readme|
next if readme =~ /html$/
@@ -53,11 +54,10 @@ def call
define_method "test_#{markup}" do
skip "Skipping MediaWiki test because wikicloth is currently not compatible with JRuby." if markup == "mediawiki" && RUBY_PLATFORM == "java"
-
source = File.read(readme)
expected_file = "#{readme}.html"
expected = File.read(expected_file).rstrip
- actual = Pipeline.to_html(nil, :filename => readme)
+ actual = Pipeline.call("", context: { filename: readme })[:output].to_s
if source != expected
assert(source != actual, "#{markup} did not render anything")
@@ -69,6 +69,10 @@ def call
f.read
end
+ if ENV['UPDATE']
+ File.open(expected_file, 'w') { |f| f.write actual }
+ end
+
assert_html_equal expected, actual, <Title')
+ assert_equal true, GitHub::Markup.can_render?('README.markdown', '=== Title')
+ assert_equal false, GitHub::Markup.can_render?('README.cmd', 'echo 1')
+ assert_equal true, GitHub::Markup.can_render?('README.litcoffee', 'Title')
+ end
+
+ def test_each_render_has_a_name
+ assert_equal "markdown", GitHub::Markup.renderer('README.md', '=== Title').name
+ assert_equal "redcloth", GitHub::Markup.renderer('README.textile', '* One').name
+ assert_equal "rdoc", GitHub::Markup.renderer('README.rdoc', '* One').name
+ assert_equal "org-ruby", GitHub::Markup.renderer('README.org', '* Title').name
+ assert_equal "creole", GitHub::Markup.renderer('README.creole', '= Title =').name
+ assert_equal "wikicloth", GitHub::Markup.renderer('README.wiki', 'Title
').name
+ assert_equal "asciidoctor", GitHub::Markup.renderer('README.adoc', '== Title').name
+ assert_equal "restructuredtext", GitHub::Markup.renderer('README.rst', 'Title').name
+ assert_equal "pod", GitHub::Markup.renderer('README.pod', '=head1').name
+ assert_equal "pod6", GitHub::Markup.renderer('README.pod6', '=begin pod').name
+ end
+
+ def test_rendering_by_symbol
+ markup = '`test`'
+ result = /test<\/code><\/p>/
+ assert_match result, GitHub::Markup.render_s(GitHub::Markups::MARKUP_MARKDOWN, markup).strip
+ assert_match result, GitHub::Markup.render_s(GitHub::Markups::MARKUP_ASCIIDOC, markup).split.join
end
def test_raises_error_if_command_exits_non_zero
- GitHub::Markup.command('test/fixtures/fail.sh', /fail/)
- assert GitHub::Markup.can_render?('README.fail')
+ GitHub::Markup.command(:doesntmatter, 'test/fixtures/fail.sh', /fail/, ['Java'], 'fail')
+ assert GitHub::Markup.can_render?('README.java', 'stop swallowing errors')
begin
- GitHub::Markup.render('README.fail', "stop swallowing errors")
+ GitHub::Markup.render('README.java', "stop swallowing errors", symlink: false)
rescue GitHub::Markup::CommandError => e
- assert_equal "failure message", e.message
+ assert_equal "failure message", e.message.strip
else
fail "an exception was expected but was not raised"
end
@@ -101,4 +123,18 @@ def test_preserve_markup
content = "Noël"
assert_equal content.encoding.name, GitHub::Markup.render('Foo.rst', content).encoding.name
end
+
+ def test_commonmarker_options
+ assert_equal "hello world
\n", GitHub::Markup.render("test.md", "hello world")
+ assert_equal "hello world
\n", GitHub::Markup.render("test.md", "hello world", options: {commonmarker_opts: [:UNSAFE]})
+
+ assert_equal "hello world
\n", GitHub::Markup.render_s(GitHub::Markups::MARKUP_MARKDOWN, "hello world")
+ assert_equal "hello world
\n", GitHub::Markup.render_s(GitHub::Markups::MARKUP_MARKDOWN, "hello world", options: {commonmarker_opts: [:UNSAFE]})
+
+ assert_equal "<style>.red{color: red;}</style>\n", GitHub::Markup.render("test.md", "", options: {commonmarker_opts: [:UNSAFE]})
+ assert_equal "\n", GitHub::Markup.render("test.md", "", options: {commonmarker_opts: [:UNSAFE], commonmarker_exts: [:autolink, :table, :strikethrough]})
+
+ assert_equal "<style>.red{color: red;}</style>\n", GitHub::Markup.render_s(GitHub::Markups::MARKUP_MARKDOWN, "", options: {commonmarker_opts: [:UNSAFE]})
+ assert_equal "\n", GitHub::Markup.render_s(GitHub::Markups::MARKUP_MARKDOWN, "", options: {commonmarker_opts: [:UNSAFE], commonmarker_exts: [:autolink, :table, :strikethrough]})
+ end
end
diff --git a/test/markups/README.asciidoc b/test/markups/README.asciidoc
index 6a0462cb..5f65dd2b 100644
--- a/test/markups/README.asciidoc
+++ b/test/markups/README.asciidoc
@@ -1,14 +1,27 @@
= Document Title
+// sectanchors will be ignored
+:sectanchors:
== First Section
* One
* Two
-== Second Section
+Refer to <> or <>.
+
+Navigate from {docname}{outfilesuffix} to xref:another-document.asciidoc[another document].
+
+== Another Section
NOTE: Here is some source code.
```ruby
puts "Hello, World!"
```
+
+* [ ] todo
+* [x] done
+
+== Another Section
+
+content
diff --git a/test/markups/README.asciidoc.html b/test/markups/README.asciidoc.html
index 4f06d0f9..4e28204e 100644
--- a/test/markups/README.asciidoc.html
+++ b/test/markups/README.asciidoc.html
@@ -1,6 +1,6 @@
Document Title
-First Section
+First Section
@@ -12,10 +12,16 @@ First Section
+
+Refer to Another Section or Another Section.
+
+
+Navigate from README.asciidoc to another document.
+
-Second Section
+Another Section
@@ -34,5 +40,23 @@ Second Section
puts "Hello, World!"
+
+
+-
+
❏ todo
+
+-
+
✓ done
+
+
+
+
+
+
+Another Section
+
+
+content
+
\ No newline at end of file
diff --git a/test/markups/README.creole.html b/test/markups/README.creole.html
index ec67cd0f..cc16572f 100644
--- a/test/markups/README.creole.html
+++ b/test/markups/README.creole.html
@@ -1,8 +1,4 @@
-H1
H2
paragraph of text that will be turned into a paragraph element. It can go over several lines with line breaks, it will be turned into a contiguous paragraph element.
You can force a linebreak in your paragraph text
thusly.
-- a list element
- sub list element
-
-- 2nd list element
-
pre formatted text
+H1
H2
paragraph of text that will be turned into a paragraph element. It can go over several lines with line breaks, it will be turned into a contiguous paragraph element.
You can force a linebreak in your paragraph text
thusly.
- a list element
- sub list element
- 2nd list element
pre formatted text
$ ls -la
total 56
diff --git a/test/markups/README.directives.rst b/test/markups/README.directives.rst
new file mode 100644
index 00000000..f9016423
--- /dev/null
+++ b/test/markups/README.directives.rst
@@ -0,0 +1,103 @@
+==================================================
+restructuredText (rst) directives and comments
+==================================================
+
+Introduction
+=================
+
+An rst directive starts with two periods, and has a keyword
+followed by two colons, like this::
+
+ .. MyDirective::
+
+The rst parser is quite flexible and configurable. Directives
+may be added for specialized operations. Sphinx is a system
+that was designed for writing documentation, and, for example,
+readthedocs.org uses sphinx.
+
+Display of rst files at github needs to cover two distinct
+use-cases:
+
+- The github display is the primary method for displaying
+ the file (e.g. for README.rst files)
+
+- The github display is incidental to the primary method
+ for displaying the file (e.g. for readthedocs documentation)
+
+Currently, github handles the first case fine, but could
+confuse viewers for the second case, because sometimes
+content is missing from the github display.
+
+It would seem that one possibility for distinguishing these
+two cases is to add a github directive to control the display.
+
+Unfortunately, this would place a burden on every other rst
+parser to ignore the github directive (some of them will error
+on unknown directives).
+
+Instead, we can assign semantic content to specific comments.
+
+This is a fairly ugly hack, but it has the benefit of not
+requiring any document changes that would create problems with
+any conformant parser.
+
+
+The proposed special comment is::
+
+ .. github display [on | off]
+
+
+If you pass this the "on" value, then all unknown directives
+will be displayed as literal code blocks. If you pass this
+the "off" value, then unknown directives will not be displayed.
+
+In addition to controlling the display of literal code blocks,
+this also allows you to show comments specifically for github.
+
+For example, somebody could place this at the top of their file::
+
+ .. github display
+
+ This file was designed to be viewed at readthedocs. Some
+ content will not display properly when viewing using the
+ github browser.
+
+Tests
+==========
+
+By default, unknown directives should be displayed.
+
+.. UnknownDirective:: This is an unknown directive
+
+ it has a lot of stuff underneath it
+
+But we can turn this off, and the next directive should
+not be displayed.
+
+.. github display off
+
+.. UnknownDirective:: This is an unknown directive
+
+ it has a lot of stuff underneath it
+
+Or we can turn it back on...
+
+.. github display on
+
+.. UnknownDirective:: This is an unknown directive (3)
+
+ it has a lot of stuff underneath it
+
+Here is a comment that should display at github
+
+.. github display
+
+ YOU SHOULD SEE THIS!
+
+And here is a comment that should not display at github
+
+.. foobar
+
+ YOU SHOULD NOT SEE THIS!
+
+This concludes the tests.
diff --git a/test/markups/README.directives.rst.html b/test/markups/README.directives.rst.html
new file mode 100644
index 00000000..1af38ee9
--- /dev/null
+++ b/test/markups/README.directives.rst.html
@@ -0,0 +1,74 @@
+restructuredText (rst) directives and comments
+
+Introduction
+An rst directive starts with two periods, and has a keyword
+followed by two colons, like this:
+
+.. MyDirective::
+
+The rst parser is quite flexible and configurable. Directives
+may be added for specialized operations. Sphinx is a system
+that was designed for writing documentation, and, for example,
+readthedocs.org uses sphinx.
+Display of rst files at github needs to cover two distinct
+use-cases:
+
+- The github display is the primary method for displaying
+the file (e.g. for README.rst files)
+- The github display is incidental to the primary method
+for displaying the file (e.g. for readthedocs documentation)
+
+Currently, github handles the first case fine, but could
+confuse viewers for the second case, because sometimes
+content is missing from the github display.
+It would seem that one possibility for distinguishing these
+two cases is to add a github directive to control the display.
+Unfortunately, this would place a burden on every other rst
+parser to ignore the github directive (some of them will error
+on unknown directives).
+Instead, we can assign semantic content to specific comments.
+This is a fairly ugly hack, but it has the benefit of not
+requiring any document changes that would create problems with
+any conformant parser.
+The proposed special comment is:
+
+.. github display [on | off]
+
+If you pass this the "on" value, then all unknown directives
+will be displayed as literal code blocks. If you pass this
+the "off" value, then unknown directives will not be displayed.
+In addition to controlling the display of literal code blocks,
+this also allows you to show comments specifically for github.
+For example, somebody could place this at the top of their file:
+
+.. github display
+
+ This file was designed to be viewed at readthedocs. Some
+ content will not display properly when viewing using the
+ github browser.
+
+
+Tests
+By default, unknown directives should be displayed.
+
+.. UnknownDirective:: This is an unknown directive
+
+ it has a lot of stuff underneath it
+
+
+But we can turn this off, and the next directive should
+not be displayed.
+Or we can turn it back on...
+
+.. UnknownDirective:: This is an unknown directive (3)
+
+ it has a lot of stuff underneath it
+
+
+Here is a comment that should display at github
+
+
+YOU SHOULD SEE THIS!
+
+And here is a comment that should not display at github
+This concludes the tests.
\ No newline at end of file
diff --git a/test/markups/README.hidetitle.asciidoc b/test/markups/README.hidetitle.asciidoc
new file mode 100644
index 00000000..38cf76b0
--- /dev/null
+++ b/test/markups/README.hidetitle.asciidoc
@@ -0,0 +1,4 @@
+= Not Shown
+:!showtitle:
+
+This test verifies the author can disable the document title by adding `:!showtitle:` to the document header.
diff --git a/test/markups/README.hidetitle.asciidoc.html b/test/markups/README.hidetitle.asciidoc.html
new file mode 100644
index 00000000..02a55fe7
--- /dev/null
+++ b/test/markups/README.hidetitle.asciidoc.html
@@ -0,0 +1,3 @@
+
+This test verifies the author can disable the document title by adding :!showtitle: to the document header.
+
\ No newline at end of file
diff --git a/test/markups/README.litcoffee.html b/test/markups/README.litcoffee.html
index 5caa8780..4c777cda 100644
--- a/test/markups/README.litcoffee.html
+++ b/test/markups/README.litcoffee.html
@@ -1,44 +1,29 @@
Literate CoffeeScript Test
-
-Taken from https://github.com/jashkenas/coffee-script/blob/master/test/literate.litcoffee
+Taken from https://github.com/jashkenas/coffee-script/blob/master/test/literate.litcoffee
-
comment comment
-
-test "basic literate CoffeeScript parsing", ->
+test "basic literate CoffeeScript parsing", ->
ok yes
-
now with a...
-
-test "broken up indentation", ->
+test "broken up indentation", ->
-
... broken up ...
-
do ->
-
... nested block.
-
ok yes
-
Code must be separated from text by a blank line.
-
-test "code blocks must be preceded by a blank line", ->
+test "code blocks must be preceded by a blank line", ->
-
The next line is part of the text and will not be executed.
- fail()
-
+fail()
ok yes
-
Code in backticks is not parsed and...
-
-test "comments in indented blocks work", ->
+test "comments in indented blocks work", ->
do ->
do ->
# Regular comment.
@@ -49,18 +34,22 @@ Literate CoffeeScript Test
ok yes
-
Regular Markdown features,
like links and unordered lists, are fine:
-
-I
-Am
-A
-List
+-
+
I
+
+-
+
Am
+
+-
+
A
+
+-
+
List
+
-
Tabs work too:
-
-test "tabbed code", ->
- ok yes
\ No newline at end of file
+test "tabbed code", ->
+ok yes
\ No newline at end of file
diff --git a/test/markups/README.long.rst b/test/markups/README.long.rst
new file mode 100644
index 00000000..b1c007a6
--- /dev/null
+++ b/test/markups/README.long.rst
@@ -0,0 +1,1319 @@
+===================
+Robot Framework 7.0
+===================
+
+.. default-role:: code
+
+`Robot Framework`_ 7.0 is a new major release with highly enhanced listener interface
+(`#3296`_), native `VAR` syntax for creating variables (`#3761`_), support for
+mixing embedded and normal arguments with library keywords (`#4710`_), JSON
+result format (`#4847`_) and various other enhancements and bug fixes.
+
+Robot Framework 7.0 was released on Thursday January 11, 2024. Questions and comments
+related to the release can be sent to the `#devel` channel on `Robot Framework Slack`_
+and possible bugs submitted to the `issue tracker`_.
+
+.. _Robot Framework: http://robotframework.org
+.. _Robot Framework Foundation: http://robotframework.org/foundation
+
+
+.. _pip: http://pip-installer.org
+.. _PyPI: https://pypi.python.org/pypi/robotframework
+.. _issue tracker milestone: https://github.com/robotframework/robotframework/milestone/64
+.. _issue tracker: https://github.com/robotframework/robotframework/issues
+.. _Slack: http://slack.robotframework.org
+.. _Robot Framework Slack: Slack_
+.. _installation instructions: ../../INSTALL.rst
+
+.. contents::
+ :depth: 2
+ :local:
+
+Installation
+============
+
+If you have pip_ installed, just run
+
+::
+
+ pip install --upgrade robotframework
+
+to install the latest available stable release or use
+
+::
+
+ pip install robotframework==7.0
+
+to install exactly this version. Alternatively you can download the package
+from PyPI_ and install it manually. For more details and other installation
+approaches, see the `installation instructions`_.
+
+Most important enhancements
+===========================
+
+ If you are interested to learn more about the new features in Robot Framework 7.0,
+ join the `RoboCon conference`__ in February, 2024. `Pekka Klärck`_, Robot Framework
+ lead developer, will go through the key features briefly in the `onsite conference`__
+ in Helsinki and more thoroughly in the `online edition`__.
+
+ The conference has also dozens of other great talks, workshops and a lot of
+ possibilities to meet other community members as well as developers of various
+ tools and libraries in the ecosystem. All profits from the conference will be
+ used for future Robot Framework development.
+
+.. _Pekka Klärck: https://github.com/pekkaklarck
+__ https://robocon.io
+__ https://robocon.io/#live-opening-the-conference
+__ https://robocon.io/#online-opening-the-conference-live
+
+Listener enhancements
+---------------------
+
+Robot Framework's `listener interface`__ is a very powerful mechanism to get
+notifications about various events during execution and it also allows modifying
+data and results on the fly. It is not typically directly used by normal Robot
+Framework users, but they are likely to use tools that use it internally.
+The listener API has been significantly enhanced making it possible
+to create even more powerful and interesting tools in the future.
+
+__ http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#listener-interface
+
+Support keywords and control structures with listener version 3
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The major limitation with the listener API has been that the listener
+API version 2 only supports getting notifications, not making modifications,
+and that the more powerful listener API version 3 has only supported suites
+and tests/tasks.
+
+The biggest enhancement in the whole Robot Framework 7.0 is that the listener
+version 3 has been extended to support also keywords and control structures (`#3296`_).
+For example, a listener having the following methods prints information
+about started keywords and ended WHILE loops:
+
+.. code:: python
+
+ from robot import result, running
+
+
+ def start_keyword(data: running.Keyword, result: result.Keyword):
+ print(f"Keyword '{result.full_name}' used on line {data.lineno} started.")
+
+
+ def end_while(data: running.While, result: result.While):
+ print(f"WHILE loop on line {data.lineno} ended with status {result.status} "
+ f"after {len(result.body)} iterations.")
+
+With keyword calls it is possible to also get more information about the actually
+executed keyword. For example, the following listener prints some information
+about the executed keyword and the library it belongs to:
+
+.. code:: python
+
+ from robot.running import Keyword as KeywordData, LibraryKeyword
+ from robot.result import Keyword as KeywordResult
+
+
+ def start_library_keyword(data: KeywordData,
+ implementation: LibraryKeyword,
+ result: KeywordResult):
+ library = implementation.owner
+ print(f"Keyword '{implementation.name}' is implemented in library "
+ f"'{library.name}' at '{implementation.source}' on line "
+ f"{implementation.lineno}. The library has {library.scope.name} "
+ f"scope and the current instance is {library.instance}.")
+
+As the above example already illustrated, it is even possible to get an access to
+the actual library instance. This means that listeners can inspect the library
+state and also modify it. With user keywords it is even possible to modify
+the keyword itself or, via the `owner` resource file, any other keyword in
+the resource file.
+
+Listeners can also modify results if needed. Possible use cases include hiding
+sensitive information and adding more details to results based on external sources.
+
+Notice that although listener can change status of any executed keyword or control
+structure, that does not directly affect the status of the executed test. In general
+listeners cannot directly fail keywords so that execution would stop or handle
+failures so that execution would continue. This kind of functionality may be
+added in the future if there are needs.
+
+The new listener version 3 methods are so powerful and versatile that going them
+through thoroughly in these release notes is not possible. For more examples, you
+can see the `acceptance tests`__ using the methods in various interesting and even
+crazy ways.
+
+__ https://github.com/robotframework/robotframework/tree/master/atest/testdata/output/listener_interface/body_items_v3
+
+Listener version 3 is the default listener version
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Earlier listeners always needed to specify the API version they used with the
+`ROBOT_LISTENER_API_VERSION` attribute. Now that the listener version 3 got
+the new methods, it is considered so much more powerful than the version 2
+that it was made the default listener version (`#4910`_).
+
+The listener version 2 continues to work, but using it requires specifying
+the listener version as earlier. The are no plans to deprecate the listener
+version 2, but we nevertheless highly recommend using the version 3 whenever
+possible.
+
+Libraries can register themselves as listeners by using string `SELF`
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Listeners are typically enabled from the command line, but libraries
+can register listeners as well. Often libraries themselves want to act
+as listeners, and that has earlier required using `self.ROBOT_LIBRARY_LISTENER = self`
+in the `__init__` method. Robot Framework 7.0 makes it possible to use string
+`SELF` (case-insensitive) for this purpose as well (`#4910`_), which means
+that a listener can be specified as a class attribute and not only in `__init__`.
+This is especially convenient when using the `@library` decorator:
+
+.. code:: python
+
+ from robot.api.deco import keyword, library
+
+
+ @library(listener='SELF')
+ class Example:
+
+ def start_suite(self, data, result):
+ ...
+
+ @keyword
+ def example(self, arg):
+ ...
+
+Nicer API for modifying keyword arguments
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Modifying keyword call arguments programmatically has been made more convenient
+(`#5000`_). This enhancement eases modifying arguments using the new listener
+version 3 `start/end_keyword` methods.
+
+Paths are passed to version 3 listeners as `pathlib.Path` objects
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Listeners have methods like `output_file` and `log_file` that are called when
+result files are ready so that they get the file path as an argument. Earlier
+paths were strings, but nowadays listener version 3 methods get them as
+more convenient `pathlib.Path`__ objects.
+
+__ https://docs.python.org/3/library/pathlib.html
+
+Native `VAR` syntax
+-------------------
+
+The new `VAR` syntax (`#3761`_) makes it possible to create local variables
+as well as global, suite and test/task scoped variables dynamically during
+execution. The motivation is to have a more convenient syntax than using
+the `Set Variable` keyword for creating local variables and to unify
+the syntax for creating variables in different scopes. Except for the mandatory
+`VAR` marker, the syntax is also the same as when creating variables in the
+Variables section. The syntax is best explained with examples:
+
+.. code:: robotframework
+
+ *** Test Cases ***
+ Example
+ # Create a local variable `${local}` with a value `value`.
+ VAR ${local} value
+
+ # Create a variable that is available throughout the whole suite.
+ # Supported scopes are GLOBAL, SUITE, TEST, TASK and LOCAL (default).
+ VAR ${suite} value scope=SUITE
+
+ # Validate created variables.
+ Should Be Equal ${local} value
+ Should Be Equal ${suite} value
+
+ Example continued
+ # Suite level variables are seen also by subsequent tests.
+ Should Be Equal ${suite} value
+
+When creating `${scalar}` variables having long values, it is possible to split
+the value to multiple lines. Lines are joined together with a space by default,
+but that can be changed with the `separator` configuration option. Similarly as
+in the Variables section, it is possible to create also `@{list}` and `&{dict}`
+variables. Unlike in the Variables section, variables can be created conditionally
+using IF/ELSE structures:
+
+.. code:: robotframework
+
+ *** Test Cases ***
+ Long value
+ VAR ${long}
+ ... This value is rather long.
+ ... It has been split to multiple lines.
+ ... Parts will be joined together with a space.
+
+ Multiline
+ VAR ${multiline}
+ ... First line.
+ ... Second line.
+ ... Last line.
+ ... separator=\n
+
+ List
+ # Creates a list with three items.
+ VAR @{list} a b c
+
+ Dictionary
+ # Creates a dictionary with two items.
+ VAR &{dict} key=value second=item
+
+ Normal IF
+ IF 1 > 0
+ VAR ${x} true value
+ ELSE
+ VAR ${x} false value
+ END
+
+ Inline IF
+ IF 1 > 0 VAR ${x} true value ELSE VAR ${x} false value
+
+Mixed argument support with library keywords
+--------------------------------------------
+
+User keywords got support to use both embedded and normal arguments in Robot
+Framework 6.1 (`#4234`__) and now that support has been added also to library keywords
+(`#4710`_). The syntax works so, that if a function or method implementing a keyword
+accepts more arguments than there are embedded arguments, the remaining arguments
+can be passed in as normal arguments. This is illustrated by the following example
+keyword:
+
+.. code:: python
+
+ @keyword('Number of ${animals} should be')
+ def example(animals, count):
+ ...
+
+The above keyword could be used like this:
+
+.. code:: robotframework
+
+ *** Test Cases ***
+ Example
+ Number of horses should be 2
+ Number of horses should be count=2
+ Number of dogs should be 3
+
+__ https://github.com/robotframework/robotframework/issues/4234
+
+JSON result format
+------------------
+
+Robot Framework 6.1 added support to `convert test/task data to JSON and back`__
+and Robot Framework 7.0 extends the JSON serialization support to execution results
+(`#4847`_). One of the core use cases for data serialization was making it easy to
+transfer data between process and machines, and now it is also easy to pass results
+back.
+
+Also the built-in Rebot tool that is used for post-processing results supports
+JSON files both in output and in input. Creating JSON output files is done using
+the normal `--output` option so that the specified file has a `.json` extension::
+
+ rebot --output output.json output.xml
+
+When reading output files, JSON files are automatically recognized by
+the extension::
+
+ rebot output.json
+ rebot output1.json output2.json
+
+When combining or merging results, it is possible to mix JSON and XML files::
+
+ rebot output1.xml output2.json
+ rebot --merge original.xml rerun.json
+
+The JSON output file structure is documented in the `result.json` `schema file`__.
+
+The plan is to enhance the support for JSON output files in the future so that
+they could be created already during execution. For more details see issue `#3423`__.
+
+__ https://github.com/robotframework/robotframework/blob/master/doc/releasenotes/rf-6.1.rst#json-data-format
+__ https://github.com/robotframework/robotframework/tree/master/doc/schema#readme
+__ https://github.com/robotframework/robotframework/issues/3423
+
+Argument conversion enhancements
+--------------------------------
+
+Automatic argument conversion is a very powerful feature that library developers
+can use to avoid converting arguments manually and to get more useful Libdoc
+documentation. There are two important new enhancements to it.
+
+Support for `Literal`
+~~~~~~~~~~~~~~~~~~~~~
+
+In Python, the Literal__ type makes it possible to type arguments so that type
+checkers accept only certain values. For example, this function only accepts
+strings `x`, `y` and `z`:
+
+.. code:: python
+
+ def example(arg: Literal['x', 'y', 'z']):
+ ...
+
+Robot Framework has been enhanced so that it validates that an argument having
+a `Literal` type can only be used with the specified values (`#4633`_). For
+example, using a keyword with the above implementation with a value `xxx` would
+fail.
+
+In addition to validation, arguments are also converted. For example, if an
+argument accepts `Literal[-1, 0, 1]`, used arguments are converted to
+integers and then validated. In addition to that, string matching is case, space,
+underscore and hyphen insensitive. In all cases exact matches have a precedence
+and the argument that is passed to the keyword is guaranteed to be in the exact
+format used with `Literal`.
+
+`Literal` conversion is in many ways similar to Enum__ conversion that Robot
+Framework has supported for long time. `Enum` conversion has benefits like
+being able to use a custom documentation and it is typically better when using
+the same type multiple times. In simple cases being able to just use
+`arg: Literal[...]` without defining a new type is very convenient, though.
+
+__ https://docs.python.org/3/library/typing.html#typing.Literal
+__ https://docs.python.org/3/library/enum.html
+
+Support "stringified" types like `'list[int]'` and `'int | float'`
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Python's type hinting syntax has evolved so that generic types can be parameterized
+like `list[int]` (new in `Python 3.9`__) and unions written as `int | float`
+(new in `Python 3.10`__). Using these constructs with older Python versions causes
+errors, but Python type checkers support also "stringified" type hints like
+`'list[int]'` and `'int | float'` that work regardless the Python version.
+
+Support for stringified generics and unions has now been added also to
+Robot Framework's argument conversion (`#4711`_). For example,
+the following typing now also works with Python 3.8:
+
+.. code:: python
+
+ def example(a: 'list[int]', b: 'int | float'):
+ ...
+
+These stringified types are also compatible with the Remote library API and other
+scenarios where using actual types is not possible.
+
+__ https://peps.python.org/pep-0585
+__ https://peps.python.org/pep-0604
+
+Tags set globally can be removed using `-tag` syntax
+----------------------------------------------------
+
+Individual tests and keywords can nowadays remove tags that have been set in
+the Settings section with `Test Tags` or `Keyword Tags` settings by using
+the `-tag` syntax with their own `[Tags]` setting (`#4374`_). For example,
+tests `T1` and `T3` below get tags `all` and `most`, and test `T2` gets
+tags `all` and `one`:
+
+.. code:: robotframework
+
+ *** Settings ***
+ Test Tags all most
+
+ *** Test Cases ***
+ T1
+ No Operation
+ T2
+ [Tags] one -most
+ No Operation
+ T3
+ No Operation
+
+With tests it is possible to get the same effect by using the `Default Tags`
+setting and overriding it where needed. That syntax is, however, considered
+deprecated (`#4365`__) and using the new `-tag` syntax is recommended. With
+keywords there was no similar functionality earlier.
+
+__ https://github.com/robotframework/robotframework/issues/4365
+
+Dynamic and hybrid library APIs support asynchronous execution
+--------------------------------------------------------------
+
+Dynamic and hybrid libraries nowadays support asynchronous execution.
+In practice the special methods like `get_keyword_names` and `run_keyword`
+can be implemented as async methods (`#4803`_).
+
+Async support was added to the normal static library API in Robot Framework
+6.1 (`#4089`_). A bug related to handling asynchronous keywords if execution
+is stopped gracefully has also been fixed (`#4808`_).
+
+.. _#4089: https://github.com/robotframework/robotframework/issues/4089
+
+Timestamps in result model and output.xml use standard format
+-------------------------------------------------------------
+
+Timestamps used in the result model and stored to the output.xml file used custom
+format like `20231107 19:57:01.123` earlier. Non-standard formats are seldom
+a good idea, and in this case parsing the custom format turned out to be slow
+as well.
+
+Nowadays the result model stores timestamps as standard datetime_ objects and
+elapsed times as a timedelta_ (`#4258`_). This makes creating timestamps and
+operating with them more convenient and considerably faster. The new objects can
+be accessed via `start_time`, `end_time` and `elapsed_time` attributes that were
+added as forward compatibility already in Robot Framework 6.1 (`#4765`_).
+Old information is still available via the old `starttime`, `endtime` and
+`elapsedtime` attributes, so this change is fully backwards compatible.
+
+The timestamp format in output.xml has also been changed from the custom
+`YYYYMMDD HH:MM:SS.mmm` format to `ISO 8601`_ compatible
+`YYYY-MM-DDTHH:MM:SS.mmmmmm`. Using a standard format makes it
+easier to process output.xml files, but this change also has big positive
+performance effect. Now that the result model stores timestamps as datetime_
+objects, formatting and parsing them with the available `isoformat()`__ and
+`fromisoformat()`__ methods is very fast compared to custom formatting and parsing.
+
+A related change is that instead of storing start and end times of each executed
+item in output.xml, we nowadays store their start and elapsed times. Elapsed times
+are represented as floats denoting seconds. Having elapsed times directly available
+is a lot more convenient than calculating them based on start and end times.
+Storing start and elapsed times also takes less space than storing start and end times.
+
+As the result of these changes, times are available in the result model and in
+output.xml in higher precision than earlier. Earlier times were stored in millisecond
+granularity, but nowadays they use microseconds. Logs and reports still use milliseconds,
+but that can be changed in the future if there are needs.
+
+Changes to output.xml are backwards incompatible and affect all external tools
+that process timestamps. This is discussed more in `Changes to output.xml`_
+section below along with other output.xml changes.
+
+.. _datetime: https://docs.python.org/3/library/datetime.html#datetime-objects
+.. _timedelta: https://docs.python.org/3/library/datetime.html#timedelta-objects
+.. _#4765: https://github.com/robotframework/robotframework/issues/4765
+.. _ISO 8601: https://en.wikipedia.org/wiki/ISO_8601
+__ https://docs.python.org/3/library/datetime.html#datetime.datetime.isoformat
+__ https://docs.python.org/3/library/datetime.html#datetime.datetime.fromisoformat
+
+Dark mode support to report and log
+-----------------------------------
+
+Report and log got a new dark mode (`#3725`_). It is enabled automatically based
+on browser and operating system preferences, but there is also a toggle to
+switch between the modes.
+
+Backwards incompatible changes
+==============================
+
+Python 3.6 and 3.7 are no longer supported
+------------------------------------------
+
+Robot Framework 7.0 requires Python 3.8 or newer (`#4294`_). The last version
+that supports Python 3.6 and 3.7 is Robot Framework 6.1.1.
+
+Changes to output.xml
+---------------------
+
+The output.xml file has changed in different ways making Robot Framework 7.0
+incompatible with external tools processing output.xml files until these tools
+are updated. We try to avoid this kind of breaking changes, but in this case
+especially the changes to timestamps were considered so important that we
+eventually would have needed to do them anyway.
+
+Due to the changes being relatively big, it can take some time before external
+tools are updated. To allow users to take Robot Framework 7.0 into use also
+if they depend on an incompatible tool, it is possible to use the new
+`--legacy-output` option both as part of execution and with the Rebot tool
+to generate output.xml files that are compatible with older versions.
+
+Timestamp related changes
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The biggest changes in output.xml are related to timestamps (`#4258`_).
+With earlier versions start and end times of executed items, as well as timestamps
+of the logged messages, were stored using a custom `YYYYMMDD HH:MM:SS.mmm` format,
+but nowadays the format is `ISO 8601`_ compatible `YYYY-MM-DDTHH:MM:SS.mmmmmm`.
+In addition to that, instead of saving start and end times to `starttime` and
+`endtime` attributes and message times to `timestamp`, start and elapsed times
+are now stored to `start` and `elapsed` attributes and message times to `time`.
+
+Examples:
+
+.. code:: xml
+
+
+ Hello world!
+
+
+
+ Hello world!
+
+
+The new format is standard compliant, contains more detailed times, makes the elapsed
+time directly available and makes the `` elements over 10% shorter.
+These are all great benefits, but we are still sorry for all the extra work
+this causes for those developing tools that process output.xml files.
+
+Keyword name related changes
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+How keyword names are stored in output.xml has changed slightly (`#4884`_).
+With each executed keywords we store both the name of the keyword and the name
+of the library or resource file containing it. Earlier the latter was stored to
+attribute `library` also with resource files, but nowadays the attribute is generic
+`owner`. In addition to `owner` being a better name in general, it also
+matches the new `owner` attribute keywords in the result model have.
+
+Another change is that the original name stored with keywords using embedded
+arguments is nowadays in `source_name` attribute when it used to be in `sourcename`.
+This change was done to make the attribute consistent with the attribute in
+the result model.
+
+Examples:
+
+.. code:: xml
+
+
+ ...
+ ...
+
+
+ ...
+ ...
+
+Other changes
+~~~~~~~~~~~~~
+
+Nowadays keywords and control structures can have a message. Messages are represented
+as the text of the `` element, and they have been present already earlier with
+tests and suites. Related to this, control structured cannot anymore have ``.
+(`#4883`_)
+
+These changes should not cause problems for tools processing output.xml files,
+but storing messages with each failed keyword and control structure may
+increase the output.xml size.
+
+Schema updates
+~~~~~~~~~~~~~~
+
+The output.xml schema has been updated and can be found via
+https://github.com/robotframework/robotframework/tree/master/doc/schema/.
+
+Changes to result model
+-----------------------
+
+There have been some changes to the result model that unfortunately affect
+external tools using it. The main motivation for these changes has been
+cleaning up the model before creating a JSON representation for it (`#4847`_).
+
+Changes related to keyword names
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The biggest changes are related to keyword names (`#4884`_). Earlier `Keyword`
+objects had a `name` attribute that contained the full keyword name like
+`BuiltIn.Log`. The actual keyword name and the name of the library or resource
+file that the keyword belonged to were in `kwname` and `libname` attributes,
+respectively. In addition to these, keywords using embedded arguments also had
+a `sourcename` attribute containing the original keyword name.
+
+Due to reasons explained in `#4884`_, the following changes have been made
+in Robot Framework 7.0:
+
+- Old `kwname` is renamed to `name`. This is consistent with the execution side `Keyword`.
+- Old `libname` is renamed to generic `owner`.
+- New `full_name` is introduced to replace the old `name`.
+- `sourcename` is renamed to `source_name`.
+- `kwname`, `libname` and `sourcename` are preserved as properties. They are considered
+ deprecated, but accessing them does not cause a deprecation warning yet.
+
+The backwards incompatible part of this change is changing the meaning of the
+`name` attribute. It used to be a read-only property yielding the full name
+like `BuiltIn.Log`, but now it is a normal attribute that contains just the actual
+keyword name like `Log`. All other old attributes have been preserved as properties
+and code using them does not need to be updated immediately.
+
+Deprecated attributes have been removed
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The following attributes that were deprecated already in Robot Framework 4.0
+have been removed (`#4846`_):
+
+- `TestSuite.keywords`. Use `TestSuite.setup` and `TestSuite.teardown` instead.
+- `TestCase.keywords`. Use `TestCase.body`, `TestCase.setup` and `TestCase.teardown` instead.
+- `Keyword.keywords`. Use `Keyword.body` and `Keyword.teardown` instead.
+- `Keyword.children`. Use `Keyword.body` and `Keyword.teardown` instead.
+- `TestCase.critical`. The whole criticality concept has been removed.
+
+Additionally, `TestSuite.keywords` and `TestCase.keywords` have been removed
+from the execution model.
+
+Changes to parsing model
+------------------------
+
+There have been some changes also to the parsing model:
+
+- The node representing the deprecated `[Return]` setting has been renamed from
+ `Return` to `ReturnSetting`. At the same time, the node representing the
+ `RETURN` statement has been renamed from `ReturnStatement` to `Return` (`#4939`_).
+
+ To ease transition, `ReturnSetting` has existed as an alias for `Return` starting
+ from Robot Framework 6.1 (`#4656`_) and `ReturnStatement` is preserved as an alias
+ now. In addition to that, the `ModelVisitor` base class has special handling for
+ `visit_ReturnSetting` and `visit_ReturnStatement` visitor methods so that they work
+ correctly with `ReturnSetting` and `ReturnStatement` with Robot Framework 6.1 and
+ newer. Issue `#4939`_ explains this in more detail and has a concrete example
+ how to support also older Robot Framework versions.
+
+- The node representing the `Test Tags` setting as well as the deprecated
+ `Force Tags` setting has been renamed from `ForceTags` to `TestTags` (`#4385`_).
+ `ModelVisitor` has special handling for the `visit_ForceTags` method so
+ that it will continue to work also after the change.
+
+- The token type used with `AS` (or `WITH NAME`) in library imports has been changed
+ to `Token.AS` (`#4375`_). `Token.WITH_NAME` still exists as an alias for `Token.AS`.
+
+- Statement `type` and `tokens` have been moved from `_fields` to `_attributes` (`#4912`_).
+ This may affect debugging the model.
+
+.. _#4656: https://github.com/robotframework/robotframework/issues/4656
+
+Changes to Libdoc spec files
+----------------------------
+
+The following deprecated constructs have been removed from Libdoc spec files (`#4667`_):
+
+- `datatypes` have been removed from XML or JSON spec files. They were deprecated in
+ favor of `typedocs` already in Robot Framework 5.0 (`#4160`_).
+- Type names are not anymore written to XML specs as content of the `` elements.
+ The name is available as the `name` attribute of `` elements since
+ Robot Framework 6.1 (`#4538`_).
+- `types` and `typedocs` attributes have been removed from arguments in JSON specs.
+ The `type` attribute introduced in RF 6.1 (`#4538`_) needs to be used instead.
+
+Libdoc schema files have been updated and can be found via
+https://github.com/robotframework/robotframework/tree/master/doc/schema/.
+
+.. _#4160: https://github.com/robotframework/robotframework/issues/4160
+.. _#4538: https://github.com/robotframework/robotframework/issues/4538
+
+Changes to selecting tests with `--suite`, `--test` and `--include`
+-------------------------------------------------------------------
+
+There are two changes related to selecting tests:
+
+- When using `--test` and `--include` together, tests matching either of them
+ are selected (`#4721`_). Earlier tests need to match both options to be selected.
+
+- When selecting a suite using its parent suite as a prefix like `--suite parent.suite`,
+ the given name must match the full suite name (`#4720`_). Earlier it was enough if
+ the prefix matched the closest parent or parents.
+
+Other backwards incompatible changes
+------------------------------------
+
+- The default value of the `stdin` argument used with `Process` library keyword
+ has been changed from `subprocess.PIPE` to `None` (`#4103`_). This change ought
+ to avoid processes hanging in some cases. Those who depend on the old behavior
+ need to use `stdin=PIPE` explicitly to enable that.
+
+- When type hints are specified as strings, they must use format `type`, `type[param]`,
+ `type[p1, p2]` or `t1 | t2` (`#4711`_). Using other formats will cause errors taking
+ keywords into use. In practice problems occur if the special characters `[`, `]`, `,`
+ and `|` occur in unexpected places. For example, `arg: "Hello, world!"` will cause
+ an error due to the comma.
+
+- `datetime`, `date` and `timedelta` objects are sent over the Remote interface
+ differently than earlier (`#4784`_). They all used to be converted to strings, but
+ nowadays `datetime` is sent as-is, `date` is converted to `datetime` and sent like
+ that, and `timedelta` is converted to a `float` by using `timedelta.total_seconds()`.
+
+- Argument conversion support with `collections.abc.ByteString` has been removed (`#4983`_).
+ The reason is that `ByteString` is deprecated and will be removed in Python 3.14.
+ It has not been too often needed, but if you happen to use it, you can change
+ `arg: ByteString` to `arg: bytes | bytearray` and the functionality
+ stays exactly the same.
+
+- Paths passed to result file related listener version 3 methods like `output_file`
+ and `log_file` have been changed from strings to `pathlib.Path` objects (`#4988`_).
+ Most of the time both kinds of paths work interchangeably, so this change is unlikely
+ to cause issues. If you need to handle these paths as strings, they can be converted
+ by using `str(path)`.
+
+- `robot.utils.normalize` does not anymore support bytes (`#4936`_).
+
+- Deprecated `accept_plain_values` argument has been removed from the
+ `timestr_to_secs` utility function (`#4861`_).
+
+Deprecations
+============
+
+`[Return]` setting
+------------------
+
+The `[Return]` setting for specifying the return value from user keywords has
+been "loudly" deprecated (`#4876`_). It has been "silently" deprecated since
+Robot Framework 5.0 when the much more versatile `RETURN` setting was introduced
+(`#4078`_), but now using it will cause a deprecation warning. The plan is to
+preserve the `[Return]` setting at least until Robot Framework 8.0.
+
+If you have lot of data that uses `[Return]`, the easiest way to update it is
+using the Robotidy_ tool that can convert `[Return]` to `RETURN` automatically.
+If you have data that is executed also with Robot Framework versions that do
+not support `RETURN`, you can use the `Return From Keyword` keyword instead.
+That keyword will eventually be deprecated and removed as well, though.
+
+.. _#4078: https://github.com/robotframework/robotframework/issues/4078
+.. _Robotidy: https://robotidy.readthedocs.io
+
+Singular section headers
+------------------------
+
+Using singular section headers like `*** Test Case ***` or `*** Setting ***`
+nowadays causes a deprecation warning (`#4432`_). They were silently deprecated
+in Robot Framework 6.0 for reasons explained in issue `#4431`_.
+
+.. _#4431: https://github.com/robotframework/robotframework/issues/4431
+
+Deprecated attributes in parsing, running and result models
+-----------------------------------------------------------
+
+- In the parsing model, `For.variables`, `ForHeader.variables`, `Try.variable` and
+ `ExceptHeader.variable` attributes have been deprecated in favor of the new `assign`
+ attribute (`#4708`_).
+
+- In running and result models, `For.variables` and `TryBranch.variable` have been
+ deprecated in favor of the new `assign` attribute (`#4708`_).
+
+- In the result model, control structures like `FOR` were earlier modeled so that they
+ looked like keywords. Nowadays they are considered totally different objects and
+ their keyword specific attributes `name`, `kwnane`, `libname`, `doc`, `args`,
+ `assign`, `tags` and `timeout` have been deprecated (`#4846`_).
+
+- `starttime`, `endtime` and `elapsed` time attributes in the result model have been
+ silently deprecated (`#4258`_). Accessing them does not yet cause a deprecation
+ warning, but users are recommended to use `start_time`, `end_time` and
+ `elapsed_time` attributes that are available since Robot Framework 6.1.
+
+- `kwname`, `libname` and `sourcename` attributes used by the `Keyword` object
+ in the result model have been silently deprecated (`#4884`_). New code should use
+ `name`, `owner` and `source_name` instead.
+
+Other deprecated features
+-------------------------
+
+- Using embedded arguments with a variable that has a value not matching custom
+ embedded argument patterns nowadays causes a deprecation warning (`#4524`_).
+ Earlier variables used as embedded arguments were always accepted without
+ validating values.
+
+- Using `FOR IN ZIP` loops with lists having different lengths without explicitly
+ using `mode=SHORTEST` has been deprecated (`#4685`_). The strict mode where lengths
+ must match will be the default mode in the future.
+
+- Various utility functions in the `robot.utils` package that are no longer used
+ by Robot Framework itself, including the whole Python 2/3 compatibility layer,
+ have been deprecated (`#4501`_). If you need some of these utils, you can copy
+ their code to your own tool or library. This change may affect existing
+ libraries and tools in the ecosystem.
+
+- `case_insensitive` and `whitespace_insensitive` arguments used by some
+ Collections and String library keywords have been deprecated in favor of
+ `ignore_case` and `ignore_whitespace`. The new arguments were added for
+ consistency reasons (`#4954`_) and the old arguments will continue to work
+ for the time being.
+
+- Passing time as milliseconds to the `elapsed_time_to_string` utility function
+ has been deprecated (`#4862`_).
+
+Acknowledgements
+================
+
+Robot Framework development is sponsored by the `Robot Framework Foundation`_
+and its over 60 member organizations. If your organization is using Robot Framework
+and benefiting from it, consider joining the foundation to support its
+development as well.
+
+Robot Framework 7.0 team funded by the foundation consists of `Pekka Klärck`_ and
+`Janne Härkönen `_ (part time).
+In addition to work done by them, the community has provided some great contributions:
+
+- `Ygor Pontelo `__ added async support to the
+ dynamic and hybrid library APIs (`#4803`_) and fixed a bug with handling async
+ keywords when execution is stopped gracefully (`#4808`_).
+
+- `Topi 'top1' Tuulensuu `__ fixed a performance regression
+ when using `Run Keyword` so that the name of the executed keyword contains a variable
+ (`#4659`_).
+
+- `Pasi Saikkonen `__ added dark mode to reports
+ and logs (`#3725`_).
+
+- `René `__ added return type information to Libdoc's
+ HTML output (`#3017`_), fixed `DotDict` equality comparisons (`#4956`_) and
+ helped finalizing the dark mode support (`#3725`_).
+
+- `Robin `__ added type hints to modules that
+ did not yet have them under the public `robot.api` package (`#4841`_).
+
+- `Mark Moberts `__ added case-insensitive list and
+ dictionary comparison support to the Collections library (`#4343`_).
+
+- `Daniel Biehl `__ enhanced performance of traversing
+ the parsing model using `ModelVisitor` (`#4934`_).
+
+Big thanks to Robot Framework Foundation, to community members listed above, and to
+everyone else who has tested preview releases, submitted bug reports, proposed
+enhancements, debugged problems, or otherwise helped with Robot Framework 7.0
+development.
+
+See you at `RoboCon 2024 `__ either onsite or online!
+
+| `Pekka Klärck`_
+| Robot Framework lead developer
+
+Full list of fixes and enhancements
+===================================
+
+.. list-table::
+ :header-rows: 1
+
+ * - ID
+ - Type
+ - Priority
+ - Summary
+ * - `#3296`_
+ - enhancement
+ - critical
+ - Support keywords and control structures with listener version 3
+ * - `#3761`_
+ - enhancement
+ - critical
+ - Native `VAR` syntax to create variables inside tests and keywords
+ * - `#4294`_
+ - enhancement
+ - critical
+ - Drop Python 3.6 and 3.7 support
+ * - `#4710`_
+ - enhancement
+ - critical
+ - Support library keywords with both embedded and normal arguments
+ * - `#4847`_
+ - enhancement
+ - critical
+ - Support JSON serialization with result model
+ * - `#4659`_
+ - bug
+ - high
+ - Performance regression when using `Run Keyword` and keyword name contains a variable
+ * - `#4921`_
+ - bug
+ - high
+ - Log levels don't work correctly with `robot:flatten`
+ * - `#3725`_
+ - enhancement
+ - high
+ - Support dark theme with report and log
+ * - `#4258`_
+ - enhancement
+ - high
+ - Change timestamps from custom strings to `datetime` in result model and to ISO 8601 format in output.xml
+ * - `#4374`_
+ - enhancement
+ - high
+ - Support removing tags set globally by using `-tag` syntax with `[Tags]` setting
+ * - `#4633`_
+ - enhancement
+ - high
+ - Automatic argument conversion and validation for `Literal`
+ * - `#4711`_
+ - enhancement
+ - high
+ - Support type aliases in formats `'list[int]'` and `'int | float'` in argument conversion
+ * - `#4803`_
+ - enhancement
+ - high
+ - Async support to dynamic and hybrid library APIs
+ * - `#4808`_
+ - bug
+ - medium
+ - Async keywords are not stopped when execution is stopped gracefully
+ * - `#4859`_
+ - bug
+ - medium
+ - Parsing errors in reStructuredText files have no source
+ * - `#4880`_
+ - bug
+ - medium
+ - Initially empty test fails even if pre-run modifier adds content to it
+ * - `#4886`_
+ - bug
+ - medium
+ - `Set Variable If` is slow if it has several conditions
+ * - `#4898`_
+ - bug
+ - medium
+ - Resolving special variables can fail with confusing message
+ * - `#4915`_
+ - bug
+ - medium
+ - `cached_property` attributes are called when importing library
+ * - `#4924`_
+ - bug
+ - medium
+ - WHILE `on_limit` missing from listener v2 attributes
+ * - `#4926`_
+ - bug
+ - medium
+ - WHILE and TRY content are not removed with `--removekeywords all`
+ * - `#4945`_
+ - bug
+ - medium
+ - `TypedDict` with forward references do not work in argument conversion
+ * - `#4956`_
+ - bug
+ - medium
+ - DotDict behaves inconsistent on equality checks. `x == y` != `not x != y` and not `x != y` == `not x == y`
+ * - `#4964`_
+ - bug
+ - medium
+ - Variables set using `Set Suite Variable` with `children=True` cannot be properly overwritten
+ * - `#4980`_
+ - bug
+ - medium
+ - DateTime library uses deprecated `datetime.utcnow()`
+ * - `#4999`_
+ - bug
+ - medium
+ - XML Library: Double namespace during Element To String
+ * - `#5005`_
+ - bug
+ - medium
+ - `Log Variables` should not consume iterables
+ * - `#3017`_
+ - enhancement
+ - medium
+ - Add return type to Libdoc specs and HTML output
+ * - `#4103`_
+ - enhancement
+ - medium
+ - Process: Change the default `stdin` behavior from `subprocess.PIPE` to `None`
+ * - `#4302`_
+ - enhancement
+ - medium
+ - Remove `Reserved` library
+ * - `#4343`_
+ - enhancement
+ - medium
+ - Collections: Support case-insensitive list and dictionary comparisons
+ * - `#4375`_
+ - enhancement
+ - medium
+ - Change token type of `AS` (or `WITH NAME`) used with library imports to `Token.AS`
+ * - `#4385`_
+ - enhancement
+ - medium
+ - Change the parsing model object produced by `Test Tags` (and `Force Tags`) to `TestTags`
+ * - `#4432`_
+ - enhancement
+ - medium
+ - Loudly deprecate singular section headers
+ * - `#4501`_
+ - enhancement
+ - medium
+ - Loudly deprecate old Python 2/3 compatibility layer and other deprecated utils
+ * - `#4524`_
+ - enhancement
+ - medium
+ - Loudly deprecate variables used as embedded arguments not matching custom patterns
+ * - `#4545`_
+ - enhancement
+ - medium
+ - Support creating assigned variable name based on another variable like `${${var}} = Keyword`
+ * - `#4667`_
+ - enhancement
+ - medium
+ - Remove deprecated constructs from Libdoc spec files
+ * - `#4685`_
+ - enhancement
+ - medium
+ - Deprecate `SHORTEST` mode being default with `FOR IN ZIP` loops
+ * - `#4708`_
+ - enhancement
+ - medium
+ - Use `assing`, not `variable`, with FOR and TRY/EXCEPT model objects when referring to assigned variables
+ * - `#4720`_
+ - enhancement
+ - medium
+ - Require `--suite parent.suite` to match the full suite name
+ * - `#4721`_
+ - enhancement
+ - medium
+ - Change behavior of `--test` and `--include` so that they are cumulative
+ * - `#4747`_
+ - enhancement
+ - medium
+ - Support `[Setup]` with user keywords
+ * - `#4784`_
+ - enhancement
+ - medium
+ - Remote: Enhance `datetime`, `date` and `timedelta` conversion
+ * - `#4841`_
+ - enhancement
+ - medium
+ - Add typing to all modules under `robot.api`
+ * - `#4846`_
+ - enhancement
+ - medium
+ - Result model: Loudly deprecate not needed attributes and remove already deprecated ones
+ * - `#4872`_
+ - enhancement
+ - medium
+ - Control continue-on-failure mode by using recursive and non-recursive tags together
+ * - `#4876`_
+ - enhancement
+ - medium
+ - Loudly deprecate `[Return]` setting
+ * - `#4877`_
+ - enhancement
+ - medium
+ - XML: Support ignoring element order with `Elements Should Be Equal`
+ * - `#4883`_
+ - enhancement
+ - medium
+ - Result model: Add `message` to keywords and control structures and remove `doc` from controls
+ * - `#4884`_
+ - enhancement
+ - medium
+ - Result model: Enhance storing keyword name
+ * - `#4896`_
+ - enhancement
+ - medium
+ - Support `separator=` configuration option with scalar variables in Variables section
+ * - `#4903`_
+ - enhancement
+ - medium
+ - Support argument conversion and named arguments with dynamic variable files
+ * - `#4905`_
+ - enhancement
+ - medium
+ - Support creating variable name based on another variable like `${${VAR}}` in Variables section
+ * - `#4910`_
+ - enhancement
+ - medium
+ - Make listener v3 the default listener API
+ * - `#4912`_
+ - enhancement
+ - medium
+ - Parsing model: Move `type` and `tokens` from `_fields` to `_attributes`
+ * - `#4930`_
+ - enhancement
+ - medium
+ - BuiltIn: New `Reset Log Level` keyword for resetting the log level to the original value
+ * - `#4939`_
+ - enhancement
+ - medium
+ - Parsing model: Rename `Return` to `ReturnSetting` and `ReturnStatement` to `Return`
+ * - `#4942`_
+ - enhancement
+ - medium
+ - Add public argument conversion API for libraries and other tools
+ * - `#4952`_
+ - enhancement
+ - medium
+ - Collections: Make `ignore_order` and `ignore_keys` recursive
+ * - `#4960`_
+ - enhancement
+ - medium
+ - Support integer conversion with strings representing whole number floats like `'1.0'` and `'2e10'`
+ * - `#4976`_
+ - enhancement
+ - medium
+ - Support string `SELF` (case-insenstive) when library registers itself as listener
+ * - `#4979`_
+ - enhancement
+ - medium
+ - Add `robot.result.TestSuite.to/from_xml` methods
+ * - `#4982`_
+ - enhancement
+ - medium
+ - DateTime: Support `datetime.date` as an input format with date related keywords
+ * - `#4983`_
+ - enhancement
+ - medium
+ - Type conversion: Remove support for deprecated `ByteString`
+ * - `#5000`_
+ - enhancement
+ - medium
+ - Nicer API for setting keyword call arguments programmatically
+ * - `#4934`_
+ - ---
+ - medium
+ - Enhance performance of visiting parsing model
+ * - `#4621`_
+ - bug
+ - low
+ - OperatingSystem library docs have broken link / title
+ * - `#4798`_
+ - bug
+ - low
+ - `--removekeywords passed` doesn't remove test setup and teardown
+ * - `#4867`_
+ - bug
+ - low
+ - Original order of dictionaries is not preserved when they are pretty printed in log messages
+ * - `#4870`_
+ - bug
+ - low
+ - User keyword teardown missing from running model JSON schema
+ * - `#4904`_
+ - bug
+ - low
+ - Importing static variable file with arguments does not fail
+ * - `#4913`_
+ - bug
+ - low
+ - Trace log level logs arguments twice for embedded arguments
+ * - `#4927`_
+ - bug
+ - low
+ - WARN level missing from the log level selector in log.html
+ * - `#4967`_
+ - bug
+ - low
+ - Variables are not resolved in keyword name in WUKS error message
+ * - `#4861`_
+ - enhancement
+ - low
+ - Remove deprecated `accept_plain_values` from `timestr_to_secs` utility function
+ * - `#4862`_
+ - enhancement
+ - low
+ - Deprecate `elapsed_time_to_string` accepting time as milliseconds
+ * - `#4864`_
+ - enhancement
+ - low
+ - Process: Make warning about processes hanging if output buffers get full more visible
+ * - `#4885`_
+ - enhancement
+ - low
+ - Add `full_name` to replace `longname` to suite and test objects
+ * - `#4900`_
+ - enhancement
+ - low
+ - Make keywords and control structures in log look more like original data
+ * - `#4922`_
+ - enhancement
+ - low
+ - Change the log level of `Set Log Level` message from INFO to DEBUG
+ * - `#4933`_
+ - enhancement
+ - low
+ - Type conversion: Ignore hyphens when matching enum members
+ * - `#4935`_
+ - enhancement
+ - low
+ - Use `casefold`, not `lower`, when comparing strings case-insensitively
+ * - `#4936`_
+ - enhancement
+ - low
+ - Remove bytes support from `robot.utils.normalize` function
+ * - `#4954`_
+ - enhancement
+ - low
+ - Collections and String: Add `ignore_case` as alias for `case_insensitive`
+ * - `#4958`_
+ - enhancement
+ - low
+ - Document `robot_running` and `dry_run_active` properties of the BuiltIn library in the User Guide
+ * - `#4975`_
+ - enhancement
+ - low
+ - Support `times` and `x` suffixes with `WHILE` limit to make it more compatible with `Wait Until Keyword Succeeds`
+ * - `#4988`_
+ - enhancement
+ - low
+ - Change paths passed to listener v3 methods to `pathlib.Path` instances
+
+Altogether 88 issues. View on the `issue tracker `__.
+
+.. _#3296: https://github.com/robotframework/robotframework/issues/3296
+.. _#3761: https://github.com/robotframework/robotframework/issues/3761
+.. _#4294: https://github.com/robotframework/robotframework/issues/4294
+.. _#4710: https://github.com/robotframework/robotframework/issues/4710
+.. _#4847: https://github.com/robotframework/robotframework/issues/4847
+.. _#4659: https://github.com/robotframework/robotframework/issues/4659
+.. _#4921: https://github.com/robotframework/robotframework/issues/4921
+.. _#3725: https://github.com/robotframework/robotframework/issues/3725
+.. _#4258: https://github.com/robotframework/robotframework/issues/4258
+.. _#4374: https://github.com/robotframework/robotframework/issues/4374
+.. _#4633: https://github.com/robotframework/robotframework/issues/4633
+.. _#4711: https://github.com/robotframework/robotframework/issues/4711
+.. _#4803: https://github.com/robotframework/robotframework/issues/4803
+.. _#4808: https://github.com/robotframework/robotframework/issues/4808
+.. _#4859: https://github.com/robotframework/robotframework/issues/4859
+.. _#4880: https://github.com/robotframework/robotframework/issues/4880
+.. _#4886: https://github.com/robotframework/robotframework/issues/4886
+.. _#4898: https://github.com/robotframework/robotframework/issues/4898
+.. _#4915: https://github.com/robotframework/robotframework/issues/4915
+.. _#4924: https://github.com/robotframework/robotframework/issues/4924
+.. _#4926: https://github.com/robotframework/robotframework/issues/4926
+.. _#4945: https://github.com/robotframework/robotframework/issues/4945
+.. _#4956: https://github.com/robotframework/robotframework/issues/4956
+.. _#4964: https://github.com/robotframework/robotframework/issues/4964
+.. _#4980: https://github.com/robotframework/robotframework/issues/4980
+.. _#4999: https://github.com/robotframework/robotframework/issues/4999
+.. _#5005: https://github.com/robotframework/robotframework/issues/5005
+.. _#3017: https://github.com/robotframework/robotframework/issues/3017
+.. _#4103: https://github.com/robotframework/robotframework/issues/4103
+.. _#4302: https://github.com/robotframework/robotframework/issues/4302
+.. _#4343: https://github.com/robotframework/robotframework/issues/4343
+.. _#4375: https://github.com/robotframework/robotframework/issues/4375
+.. _#4385: https://github.com/robotframework/robotframework/issues/4385
+.. _#4432: https://github.com/robotframework/robotframework/issues/4432
+.. _#4501: https://github.com/robotframework/robotframework/issues/4501
+.. _#4524: https://github.com/robotframework/robotframework/issues/4524
+.. _#4545: https://github.com/robotframework/robotframework/issues/4545
+.. _#4667: https://github.com/robotframework/robotframework/issues/4667
+.. _#4685: https://github.com/robotframework/robotframework/issues/4685
+.. _#4708: https://github.com/robotframework/robotframework/issues/4708
+.. _#4720: https://github.com/robotframework/robotframework/issues/4720
+.. _#4721: https://github.com/robotframework/robotframework/issues/4721
+.. _#4747: https://github.com/robotframework/robotframework/issues/4747
+.. _#4784: https://github.com/robotframework/robotframework/issues/4784
+.. _#4841: https://github.com/robotframework/robotframework/issues/4841
+.. _#4846: https://github.com/robotframework/robotframework/issues/4846
+.. _#4872: https://github.com/robotframework/robotframework/issues/4872
+.. _#4876: https://github.com/robotframework/robotframework/issues/4876
+.. _#4877: https://github.com/robotframework/robotframework/issues/4877
+.. _#4883: https://github.com/robotframework/robotframework/issues/4883
+.. _#4884: https://github.com/robotframework/robotframework/issues/4884
+.. _#4896: https://github.com/robotframework/robotframework/issues/4896
+.. _#4903: https://github.com/robotframework/robotframework/issues/4903
+.. _#4905: https://github.com/robotframework/robotframework/issues/4905
+.. _#4910: https://github.com/robotframework/robotframework/issues/4910
+.. _#4912: https://github.com/robotframework/robotframework/issues/4912
+.. _#4930: https://github.com/robotframework/robotframework/issues/4930
+.. _#4939: https://github.com/robotframework/robotframework/issues/4939
+.. _#4942: https://github.com/robotframework/robotframework/issues/4942
+.. _#4952: https://github.com/robotframework/robotframework/issues/4952
+.. _#4960: https://github.com/robotframework/robotframework/issues/4960
+.. _#4976: https://github.com/robotframework/robotframework/issues/4976
+.. _#4979: https://github.com/robotframework/robotframework/issues/4979
+.. _#4982: https://github.com/robotframework/robotframework/issues/4982
+.. _#4983: https://github.com/robotframework/robotframework/issues/4983
+.. _#5000: https://github.com/robotframework/robotframework/issues/5000
+.. _#4934: https://github.com/robotframework/robotframework/issues/4934
+.. _#4621: https://github.com/robotframework/robotframework/issues/4621
+.. _#4798: https://github.com/robotframework/robotframework/issues/4798
+.. _#4867: https://github.com/robotframework/robotframework/issues/4867
+.. _#4870: https://github.com/robotframework/robotframework/issues/4870
+.. _#4904: https://github.com/robotframework/robotframework/issues/4904
+.. _#4913: https://github.com/robotframework/robotframework/issues/4913
+.. _#4927: https://github.com/robotframework/robotframework/issues/4927
+.. _#4967: https://github.com/robotframework/robotframework/issues/4967
+.. _#4861: https://github.com/robotframework/robotframework/issues/4861
+.. _#4862: https://github.com/robotframework/robotframework/issues/4862
+.. _#4864: https://github.com/robotframework/robotframework/issues/4864
+.. _#4885: https://github.com/robotframework/robotframework/issues/4885
+.. _#4900: https://github.com/robotframework/robotframework/issues/4900
+.. _#4922: https://github.com/robotframework/robotframework/issues/4922
+.. _#4933: https://github.com/robotframework/robotframework/issues/4933
+.. _#4935: https://github.com/robotframework/robotframework/issues/4935
+.. _#4936: https://github.com/robotframework/robotframework/issues/4936
+.. _#4954: https://github.com/robotframework/robotframework/issues/4954
+.. _#4958: https://github.com/robotframework/robotframework/issues/4958
+.. _#4975: https://github.com/robotframework/robotframework/issues/4975
+.. _#4988: https://github.com/robotframework/robotframework/issues/4988
diff --git a/test/markups/README.long.rst.html b/test/markups/README.long.rst.html
new file mode 100644
index 00000000..6ae0e2b4
--- /dev/null
+++ b/test/markups/README.long.rst.html
@@ -0,0 +1,1161 @@
+Robot Framework 7.0
+Robot Framework 7.0 is a new major release with highly enhanced listener interface
+(#3296), native VAR syntax for creating variables (#3761), support for
+mixing embedded and normal arguments with library keywords (#4710), JSON
+result format (#4847) and various other enhancements and bug fixes.
+Robot Framework 7.0 was released on Thursday January 11, 2024. Questions and comments
+related to the release can be sent to the #devel channel on Robot Framework Slack
+and possible bugs submitted to the issue tracker.
+
+
+- Installation
+- Most important enhancements
+- Listener enhancements
+- Native
VAR syntax
+- Mixed argument support with library keywords
+- JSON result format
+- Argument conversion enhancements
+- Tags set globally can be removed using
-tag syntax
+- Dynamic and hybrid library APIs support asynchronous execution
+- Timestamps in result model and output.xml use standard format
+- Dark mode support to report and log
+
+
+- Backwards incompatible changes
+
+- Deprecations
+
+- Acknowledgements
+- Full list of fixes and enhancements
+
+
+
+Installation
+If you have pip installed, just run
+
+pip install --upgrade robotframework
+
+to install the latest available stable release or use
+
+pip install robotframework==7.0
+
+to install exactly this version. Alternatively you can download the package
+from PyPI and install it manually. For more details and other installation
+approaches, see the installation instructions.
+
+Most important enhancements
+
+If you are interested to learn more about the new features in Robot Framework 7.0,
+join the RoboCon conference in February, 2024. Pekka Klärck, Robot Framework
+lead developer, will go through the key features briefly in the onsite conference
+in Helsinki and more thoroughly in the online edition.
+The conference has also dozens of other great talks, workshops and a lot of
+possibilities to meet other community members as well as developers of various
+tools and libraries in the ecosystem. All profits from the conference will be
+used for future Robot Framework development.
+
+
+Listener enhancements
+Robot Framework's listener interface is a very powerful mechanism to get
+notifications about various events during execution and it also allows modifying
+data and results on the fly. It is not typically directly used by normal Robot
+Framework users, but they are likely to use tools that use it internally.
+The listener API has been significantly enhanced making it possible
+to create even more powerful and interesting tools in the future.
+
+Support keywords and control structures with listener version 3
+The major limitation with the listener API has been that the listener
+API version 2 only supports getting notifications, not making modifications,
+and that the more powerful listener API version 3 has only supported suites
+and tests/tasks.
+The biggest enhancement in the whole Robot Framework 7.0 is that the listener
+version 3 has been extended to support also keywords and control structures (#3296).
+For example, a listener having the following methods prints information
+about started keywords and ended WHILE loops:
+
+from robot import result, running
+
+
+def start_keyword(data: running.Keyword, result: result.Keyword):
+ print(f"Keyword '{result.full_name}' used on line {data.lineno} started.")
+
+
+def end_while(data: running.While, result: result.While):
+ print(f"WHILE loop on line {data.lineno} ended with status {result.status} "
+ f"after {len(result.body)} iterations.")
+
+With keyword calls it is possible to also get more information about the actually
+executed keyword. For example, the following listener prints some information
+about the executed keyword and the library it belongs to:
+
+from robot.running import Keyword as KeywordData, LibraryKeyword
+from robot.result import Keyword as KeywordResult
+
+
+def start_library_keyword(data: KeywordData,
+ implementation: LibraryKeyword,
+ result: KeywordResult):
+ library = implementation.owner
+ print(f"Keyword '{implementation.name}' is implemented in library "
+ f"'{library.name}' at '{implementation.source}' on line "
+ f"{implementation.lineno}. The library has {library.scope.name} "
+ f"scope and the current instance is {library.instance}.")
+
+As the above example already illustrated, it is even possible to get an access to
+the actual library instance. This means that listeners can inspect the library
+state and also modify it. With user keywords it is even possible to modify
+the keyword itself or, via the owner resource file, any other keyword in
+the resource file.
+Listeners can also modify results if needed. Possible use cases include hiding
+sensitive information and adding more details to results based on external sources.
+Notice that although listener can change status of any executed keyword or control
+structure, that does not directly affect the status of the executed test. In general
+listeners cannot directly fail keywords so that execution would stop or handle
+failures so that execution would continue. This kind of functionality may be
+added in the future if there are needs.
+The new listener version 3 methods are so powerful and versatile that going them
+through thoroughly in these release notes is not possible. For more examples, you
+can see the acceptance tests using the methods in various interesting and even
+crazy ways.
+
+Listener version 3 is the default listener version
+Earlier listeners always needed to specify the API version they used with the
+ROBOT_LISTENER_API_VERSION attribute. Now that the listener version 3 got
+the new methods, it is considered so much more powerful than the version 2
+that it was made the default listener version (#4910).
+The listener version 2 continues to work, but using it requires specifying
+the listener version as earlier. The are no plans to deprecate the listener
+version 2, but we nevertheless highly recommend using the version 3 whenever
+possible.
+
+Libraries can register themselves as listeners by using string SELF
+Listeners are typically enabled from the command line, but libraries
+can register listeners as well. Often libraries themselves want to act
+as listeners, and that has earlier required using self.ROBOT_LIBRARY_LISTENER = self
+in the __init__ method. Robot Framework 7.0 makes it possible to use string
+SELF (case-insensitive) for this purpose as well (#4910), which means
+that a listener can be specified as a class attribute and not only in __init__.
+This is especially convenient when using the @library decorator:
+
+from robot.api.deco import keyword, library
+
+
+@library(listener='SELF')
+class Example:
+
+ def start_suite(self, data, result):
+ ...
+
+ @keyword
+ def example(self, arg):
+ ...
+
+
+Nicer API for modifying keyword arguments
+Modifying keyword call arguments programmatically has been made more convenient
+(#5000). This enhancement eases modifying arguments using the new listener
+version 3 start/end_keyword methods.
+
+Paths are passed to version 3 listeners as pathlib.Path objects
+Listeners have methods like output_file and log_file that are called when
+result files are ready so that they get the file path as an argument. Earlier
+paths were strings, but nowadays listener version 3 methods get them as
+more convenient pathlib.Path objects.
+
+Native VAR syntax
+The new VAR syntax (#3761) makes it possible to create local variables
+as well as global, suite and test/task scoped variables dynamically during
+execution. The motivation is to have a more convenient syntax than using
+the Set Variable keyword for creating local variables and to unify
+the syntax for creating variables in different scopes. Except for the mandatory
+VAR marker, the syntax is also the same as when creating variables in the
+Variables section. The syntax is best explained with examples:
+
+*** Test Cases ***
+Example
+ # Create a local variable `${local}` with a value `value`.
+ VAR ${local} value
+
+ # Create a variable that is available throughout the whole suite.
+ # Supported scopes are GLOBAL, SUITE, TEST, TASK and LOCAL (default).
+ VAR ${suite} value scope=SUITE
+
+ # Validate created variables.
+ Should Be Equal ${local} value
+ Should Be Equal ${suite} value
+
+Example continued
+ # Suite level variables are seen also by subsequent tests.
+ Should Be Equal ${suite} value
+
+When creating ${scalar} variables having long values, it is possible to split
+the value to multiple lines. Lines are joined together with a space by default,
+but that can be changed with the separator configuration option. Similarly as
+in the Variables section, it is possible to create also @{list} and &{dict}
+variables. Unlike in the Variables section, variables can be created conditionally
+using IF/ELSE structures:
+
+*** Test Cases ***
+Long value
+ VAR ${long}
+ ... This value is rather long.
+ ... It has been split to multiple lines.
+ ... Parts will be joined together with a space.
+
+Multiline
+ VAR ${multiline}
+ ... First line.
+ ... Second line.
+ ... Last line.
+ ... separator=\n
+
+List
+ # Creates a list with three items.
+ VAR @{list} a b c
+
+Dictionary
+ # Creates a dictionary with two items.
+ VAR &{dict} key=value second=item
+
+Normal IF
+ IF 1 > 0
+ VAR ${x} true value
+ ELSE
+ VAR ${x} false value
+ END
+
+Inline IF
+ IF 1 > 0 VAR ${x} true value ELSE VAR ${x} false value
+
+
+Mixed argument support with library keywords
+User keywords got support to use both embedded and normal arguments in Robot
+Framework 6.1 (#4234) and now that support has been added also to library keywords
+(#4710). The syntax works so, that if a function or method implementing a keyword
+accepts more arguments than there are embedded arguments, the remaining arguments
+can be passed in as normal arguments. This is illustrated by the following example
+keyword:
+
+@keyword('Number of ${animals} should be')
+def example(animals, count):
+ ...
+
+The above keyword could be used like this:
+
+*** Test Cases ***
+Example
+ Number of horses should be 2
+ Number of horses should be count=2
+ Number of dogs should be 3
+
+
+JSON result format
+Robot Framework 6.1 added support to convert test/task data to JSON and back
+and Robot Framework 7.0 extends the JSON serialization support to execution results
+(#4847). One of the core use cases for data serialization was making it easy to
+transfer data between process and machines, and now it is also easy to pass results
+back.
+Also the built-in Rebot tool that is used for post-processing results supports
+JSON files both in output and in input. Creating JSON output files is done using
+the normal --output option so that the specified file has a .json extension:
+
+rebot --output output.json output.xml
+
+When reading output files, JSON files are automatically recognized by
+the extension:
+
+rebot output.json
+rebot output1.json output2.json
+
+When combining or merging results, it is possible to mix JSON and XML files:
+
+rebot output1.xml output2.json
+rebot --merge original.xml rerun.json
+
+The JSON output file structure is documented in the result.json schema file.
+The plan is to enhance the support for JSON output files in the future so that
+they could be created already during execution. For more details see issue #3423.
+
+Argument conversion enhancements
+Automatic argument conversion is a very powerful feature that library developers
+can use to avoid converting arguments manually and to get more useful Libdoc
+documentation. There are two important new enhancements to it.
+
+Support for Literal
+In Python, the Literal type makes it possible to type arguments so that type
+checkers accept only certain values. For example, this function only accepts
+strings x, y and z:
+
+def example(arg: Literal['x', 'y', 'z']):
+ ...
+
+Robot Framework has been enhanced so that it validates that an argument having
+a Literal type can only be used with the specified values (#4633). For
+example, using a keyword with the above implementation with a value xxx would
+fail.
+In addition to validation, arguments are also converted. For example, if an
+argument accepts Literal[-1, 0, 1], used arguments are converted to
+integers and then validated. In addition to that, string matching is case, space,
+underscore and hyphen insensitive. In all cases exact matches have a precedence
+and the argument that is passed to the keyword is guaranteed to be in the exact
+format used with Literal.
+Literal conversion is in many ways similar to Enum conversion that Robot
+Framework has supported for long time. Enum conversion has benefits like
+being able to use a custom documentation and it is typically better when using
+the same type multiple times. In simple cases being able to just use
+arg: Literal[...] without defining a new type is very convenient, though.
+
+Support "stringified" types like 'list[int]' and 'int | float'
+Python's type hinting syntax has evolved so that generic types can be parameterized
+like list[int] (new in Python 3.9) and unions written as int | float
+(new in Python 3.10). Using these constructs with older Python versions causes
+errors, but Python type checkers support also "stringified" type hints like
+'list[int]' and 'int | float' that work regardless the Python version.
+Support for stringified generics and unions has now been added also to
+Robot Framework's argument conversion (#4711). For example,
+the following typing now also works with Python 3.8:
+
+def example(a: 'list[int]', b: 'int | float'):
+ ...
+
+These stringified types are also compatible with the Remote library API and other
+scenarios where using actual types is not possible.
+
+Tags set globally can be removed using -tag syntax
+Individual tests and keywords can nowadays remove tags that have been set in
+the Settings section with Test Tags or Keyword Tags settings by using
+the -tag syntax with their own [Tags] setting (#4374). For example,
+tests T1 and T3 below get tags all and most, and test T2 gets
+tags all and one:
+
+*** Settings ***
+Test Tags all most
+
+*** Test Cases ***
+T1
+ No Operation
+T2
+ [Tags] one -most
+ No Operation
+T3
+ No Operation
+
+With tests it is possible to get the same effect by using the Default Tags
+setting and overriding it where needed. That syntax is, however, considered
+deprecated (#4365) and using the new -tag syntax is recommended. With
+keywords there was no similar functionality earlier.
+
+Dynamic and hybrid library APIs support asynchronous execution
+Dynamic and hybrid libraries nowadays support asynchronous execution.
+In practice the special methods like get_keyword_names and run_keyword
+can be implemented as async methods (#4803).
+Async support was added to the normal static library API in Robot Framework
+6.1 (#4089). A bug related to handling asynchronous keywords if execution
+is stopped gracefully has also been fixed (#4808).
+
+Timestamps in result model and output.xml use standard format
+Timestamps used in the result model and stored to the output.xml file used custom
+format like 20231107 19:57:01.123 earlier. Non-standard formats are seldom
+a good idea, and in this case parsing the custom format turned out to be slow
+as well.
+Nowadays the result model stores timestamps as standard datetime objects and
+elapsed times as a timedelta (#4258). This makes creating timestamps and
+operating with them more convenient and considerably faster. The new objects can
+be accessed via start_time, end_time and elapsed_time attributes that were
+added as forward compatibility already in Robot Framework 6.1 (#4765).
+Old information is still available via the old starttime, endtime and
+elapsedtime attributes, so this change is fully backwards compatible.
+The timestamp format in output.xml has also been changed from the custom
+YYYYMMDD HH:MM:SS.mmm format to ISO 8601 compatible
+YYYY-MM-DDTHH:MM:SS.mmmmmm. Using a standard format makes it
+easier to process output.xml files, but this change also has big positive
+performance effect. Now that the result model stores timestamps as datetime
+objects, formatting and parsing them with the available isoformat() and
+fromisoformat() methods is very fast compared to custom formatting and parsing.
+A related change is that instead of storing start and end times of each executed
+item in output.xml, we nowadays store their start and elapsed times. Elapsed times
+are represented as floats denoting seconds. Having elapsed times directly available
+is a lot more convenient than calculating them based on start and end times.
+Storing start and elapsed times also takes less space than storing start and end times.
+As the result of these changes, times are available in the result model and in
+output.xml in higher precision than earlier. Earlier times were stored in millisecond
+granularity, but nowadays they use microseconds. Logs and reports still use milliseconds,
+but that can be changed in the future if there are needs.
+Changes to output.xml are backwards incompatible and affect all external tools
+that process timestamps. This is discussed more in Changes to output.xml
+section below along with other output.xml changes.
+
+Dark mode support to report and log
+Report and log got a new dark mode (#3725). It is enabled automatically based
+on browser and operating system preferences, but there is also a toggle to
+switch between the modes.
+
+Backwards incompatible changes
+
+Python 3.6 and 3.7 are no longer supported
+Robot Framework 7.0 requires Python 3.8 or newer (#4294). The last version
+that supports Python 3.6 and 3.7 is Robot Framework 6.1.1.
+
+Changes to output.xml
+The output.xml file has changed in different ways making Robot Framework 7.0
+incompatible with external tools processing output.xml files until these tools
+are updated. We try to avoid this kind of breaking changes, but in this case
+especially the changes to timestamps were considered so important that we
+eventually would have needed to do them anyway.
+Due to the changes being relatively big, it can take some time before external
+tools are updated. To allow users to take Robot Framework 7.0 into use also
+if they depend on an incompatible tool, it is possible to use the new
+--legacy-output option both as part of execution and with the Rebot tool
+to generate output.xml files that are compatible with older versions.
+
+Timestamp related changes
+The biggest changes in output.xml are related to timestamps (#4258).
+With earlier versions start and end times of executed items, as well as timestamps
+of the logged messages, were stored using a custom YYYYMMDD HH:MM:SS.mmm format,
+but nowadays the format is ISO 8601 compatible YYYY-MM-DDTHH:MM:SS.mmmmmm.
+In addition to that, instead of saving start and end times to starttime and
+endtime attributes and message times to timestamp, start and elapsed times
+are now stored to start and elapsed attributes and message times to time.
+Examples:
+
+<!-- Old format -->
+<msg timestamp="20231108 15:36:34.278" level="INFO">Hello world!</msg>
+<status status="PASS" starttime="20231108 15:37:35.046" endtime="20231108 15:37:35.046"/>
+
+<!-- New format -->
+<msg time="2023-11-08T15:36:34.278343" level="INFO">Hello world!</msg>
+<status status="PASS" start="2023-11-08T15:37:35.046153" elapsed="0.000161"/>
+
+The new format is standard compliant, contains more detailed times, makes the elapsed
+time directly available and makes the <status> elements over 10% shorter.
+These are all great benefits, but we are still sorry for all the extra work
+this causes for those developing tools that process output.xml files.
+
+Keyword name related changes
+How keyword names are stored in output.xml has changed slightly (#4884).
+With each executed keywords we store both the name of the keyword and the name
+of the library or resource file containing it. Earlier the latter was stored to
+attribute library also with resource files, but nowadays the attribute is generic
+owner. In addition to owner being a better name in general, it also
+matches the new owner attribute keywords in the result model have.
+Another change is that the original name stored with keywords using embedded
+arguments is nowadays in source_name attribute when it used to be in sourcename.
+This change was done to make the attribute consistent with the attribute in
+the result model.
+Examples:
+
+<!-- Old format -->
+<kw name="Log" library="BuiltIn">...</kw>
+<kw name="Number of horses should be" sourcename="Number of ${animals} should be" library="my_resource">...</kw>
+
+<!-- New format -->
+<kw name="Log" owner="BuiltIn">...</kw>
+<kw name="Number of horses should be" source_name="Number of ${animals} should be" owner="my_resource">...</kw>
+
+
+Other changes
+Nowadays keywords and control structures can have a message. Messages are represented
+as the text of the <status> element, and they have been present already earlier with
+tests and suites. Related to this, control structured cannot anymore have <doc>.
+(#4883)
+These changes should not cause problems for tools processing output.xml files,
+but storing messages with each failed keyword and control structure may
+increase the output.xml size.
+
+Schema updates
+The output.xml schema has been updated and can be found via
+https://github.com/robotframework/robotframework/tree/master/doc/schema/.
+
+Changes to result model
+There have been some changes to the result model that unfortunately affect
+external tools using it. The main motivation for these changes has been
+cleaning up the model before creating a JSON representation for it (#4847).
+
+Changes related to keyword names
+The biggest changes are related to keyword names (#4884). Earlier Keyword
+objects had a name attribute that contained the full keyword name like
+BuiltIn.Log. The actual keyword name and the name of the library or resource
+file that the keyword belonged to were in kwname and libname attributes,
+respectively. In addition to these, keywords using embedded arguments also had
+a sourcename attribute containing the original keyword name.
+Due to reasons explained in #4884, the following changes have been made
+in Robot Framework 7.0:
+
+- Old
kwname is renamed to name. This is consistent with the execution side Keyword.
+- Old
libname is renamed to generic owner.
+- New
full_name is introduced to replace the old name.
+sourcename is renamed to source_name.
+kwname, libname and sourcename are preserved as properties. They are considered
+deprecated, but accessing them does not cause a deprecation warning yet.
+
+The backwards incompatible part of this change is changing the meaning of the
+name attribute. It used to be a read-only property yielding the full name
+like BuiltIn.Log, but now it is a normal attribute that contains just the actual
+keyword name like Log. All other old attributes have been preserved as properties
+and code using them does not need to be updated immediately.
+
+Deprecated attributes have been removed
+The following attributes that were deprecated already in Robot Framework 4.0
+have been removed (#4846):
+
+TestSuite.keywords. Use TestSuite.setup and TestSuite.teardown instead.
+TestCase.keywords. Use TestCase.body, TestCase.setup and TestCase.teardown instead.
+Keyword.keywords. Use Keyword.body and Keyword.teardown instead.
+Keyword.children. Use Keyword.body and Keyword.teardown instead.
+TestCase.critical. The whole criticality concept has been removed.
+
+Additionally, TestSuite.keywords and TestCase.keywords have been removed
+from the execution model.
+
+Changes to parsing model
+There have been some changes also to the parsing model:
+
+The node representing the deprecated [Return] setting has been renamed from
+Return to ReturnSetting. At the same time, the node representing the
+RETURN statement has been renamed from ReturnStatement to Return (#4939).
+To ease transition, ReturnSetting has existed as an alias for Return starting
+from Robot Framework 6.1 (#4656) and ReturnStatement is preserved as an alias
+now. In addition to that, the ModelVisitor base class has special handling for
+visit_ReturnSetting and visit_ReturnStatement visitor methods so that they work
+correctly with ReturnSetting and ReturnStatement with Robot Framework 6.1 and
+newer. Issue #4939 explains this in more detail and has a concrete example
+how to support also older Robot Framework versions.
+
+The node representing the Test Tags setting as well as the deprecated
+Force Tags setting has been renamed from ForceTags to TestTags (#4385).
+ModelVisitor has special handling for the visit_ForceTags method so
+that it will continue to work also after the change.
+
+The token type used with AS (or WITH NAME) in library imports has been changed
+to Token.AS (#4375). Token.WITH_NAME still exists as an alias for Token.AS.
+
+Statement type and tokens have been moved from _fields to _attributes (#4912).
+This may affect debugging the model.
+
+
+
+Changes to Libdoc spec files
+The following deprecated constructs have been removed from Libdoc spec files (#4667):
+
+datatypes have been removed from XML or JSON spec files. They were deprecated in
+favor of typedocs already in Robot Framework 5.0 (#4160).
+- Type names are not anymore written to XML specs as content of the
<type> elements.
+The name is available as the name attribute of <type> elements since
+Robot Framework 6.1 (#4538).
+types and typedocs attributes have been removed from arguments in JSON specs.
+The type attribute introduced in RF 6.1 (#4538) needs to be used instead.
+
+Libdoc schema files have been updated and can be found via
+https://github.com/robotframework/robotframework/tree/master/doc/schema/.
+
+Changes to selecting tests with --suite, --test and --include
+There are two changes related to selecting tests:
+
+- When using
--test and --include together, tests matching either of them
+are selected (#4721). Earlier tests need to match both options to be selected.
+- When selecting a suite using its parent suite as a prefix like
--suite parent.suite,
+the given name must match the full suite name (#4720). Earlier it was enough if
+the prefix matched the closest parent or parents.
+
+
+Other backwards incompatible changes
+
+- The default value of the
stdin argument used with Process library keyword
+has been changed from subprocess.PIPE to None (#4103). This change ought
+to avoid processes hanging in some cases. Those who depend on the old behavior
+need to use stdin=PIPE explicitly to enable that.
+- When type hints are specified as strings, they must use format
type, type[param],
+type[p1, p2] or t1 | t2 (#4711). Using other formats will cause errors taking
+keywords into use. In practice problems occur if the special characters [, ], ,
+and | occur in unexpected places. For example, arg: "Hello, world!" will cause
+an error due to the comma.
+datetime, date and timedelta objects are sent over the Remote interface
+differently than earlier (#4784). They all used to be converted to strings, but
+nowadays datetime is sent as-is, date is converted to datetime and sent like
+that, and timedelta is converted to a float by using timedelta.total_seconds().
+- Argument conversion support with
collections.abc.ByteString has been removed (#4983).
+The reason is that ByteString is deprecated and will be removed in Python 3.14.
+It has not been too often needed, but if you happen to use it, you can change
+arg: ByteString to arg: bytes | bytearray and the functionality
+stays exactly the same.
+- Paths passed to result file related listener version 3 methods like
output_file
+and log_file have been changed from strings to pathlib.Path objects (#4988).
+Most of the time both kinds of paths work interchangeably, so this change is unlikely
+to cause issues. If you need to handle these paths as strings, they can be converted
+by using str(path).
+robot.utils.normalize does not anymore support bytes (#4936).
+- Deprecated
accept_plain_values argument has been removed from the
+timestr_to_secs utility function (#4861).
+
+
+Deprecations
+
+[Return] setting
+The [Return] setting for specifying the return value from user keywords has
+been "loudly" deprecated (#4876). It has been "silently" deprecated since
+Robot Framework 5.0 when the much more versatile RETURN setting was introduced
+(#4078), but now using it will cause a deprecation warning. The plan is to
+preserve the [Return] setting at least until Robot Framework 8.0.
+If you have lot of data that uses [Return], the easiest way to update it is
+using the Robotidy tool that can convert [Return] to RETURN automatically.
+If you have data that is executed also with Robot Framework versions that do
+not support RETURN, you can use the Return From Keyword keyword instead.
+That keyword will eventually be deprecated and removed as well, though.
+
+Singular section headers
+Using singular section headers like *** Test Case *** or *** Setting ***
+nowadays causes a deprecation warning (#4432). They were silently deprecated
+in Robot Framework 6.0 for reasons explained in issue #4431.
+
+Deprecated attributes in parsing, running and result models
+
+- In the parsing model,
For.variables, ForHeader.variables, Try.variable and
+ExceptHeader.variable attributes have been deprecated in favor of the new assign
+attribute (#4708).
+- In running and result models,
For.variables and TryBranch.variable have been
+deprecated in favor of the new assign attribute (#4708).
+- In the result model, control structures like
FOR were earlier modeled so that they
+looked like keywords. Nowadays they are considered totally different objects and
+their keyword specific attributes name, kwnane, libname, doc, args,
+assign, tags and timeout have been deprecated (#4846).
+starttime, endtime and elapsed time attributes in the result model have been
+silently deprecated (#4258). Accessing them does not yet cause a deprecation
+warning, but users are recommended to use start_time, end_time and
+elapsed_time attributes that are available since Robot Framework 6.1.
+kwname, libname and sourcename attributes used by the Keyword object
+in the result model have been silently deprecated (#4884). New code should use
+name, owner and source_name instead.
+
+
+Other deprecated features
+
+- Using embedded arguments with a variable that has a value not matching custom
+embedded argument patterns nowadays causes a deprecation warning (#4524).
+Earlier variables used as embedded arguments were always accepted without
+validating values.
+- Using
FOR IN ZIP loops with lists having different lengths without explicitly
+using mode=SHORTEST has been deprecated (#4685). The strict mode where lengths
+must match will be the default mode in the future.
+- Various utility functions in the
robot.utils package that are no longer used
+by Robot Framework itself, including the whole Python 2/3 compatibility layer,
+have been deprecated (#4501). If you need some of these utils, you can copy
+their code to your own tool or library. This change may affect existing
+libraries and tools in the ecosystem.
+case_insensitive and whitespace_insensitive arguments used by some
+Collections and String library keywords have been deprecated in favor of
+ignore_case and ignore_whitespace. The new arguments were added for
+consistency reasons (#4954) and the old arguments will continue to work
+for the time being.
+- Passing time as milliseconds to the
elapsed_time_to_string utility function
+has been deprecated (#4862).
+
+
+Acknowledgements
+Robot Framework development is sponsored by the Robot Framework Foundation
+and its over 60 member organizations. If your organization is using Robot Framework
+and benefiting from it, consider joining the foundation to support its
+development as well.
+Robot Framework 7.0 team funded by the foundation consists of Pekka Klärck and
+Janne Härkönen (part time).
+In addition to work done by them, the community has provided some great contributions:
+
+- Ygor Pontelo added async support to the
+dynamic and hybrid library APIs (#4803) and fixed a bug with handling async
+keywords when execution is stopped gracefully (#4808).
+- Topi 'top1' Tuulensuu fixed a performance regression
+when using
Run Keyword so that the name of the executed keyword contains a variable
+(#4659).
+- Pasi Saikkonen added dark mode to reports
+and logs (#3725).
+- René added return type information to Libdoc's
+HTML output (#3017), fixed
DotDict equality comparisons (#4956) and
+helped finalizing the dark mode support (#3725).
+- Robin added type hints to modules that
+did not yet have them under the public
robot.api package (#4841).
+- Mark Moberts added case-insensitive list and
+dictionary comparison support to the Collections library (#4343).
+- Daniel Biehl enhanced performance of traversing
+the parsing model using
ModelVisitor (#4934).
+
+Big thanks to Robot Framework Foundation, to community members listed above, and to
+everyone else who has tested preview releases, submitted bug reports, proposed
+enhancements, debugged problems, or otherwise helped with Robot Framework 7.0
+development.
+See you at RoboCon 2024 either onsite or online!
+
+
+Robot Framework lead developer
+
+
+Full list of fixes and enhancements
+
+
+
+
+
+
+
+
+ID
+Type
+Priority
+Summary
+
+
+
+#3296
+enhancement
+critical
+Support keywords and control structures with listener version 3
+
+#3761
+enhancement
+critical
+Native VAR syntax to create variables inside tests and keywords
+
+#4294
+enhancement
+critical
+Drop Python 3.6 and 3.7 support
+
+#4710
+enhancement
+critical
+Support library keywords with both embedded and normal arguments
+
+#4847
+enhancement
+critical
+Support JSON serialization with result model
+
+#4659
+bug
+high
+Performance regression when using Run Keyword and keyword name contains a variable
+
+#4921
+bug
+high
+Log levels don't work correctly with robot:flatten
+
+#3725
+enhancement
+high
+Support dark theme with report and log
+
+#4258
+enhancement
+high
+Change timestamps from custom strings to datetime in result model and to ISO 8601 format in output.xml
+
+#4374
+enhancement
+high
+Support removing tags set globally by using -tag syntax with [Tags] setting
+
+#4633
+enhancement
+high
+Automatic argument conversion and validation for Literal
+
+#4711
+enhancement
+high
+Support type aliases in formats 'list[int]' and 'int | float' in argument conversion
+
+#4803
+enhancement
+high
+Async support to dynamic and hybrid library APIs
+
+#4808
+bug
+medium
+Async keywords are not stopped when execution is stopped gracefully
+
+#4859
+bug
+medium
+Parsing errors in reStructuredText files have no source
+
+#4880
+bug
+medium
+Initially empty test fails even if pre-run modifier adds content to it
+
+#4886
+bug
+medium
+Set Variable If is slow if it has several conditions
+
+#4898
+bug
+medium
+Resolving special variables can fail with confusing message
+
+#4915
+bug
+medium
+cached_property attributes are called when importing library
+
+#4924
+bug
+medium
+WHILE on_limit missing from listener v2 attributes
+
+#4926
+bug
+medium
+WHILE and TRY content are not removed with --removekeywords all
+
+#4945
+bug
+medium
+TypedDict with forward references do not work in argument conversion
+
+#4956
+bug
+medium
+DotDict behaves inconsistent on equality checks. x == y != not x != y and not x != y == not x == y
+
+#4964
+bug
+medium
+Variables set using Set Suite Variable with children=True cannot be properly overwritten
+
+#4980
+bug
+medium
+DateTime library uses deprecated datetime.utcnow()
+
+#4999
+bug
+medium
+XML Library: Double namespace during Element To String
+
+#5005
+bug
+medium
+Log Variables should not consume iterables
+
+#3017
+enhancement
+medium
+Add return type to Libdoc specs and HTML output
+
+#4103
+enhancement
+medium
+Process: Change the default stdin behavior from subprocess.PIPE to None
+
+#4302
+enhancement
+medium
+Remove Reserved library
+
+#4343
+enhancement
+medium
+Collections: Support case-insensitive list and dictionary comparisons
+
+#4375
+enhancement
+medium
+Change token type of AS (or WITH NAME) used with library imports to Token.AS
+
+#4385
+enhancement
+medium
+Change the parsing model object produced by Test Tags (and Force Tags) to TestTags
+
+#4432
+enhancement
+medium
+Loudly deprecate singular section headers
+
+#4501
+enhancement
+medium
+Loudly deprecate old Python 2/3 compatibility layer and other deprecated utils
+
+#4524
+enhancement
+medium
+Loudly deprecate variables used as embedded arguments not matching custom patterns
+
+#4545
+enhancement
+medium
+Support creating assigned variable name based on another variable like ${${var}} = Keyword
+
+#4667
+enhancement
+medium
+Remove deprecated constructs from Libdoc spec files
+
+#4685
+enhancement
+medium
+Deprecate SHORTEST mode being default with FOR IN ZIP loops
+
+#4708
+enhancement
+medium
+Use assing, not variable, with FOR and TRY/EXCEPT model objects when referring to assigned variables
+
+#4720
+enhancement
+medium
+Require --suite parent.suite to match the full suite name
+
+#4721
+enhancement
+medium
+Change behavior of --test and --include so that they are cumulative
+
+#4747
+enhancement
+medium
+Support [Setup] with user keywords
+
+#4784
+enhancement
+medium
+Remote: Enhance datetime, date and timedelta conversion
+
+#4841
+enhancement
+medium
+Add typing to all modules under robot.api
+
+#4846
+enhancement
+medium
+Result model: Loudly deprecate not needed attributes and remove already deprecated ones
+
+#4872
+enhancement
+medium
+Control continue-on-failure mode by using recursive and non-recursive tags together
+
+#4876
+enhancement
+medium
+Loudly deprecate [Return] setting
+
+#4877
+enhancement
+medium
+XML: Support ignoring element order with Elements Should Be Equal
+
+#4883
+enhancement
+medium
+Result model: Add message to keywords and control structures and remove doc from controls
+
+#4884
+enhancement
+medium
+Result model: Enhance storing keyword name
+
+#4896
+enhancement
+medium
+Support separator=<value> configuration option with scalar variables in Variables section
+
+#4903
+enhancement
+medium
+Support argument conversion and named arguments with dynamic variable files
+
+#4905
+enhancement
+medium
+Support creating variable name based on another variable like ${${VAR}} in Variables section
+
+#4910
+enhancement
+medium
+Make listener v3 the default listener API
+
+#4912
+enhancement
+medium
+Parsing model: Move type and tokens from _fields to _attributes
+
+#4930
+enhancement
+medium
+BuiltIn: New Reset Log Level keyword for resetting the log level to the original value
+
+#4939
+enhancement
+medium
+Parsing model: Rename Return to ReturnSetting and ReturnStatement to Return
+
+#4942
+enhancement
+medium
+Add public argument conversion API for libraries and other tools
+
+#4952
+enhancement
+medium
+Collections: Make ignore_order and ignore_keys recursive
+
+#4960
+enhancement
+medium
+Support integer conversion with strings representing whole number floats like '1.0' and '2e10'
+
+#4976
+enhancement
+medium
+Support string SELF (case-insenstive) when library registers itself as listener
+
+#4979
+enhancement
+medium
+Add robot.result.TestSuite.to/from_xml methods
+
+#4982
+enhancement
+medium
+DateTime: Support datetime.date as an input format with date related keywords
+
+#4983
+enhancement
+medium
+Type conversion: Remove support for deprecated ByteString
+
+#5000
+enhancement
+medium
+Nicer API for setting keyword call arguments programmatically
+
+#4934
+---
+medium
+Enhance performance of visiting parsing model
+
+#4621
+bug
+low
+OperatingSystem library docs have broken link / title
+
+#4798
+bug
+low
+--removekeywords passed doesn't remove test setup and teardown
+
+#4867
+bug
+low
+Original order of dictionaries is not preserved when they are pretty printed in log messages
+
+#4870
+bug
+low
+User keyword teardown missing from running model JSON schema
+
+#4904
+bug
+low
+Importing static variable file with arguments does not fail
+
+#4913
+bug
+low
+Trace log level logs arguments twice for embedded arguments
+
+#4927
+bug
+low
+WARN level missing from the log level selector in log.html
+
+#4967
+bug
+low
+Variables are not resolved in keyword name in WUKS error message
+
+#4861
+enhancement
+low
+Remove deprecated accept_plain_values from timestr_to_secs utility function
+
+#4862
+enhancement
+low
+Deprecate elapsed_time_to_string accepting time as milliseconds
+
+#4864
+enhancement
+low
+Process: Make warning about processes hanging if output buffers get full more visible
+
+#4885
+enhancement
+low
+Add full_name to replace longname to suite and test objects
+
+#4900
+enhancement
+low
+Make keywords and control structures in log look more like original data
+
+#4922
+enhancement
+low
+Change the log level of Set Log Level message from INFO to DEBUG
+
+#4933
+enhancement
+low
+Type conversion: Ignore hyphens when matching enum members
+
+#4935
+enhancement
+low
+Use casefold, not lower, when comparing strings case-insensitively
+
+#4936
+enhancement
+low
+Remove bytes support from robot.utils.normalize function
+
+#4954
+enhancement
+low
+Collections and String: Add ignore_case as alias for case_insensitive
+
+#4958
+enhancement
+low
+Document robot_running and dry_run_active properties of the BuiltIn library in the User Guide
+
+#4975
+enhancement
+low
+Support times and x suffixes with WHILE limit to make it more compatible with Wait Until Keyword Succeeds
+
+#4988
+enhancement
+low
+Change paths passed to listener v3 methods to pathlib.Path instances
+
+
+
+Altogether 88 issues. View on the issue tracker.
\ No newline at end of file
diff --git a/test/markups/README.mediawiki b/test/markups/README.mediawiki
index 36c3ef6b..c3cc004a 100644
--- a/test/markups/README.mediawiki
+++ b/test/markups/README.mediawiki
@@ -6,6 +6,9 @@ __TOC__
= Red Bridge (JRuby Embed) =
+one-
+a-b
+
JRuby has long had a private embedding API, which was closely tied to the runtime's internals and therefore changed frequently as JRuby evolved. Since version 1.4, however, we have also provided a more stable public API, known as Red Bridge or JRuby Embed. Existing Java programs written to the [[DirectJRubyEmbedding|legacy API]] should still work, but we strongly recommend Red Bridge for all new projects.
== Features of Red Bridge ==
diff --git a/test/markups/README.mediawiki.html b/test/markups/README.mediawiki.html
index 6f9ac5e3..12768dc6 100644
--- a/test/markups/README.mediawiki.html
+++ b/test/markups/README.mediawiki.html
@@ -1,9 +1,9 @@
-» JRuby Project Wiki Home Page
-
Embedding JRuby
+» JRuby Project Wiki Home Page
+
Embedding JRuby
Using Java from Ruby is JRuby's best-known feature---but you can also go in the other direction, and use Ruby from Java. There are several different ways to do this. You can execute entire Ruby scripts, call individual Ruby methods, or even implement a Java interface in Ruby (thus allowing you to treat Ruby objects like Java ones). We refer to all these techniques generically as "embedding." This section will explain how to embed JRuby in your Java project.
-
-
-Table of Contents
+
+
+Table of Contents
-
Red Bridge (JRuby Embed)
@@ -12,46 +12,53 @@
- References
-
+
-Red Bridge (JRuby Embed)
+Red Bridge (JRuby Embed)
+
-JRuby has long had a private embedding API, which was closely tied to the runtime's internals and therefore changed frequently as JRuby evolved. Since version 1.4, however, we have also provided a more stable public API, known as Red Bridge or JRuby Embed. Existing Java programs written to the legacy API should still work, but we strongly recommend Red Bridge for all new projects.
+
one-<two
+
a-b
+
+JRuby has long had a private embedding API, which was closely tied to the runtime's internals and therefore changed frequently as JRuby evolved. Since version 1.4, however, we have also provided a more stable public API, known as Red Bridge or JRuby Embed. Existing Java programs written to the legacy API should still work, but we strongly recommend Red Bridge for all new projects.
-Features of Red Bridge
+Features of Red Bridge
+
-Red Bridge consists of two layers: Embed Core on the bottom, and implementations of JSR223 and BSF on top. Embed Core is JRuby-specific, and can take advantage of much of JRuby's power. JSR223 and BSF are more general interfaces that provide a common ground across scripting languages.
+
Red Bridge consists of two layers: Embed Core on the bottom, and implementations of JSR223 and BSF on top. Embed Core is JRuby-specific, and can take advantage of much of JRuby's power. JSR223 and BSF are more general interfaces that provide a common ground across scripting languages.
Which API should you use? For projects where Ruby is the only scripting language involved, we recommend Embed Core for the following reasons:
-
+
- With Embed Core, you can create several Ruby environments in one JVM, and configure them individually (via
org.jruby.RubyInstanceConfig. With the other APIs, configuration options can only be set globally, via the System properties.
- Embed Core offers several shortcuts, such as loading scripts from a
java.io.InputStream, or returning Java-friendly objects from Ruby code. These allow you to skip a lot of boilerplate.
For projects requiring multiple scripting languages, JSR223 is a good fit. Though the API is language-independent, JRuby's implementation of it allows you to set some Ruby-specific options. In particular, you can control the threading model of the scripting engine, the lifetime of local variables, compilation mode, and how line numbers are displayed.
-
-The full API documentation has all the gory details. It's worth talking about a couple of the finer points here.
+
+The full API documentation has all the gory details. It's worth talking about a couple of the finer points here.
-Previous Embedding JRuby Page
+Previous Embedding JRuby Page
+
-We recommend using Embed Core; however, if you're maintaining code that uses the old API, you can find its documentation on the legacy embedding[1] page.
+
We recommend using Embed Core; however, if you're maintaining code that uses the old API, you can find its documentation on the legacy embedding[1] page.
-References
+References
+
--
-^ This link goes nowhere.
\ No newline at end of file
+-
+^ This link goes nowhere.
\ No newline at end of file
diff --git a/test/markups/README.org b/test/markups/README.org
index c479b047..0eca800a 100644
--- a/test/markups/README.org
+++ b/test/markups/README.org
@@ -58,7 +58,7 @@ end
- Skipping text before the first headline (option skip:t)
- Skipping tables (option |:nil)
- Custom todo keywords
- - EXPORT_SELECT_TAGS and EXPORT_EXLUDE_TAGS for controlling parts of
+ - EXPORT_SELECT_TAGS and EXPORT_EXCLUDE_TAGS for controlling parts of
the tree to export
- Rewrite "file:(blah).org" links to "http:(blah).html" links. This
makes the inter-links to other org-mode files work.
@@ -91,7 +91,7 @@ end
** 2009-12-27: Version 0.3
- - Uses rubypants to get better typography (smart quotes, elipses, etc...).
+ - Uses rubypants to get better typography (smart quotes, ellipses, etc...).
- Fixed bugs:
- Tables and lists did not get properly closed at the end of file
- You couldn't do inline formatting inside table cells
diff --git a/test/markups/README.org.html b/test/markups/README.org.html
index 73f9cf4b..c3909dfa 100644
--- a/test/markups/README.org.html
+++ b/test/markups/README.org.html
@@ -1,28 +1,19 @@
org-ruby
-
-Status:
-Under Development
-
-
-Location:
-http://github.com/wallyqs/org-ruby
-
-
-Version:
-0.9.0
-
+ Status: Under Development
+ Location: http://github.com/wallyqs/org-ruby
+ Version: 0.9.0
-1 Description
+1 Description
Helpful Ruby routines for parsing orgmode files. The most
significant thing this library does today is convert orgmode files
to textile. Currently, you cannot do much to customize the
conversion. The supplied textile conversion is optimized for
- extracting “content” from the orgfile as opposed to “metadata.”
-2 History
-2.1 2014-02-08: Version 0.9.0
+ extracting “content” from the orgfile as opposed to “metadata.”
+2 History
+2.1 2014-02-08: Version 0.9.0
- - Let’s make sure
#+INCLUDE: is not supported
+ - Let’s make sure
#+INCLUDE: is not supported
- And confirm that syntax highlight is supported
@@ -30,18 +21,18 @@ 2.1 2014-02-08: Version 0.9.0
module GitHub
module Markup
- VERSION = 'test'
+ VERSION = 'test'
Version = VERSION
end
end
-2.2 2009-12-30: Version 0.5.1
+2.2 2009-12-30: Version 0.5.1
- - Minor enhancement: Recognize lines starting with “:” as examples.
+ - Minor enhancement: Recognize lines starting with “:” as examples.
- Minor enhancement: Recognize #+BEGIN_SRC as source blocks
- - Minor enhancement: Add “src” and “example” classes to <pre> blocks.
+ - Minor enhancement: Add “src” and “example” classes to <pre> blocks.
-2.3 2009-12-30: Version 0.5.0
+2.3 2009-12-30: Version 0.5.0
- Parse (but not necessarily use) in-buffer settings. The following
in-buffer settings are used:
@@ -52,11 +43,11 @@
2.3 2009-12-30: Version 0.5.0
- Skipping text before the first headline (option skip:t)
- Skipping tables (option |:nil)
- Custom todo keywords
- - EXPORT_SELECT_TAGS and EXPORT_EXLUDE_TAGS for controlling parts of
+
- EXPORT_SELECT_TAGS and EXPORT_EXCLUDE_TAGS for controlling parts of
the tree to export
- - Rewrite “file:(blah).org” links to “http:(blah).html” links. This
+
- Rewrite “file:(blah).org” links to “http:(blah).html” links. This
makes the inter-links to other org-mode files work.
- Uses <th> tags inside table rows that precede table separators.
- Bugfixes:
@@ -65,7 +56,7 @@
2.3 2009-12-30: Version 0.5.0
-2.4 2009-12-29: Version 0.4.2
+2.4 2009-12-29: Version 0.4.2
- Got rid of the extraneous newline at the start of code blocks.
- Everything now shows up in code blocks, even org-mode metadata.
@@ -76,59 +67,59 @@ 2.4 2009-12-29: Version 0.4.2
-2.5 2009-12-29: Version 0.4.1
+2.5 2009-12-29: Version 0.4.1
- HTML is now escaped by default
- org-mode comments will show up in a code block.
-2.6 2009-12-29: Version 0.4
+2.6 2009-12-29: Version 0.4
- - The first thing output in HTML gets the class “title”
+ - The first thing output in HTML gets the class “title”
- HTML output is now indented
- Proper support for multi-paragraph list items.
See? This paragraph is part of the last bullet.
- Fixed bugs:
- - “rake spec” wouldn’t work on Linux. Needed “require ‘rubygems’”.
+ - “rake spec” wouldn’t work on Linux. Needed “require ‘rubygems’”.
-2.7 2009-12-27: Version 0.3
+2.7 2009-12-27: Version 0.3
- - Uses rubypants to get better typography (smart quotes, elipses, etc…).
+ - Uses rubypants to get better typography (smart quotes, ellipses, etc…).
- Fixed bugs:
- Tables and lists did not get properly closed at the end of file
- - You couldn’t do inline formatting inside table cells
+ - You couldn’t do inline formatting inside table cells
- Characters in PRE blocks were not HTML escaped.
-2.8 2009-12-26: Version 0.2
+2.8 2009-12-26: Version 0.2
- Added
to_html output on the parser.
- Added support for the full range of inline markup: bold,
- italic,
code, verbatim, underline, strikethrough.
+ italic, code, verbatim, underline, strikethrough.
- Lots of refactoring to make the code more maintainable.
-2.9 2009-12-23: Version 0.1
+2.9 2009-12-23: Version 0.1
- Added support for block code, like this:
def flush!
- @logger.debug "FLUSH ==========> #{@output_type}"
+ @logger.debug "FLUSH ==========> #{@output_type}"
if (@output_type == :blank) then
- @output << "\n"
+ @output << "\n"
elsif (@buffer.length > 0) then
if @cancel_modifier then
- @output << "p. " if @output_type == :paragraph
+ @output << "p. " if @output_type == :paragraph
@cancel_modifier = false
end
@output << @paragraph_modifier if (@paragraph_modifier and not sticky_modifier?)
- @output << @buffer.textile_substitution << "\n"
+ @output << @buffer.textile_substitution << "\n"
end
- @buffer = ""
+ @buffer = ""
end
diff --git a/test/markups/README.pod b/test/markups/README.pod
index f7b5aac5..8c92eb5b 100644
--- a/test/markups/README.pod
+++ b/test/markups/README.pod
@@ -12,7 +12,7 @@ Primary goals are:
=over 4
-=item* Create a working compiler that understands the majority of the
+=item * Create a working compiler that understands the majority of the
MATLAB/Octave programming language.
=back
@@ -23,14 +23,14 @@ This project is broken into three primary components:
=over 4
-=item* The first is the parser, located in the C directory. The
+=item * The first is the parser, located in the C directory. The
parser proper is composed of three source files, F which is a
Perl6Grammar file, and F which is the associated actions file
-written in NQP, and F which is the operator precidence parser.
+written in NQP, and F which is the operator precedence parser.
In addition, several helper functions used by the parser are located in
C.
-=item* The second component is the library of builtin functions in the
+=item * The second component is the library of builtin functions in the
C directory. These functions are, currently, written primarily in
PIR. Function names prefixed with an underscore are "private" functions for use
with the parser. Other functions should have names which are the same as names
@@ -38,7 +38,7 @@ for regular MATLAB or Octave functions, since they will be available to the
HLL. These are also separated into different namespaces depending on visibility
and utility.
-=item* A number of library functions are written in M, or mostly M with some
+=item * A number of library functions are written in M, or mostly M with some
inline PIR code in C .
=back
diff --git a/test/markups/README.pod.html b/test/markups/README.pod.html
index ea810bb6..96f2cf2b 100644
--- a/test/markups/README.pod.html
+++ b/test/markups/README.pod.html
@@ -1,84 +1,72 @@
-
+Matrixy
-Matrixy
+INTRODUCTION
-INTRODUCTION
+This is a port of the MATLAB/Octave programming language to Parrot. See the ROADMAP file for more information on the status of this project, and what else needs to be done.
-This is a port of the MATLAB/Octave programming language to Parrot.
-See the ROADMAP file for more information on the status of this project,
-and what else needs to be done.
-
-ABOUT
+ABOUT
Primary goals are:
-
-=item* Create a working compiler that understands the majority of the MATLAB/Octave programming language.
-
+
+
+Create a working compiler that understands the majority of the MATLAB/Octave programming language.
-IMPLEMENTATION
+
+
+
+IMPLEMENTATION
This project is broken into three primary components:
-
-=item* The first is the parser,
-located in the src/parser/ directory.
-The parser proper is composed of three source files,
-grammar.pg which is a Perl6Grammar file,
-and actions.pm which is the associated actions file written in NQP,
-and grammar-oper.pm which is the operator precidence parser.
-In addition,
-several helper functions used by the parser are located in src/internals.
-
-=item* The second component is the library of builtin functions in the src/builtins/ directory.
-These functions are,
-currently,
-written primarily in PIR.
-Function names prefixed with an underscore are "private" functions for use with the parser.
-Other functions should have names which are the same as names for regular MATLAB or Octave functions,
-since they will be available to the HLL.
-These are also separated into different namespaces depending on visibility and utility.
-
-=item* A number of library functions are written in M,
-or mostly M with some inline PIR code in toolbox/.
-
-
-DEPENDENCIES
+
+
+The first is the parser, located in the src/parser/ directory. The parser proper is composed of three source files, grammar.pg which is a Perl6Grammar file, and actions.pm which is the associated actions file written in NQP, and grammar-oper.pm which is the operator precedence parser. In addition, several helper functions used by the parser are located in src/internals.
+
+
+The second component is the library of builtin functions in the src/builtins/ directory. These functions are, currently, written primarily in PIR. Function names prefixed with an underscore are "private" functions for use with the parser. Other functions should have names which are the same as names for regular MATLAB or Octave functions, since they will be available to the HLL. These are also separated into different namespaces depending on visibility and utility.
+
+
+A number of library functions are written in M, or mostly M with some inline PIR code in toolbox/.
+
+
+
+
+DEPENDENCIES
Matrixy depends on these dependencies:
-Parrot
+Parrot
-To get a proper version of Parrot to build Matrixy,
-you will need to check out and build Parrot from source:
+To get a proper version of Parrot to build Matrixy, you will need to check out and build Parrot from source:
- svn co http://svn.parrot.org/parrot/trunk parrot
- cd parrot
- perl Configure.pl
- make && make test && make install-dev
+svn co http://svn.parrot.org/parrot/trunk parrot
+cd parrot
+perl Configure.pl
+make && make test && make install-dev
-Parrot-Linear-Algebra
+Parrot-Linear-Algebra
The linear algebra package for Parrot is available separately and provides functionality required by Matrixy. This includes matrix data types and matrix manipulation libraries
-BUILDING
+BUILDING
Once all dependencies are in place, you can build Matrixy using this sequence of commands:
- perl Configure.pl
- nmake test
+perl Configure.pl
+nmake test
-TODO
+TODO
- * Parser
- * Standard Builtins
- * Test against Octave Test Suite.
+* Parser
+* Standard Builtins
+* Test against Octave Test Suite.
-BUGS
+BUGS
Lots!
-CONTACT
+CONTACT
If you need to contact the Matrixy team, go to the project home page at:
diff --git a/test/markups/README.rdoc.html b/test/markups/README.rdoc.html
index 93770dde..996dc23e 100644
--- a/test/markups/README.rdoc.html
+++ b/test/markups/README.rdoc.html
@@ -1,12 +1,9 @@
-
--
+
-
One
-
--
+
-
Two
-
-
+
This is an absolute link. So is this: github.com
-This is a relative link. So is this: rawr.html
\ No newline at end of file
+This is a relative link. So is this: rawr.html
\ No newline at end of file
diff --git a/test/markups/README.rmd b/test/markups/README.rmd
deleted file mode 100644
index f9f5f702..00000000
--- a/test/markups/README.rmd
+++ /dev/null
@@ -1,3 +0,0 @@
-# Title
-* One
-* Two
diff --git a/test/markups/README.rmd.html b/test/markups/README.rmd.html
deleted file mode 100644
index 39644656..00000000
--- a/test/markups/README.rmd.html
+++ /dev/null
@@ -1,6 +0,0 @@
-Title
-
-
-- One
-- Two
-
\ No newline at end of file
diff --git a/test/markups/README.rst b/test/markups/README.rst
index efd1685b..ad7af380 100644
--- a/test/markups/README.rst
+++ b/test/markups/README.rst
@@ -8,6 +8,8 @@ Example text.
.. contents:: Table of Contents
+.. _label_for_header_2:
+
Header 2
--------
@@ -17,6 +19,8 @@ Header 2
3. Somé UTF-8°
+4. `Link to the above header `_
+
The UTF-8 quote character in this table used to cause python to go boom. Now docutils just silently ignores it.
.. csv-table:: Things that are Awesome (on a scale of 1-11)
@@ -31,6 +35,28 @@ The UTF-8 quote character in this table used to cause python to go boom. Now doc
Tabular Data, 5
Made up ratings, 11
+.. code::
+
+ A block of code
+
+.. code:: python
+
+ python.code('hooray')
+
+.. code:: python
+ :caption: An ignored Sphinx option
+ :made-up-option: An ignored made up option
+
+ python.code('hello world')
+
+.. doctest:: ignored
+
+ >>> some_function()
+ 'result'
+
+>>> some_function()
+'result'
+
============== ==========================================================
Travis http://travis-ci.org/tony/pullv
Docs http://pullv.rtfd.org
@@ -55,3 +81,12 @@ Field list
but no problem!
:123456789 12345: this is not so long, but long enough for the default!
:123456789 1234: this should work even with the default :)
+
+someone@somewhere.org
+
+Press :kbd:`Ctrl+C` to quit
+
+
+.. raw:: html
+
+ RAW HTML!
diff --git a/test/markups/README.rst.html b/test/markups/README.rst.html
index a074e8d5..40c7635e 100644
--- a/test/markups/README.rst.html
+++ b/test/markups/README.rst.html
@@ -1,73 +1,118 @@
Header 1
-Subtitle
+Subtitle
Example text.
-
-Table of Contents
+
+
+
Header 2
- Blah blah
code blah
- More
code, hooray
- Somé UTF-8°
+- Link to the above header
The UTF-8 quote character in this table used to cause python to go boom. Now docutils just silently ignores it.
+Things that are Awesome (on a scale of 1-11)
-
-Travis
+ Thing
+Awesomeness
+
+Icecream
+7
+
+Honey Badgers
+10.5
+
+Nickelback
+-2
+
+Iron Man
+10
+
+Iron Man 2
+3
+
+Tabular Data
+5
+
+Made up ratings
+11
+
+
+
+
+A block of code
+
+
+python.code('hooray')
+
+
+python.code('hello world')
+
+
+>>> some_function()
+'result'
+
+
+>>> some_function()
+'result'
+
+
+
+
+
+
+
+Travis
http://travis-ci.org/tony/pullv
-
-Docs
+ Docs
http://pullv.rtfd.org
-
-API
+ API
http://pullv.readthedocs.org/en/latest/api.html
-
-Issues
+ Issues
https://github.com/tony/pullv/issues
-
-Source
+ Source
https://github.com/tony/pullv
-
+
+
+
Field list
123456789 123456789 123456789 123456789 123456789 1:
-
-
-Uh-oh! This name is too long!
+ Uh-oh! This name is too long!
-
-123456789 123456789 123456789 123456789 1234567890:
-this is a long name,
+ 123456789 123456789 123456789 123456789 1234567890: this is a long name,
but no problem!
-
-123456789 12345:
-this is not so long, but long enough for the default!
+ 123456789 12345: this is not so long, but long enough for the default!
-
-123456789 1234:
-this should work even with the default :)
+ 123456789 1234: this should work even with the default :)
-
\ No newline at end of file
+
+
+Press Ctrl+C to quit
+RAW HTML!
\ No newline at end of file
diff --git a/test/markups/README.rst.txt.html b/test/markups/README.rst.txt.html
index d2e61dc6..0accc78e 100644
--- a/test/markups/README.rst.txt.html
+++ b/test/markups/README.rst.txt.html
@@ -1,5 +1,6 @@
Header 1
Example text.
+
Header 2
- Blah blah
code blah
@@ -12,24 +13,19 @@ Header 2
-
-Travis
+ Travis
http://travis-ci.org/tony/pullv
-
-Docs
+ Docs
http://pullv.rtfd.org
-
-API
+ API
http://pullv.readthedocs.org/en/latest/api.html
-
-Issues
+ Issues
https://github.com/tony/pullv/issues
-
-Source
+ Source
https://github.com/tony/pullv
diff --git a/test/markups/README.toc.asciidoc b/test/markups/README.toc.asciidoc
new file mode 100644
index 00000000..a8e42bdd
--- /dev/null
+++ b/test/markups/README.toc.asciidoc
@@ -0,0 +1,15 @@
+= Document Title
+:toc:
+:toc-title: Contents
+
+== Section A
+
+=== Subsection A-1
+
+=== Subsection A-2
+
+== Section B
+
+=== Subsection B-1
+
+=== Subsection B-2
diff --git a/test/markups/README.toc.asciidoc.html b/test/markups/README.toc.asciidoc.html
new file mode 100644
index 00000000..a692ce33
--- /dev/null
+++ b/test/markups/README.toc.asciidoc.html
@@ -0,0 +1,44 @@
+Document Title
+
+
+Section A
+
+
+Subsection A-1
+
+
+
+Subsection A-2
+
+
+
+
+
+Section B
+
+
+Subsection B-1
+
+
+
+Subsection B-2
+
+
+
+
\ No newline at end of file
diff --git a/test/markups/README.toc.rst b/test/markups/README.toc.rst
new file mode 100644
index 00000000..a86b88a5
--- /dev/null
+++ b/test/markups/README.toc.rst
@@ -0,0 +1,30 @@
+.. contents::
+ :backlinks: none
+
+.. sectnum::
+
+Introduction
+============
+
+What is pycparser?
+------------------
+
+**pycparser** is a parser for the C language, written in pure Python. It is a
+module designed to be easily integrated into applications that need to parse
+C source code.
+
+What is it good for?
+--------------------
+
+Anything that needs C code to be parsed. The following are some uses for
+**pycparser**, taken from real user reports:
+
+* C code obfuscator
+* Front-end for various specialized C compilers
+* Static code checker
+* Automatic unit-test discovery
+* Adding specialized extensions to the C language
+
+**pycparser** is unique in the sense that it's written in pure Python - a very
+high level language that's easy to experiment with and tweak. To people familiar
+with Lex and Yacc, **pycparser**'s code will be simple to understand.
diff --git a/test/markups/README.toc.rst.html b/test/markups/README.toc.rst.html
new file mode 100644
index 00000000..0bb5a753
--- /dev/null
+++ b/test/markups/README.toc.rst.html
@@ -0,0 +1,31 @@
+
+
+
+
+
+1 Introduction
+
+1.1 What is pycparser?
+pycparser is a parser for the C language, written in pure Python. It is a
+module designed to be easily integrated into applications that need to parse
+C source code.
+
+1.2 What is it good for?
+Anything that needs C code to be parsed. The following are some uses for
+pycparser, taken from real user reports:
+
+- C code obfuscator
+- Front-end for various specialized C compilers
+- Static code checker
+- Automatic unit-test discovery
+- Adding specialized extensions to the C language
+
+pycparser is unique in the sense that it's written in pure Python - a very
+high level language that's easy to experiment with and tweak. To people familiar
+with Lex and Yacc, pycparser's code will be simple to understand.
\ No newline at end of file
diff --git a/test/test_helper.rb b/test/test_helper.rb
new file mode 100644
index 00000000..e04c9fdd
--- /dev/null
+++ b/test/test_helper.rb
@@ -0,0 +1,11 @@
+require "simplecov"
+
+SimpleCov.start do
+ enable_coverage :branch
+ add_filter "/test/"
+ add_filter "/spec/"
+ add_filter "/features/"
+ track_files "lib/**/*.rb"
+ command_name "MarkupTests"
+ minimum_coverage line: 100, branch: 100
+end