├── .github ├── pull_request_template.md └── workflows │ ├── lint.yml │ ├── tag_and_release.yml │ └── test.yml ├── .gitignore ├── .gitmodules ├── .rubocop.yml ├── .rubocop_todo.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── Dockerfile ├── Gemfile ├── LICENSE.txt ├── Makefile ├── README.md ├── Rakefile ├── bin └── qiita_marker ├── docker-compose.yml ├── ext └── qiita_marker │ ├── arena.c │ ├── autolink.c │ ├── autolink.h │ ├── blocks.c │ ├── buffer.c │ ├── buffer.h │ ├── case_fold_switch.inc │ ├── chunk.h │ ├── cmark-gfm-core-extensions.h │ ├── cmark-gfm-extension_api.h │ ├── cmark-gfm-extensions_export.h │ ├── cmark-gfm.h │ ├── cmark-gfm_export.h │ ├── cmark-gfm_version.h │ ├── cmark.c │ ├── cmark_ctype.c │ ├── cmark_ctype.h │ ├── commonmark.c │ ├── config.h │ ├── core-extensions.c │ ├── entities.inc │ ├── ext_scanners.c │ ├── ext_scanners.h │ ├── extconf.rb │ ├── footnotes.c │ ├── footnotes.h │ ├── houdini.h │ ├── houdini_href_e.c │ ├── houdini_html_e.c │ ├── houdini_html_u.c │ ├── html.c │ ├── html.h │ ├── inlines.c │ ├── inlines.h │ ├── iterator.c │ ├── iterator.h │ ├── latex.c │ ├── linked_list.c │ ├── man.c │ ├── map.c │ ├── map.h │ ├── node.c │ ├── node.h │ ├── parser.h │ ├── plaintext.c │ ├── plugin.c │ ├── plugin.h │ ├── qfm.h │ ├── qfm_custom_block.c │ ├── qfm_custom_block.h │ ├── qfm_mention_no_emphasis.c │ ├── qfm_mention_no_emphasis.h │ ├── qfm_scanners.c │ ├── qfm_scanners.h │ ├── qfm_scanners.re │ ├── qiita_marker.c │ ├── qiita_marker.h │ ├── references.c │ ├── references.h │ ├── registry.c │ ├── registry.h │ ├── render.c │ ├── render.h │ ├── scanners.c │ ├── scanners.h │ ├── scanners.re │ ├── strikethrough.c │ ├── strikethrough.h │ ├── syntax_extension.c │ ├── syntax_extension.h │ ├── table.c │ ├── table.h │ ├── tagfilter.c │ ├── tagfilter.h │ ├── tasklist.c │ ├── tasklist.h │ ├── utf8.c │ ├── utf8.h │ └── xml.c ├── lib ├── qiita_marker.rb └── qiita_marker │ ├── config.rb │ ├── node.rb │ ├── node │ └── inspect.rb │ ├── renderer.rb │ ├── renderer │ └── html_renderer.rb │ └── version.rb ├── qiita_marker.gemspec ├── script ├── bootstrap ├── changelog ├── cibuild ├── single_test └── update_submodules ├── tasks └── qiita_marker.rake └── test ├── benchmark.rb ├── fixtures ├── curly.md ├── dingus.md ├── strong.md └── table.md ├── test_attributes.rb ├── test_basics.rb ├── test_commands.rb ├── test_commonmark.rb ├── test_doc.rb ├── test_encoding.rb ├── test_extensions.rb ├── test_footnotes.rb ├── test_gc.rb ├── test_helper.rb ├── test_linebreaks.rb ├── test_maliciousness.rb ├── test_node.rb ├── test_options.rb ├── test_pathological_inputs.rb ├── test_plaintext.rb ├── test_qfm_autolink_class_name.rb ├── test_qfm_code_data_metadata.rb ├── test_qfm_custom_block.rb ├── test_qfm_mention_no_emphasis.rb ├── test_renderer.rb ├── test_smartpunct.rb ├── test_spec.rb ├── test_tasklists.rb └── test_xml.rb /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Linting 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | test: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: ruby/setup-ruby@v1 12 | with: 13 | ruby-version: '3.0' 14 | bundler-cache: true # 'bundle install' and cache 15 | - name: Rubocop 16 | run: bundle exec rake rubocop 17 | -------------------------------------------------------------------------------- /.github/workflows/tag_and_release.yml: -------------------------------------------------------------------------------- 1 | name: Tag and Release 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - c-api-stable 8 | paths: 9 | - "lib/qiita_marker/version.rb" 10 | 11 | jobs: 12 | c_gem: 13 | name: Compile C gem 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - name: Set up Ruby 20 | uses: ruby/setup-ruby@v1 21 | with: 22 | ruby-version: 3.1 23 | bundler-cache: true 24 | 25 | - name: Compile gem 26 | run: bundle exec rake compile 27 | 28 | - name: Build gem 29 | run: bundle exec rake build 30 | 31 | - name: Display structure of built gems 32 | run: ls -R 33 | working-directory: pkg/ 34 | 35 | - name: Publish to RubyGems 36 | working-directory: pkg/ 37 | env: 38 | GEM_HOST_API_KEY: ${{secrets.RUBYGEMS_API_BOT_KEY}} 39 | run: | 40 | mkdir -p $HOME/.gem 41 | touch $HOME/.gem/credentials 42 | chmod 0600 $HOME/.gem/credentials 43 | printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials 44 | for i in *.gem; do 45 | if [ -f "$i" ] 46 | then 47 | gem push "$i" || true 48 | fi 49 | done 50 | 51 | release: 52 | needs: ["c_gem"] 53 | env: 54 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 55 | runs-on: ubuntu-latest 56 | steps: 57 | - uses: actions/checkout@v3 58 | 59 | - name: Set up Ruby 3.1 60 | uses: ruby/setup-ruby@v1 61 | with: 62 | rubygems: latest 63 | ruby-version: 3.1 64 | bundler-cache: true 65 | 66 | - name: Configure Git 67 | run: | 68 | git config --local user.email "actions@github.com" 69 | git config --local user.name "Actions Auto Build" 70 | 71 | - name: Get current version 72 | id: version-label 73 | run: | 74 | VERSION=$(grep VERSION lib/qiita_marker/version.rb | head -n 1 | cut -d'"' -f2) 75 | echo "version=${VERSION}" >> $GITHUB_OUTPUT 76 | 77 | - name: Create tag 78 | run: | 79 | git tag -a v${{ steps.version-label.outputs.version }} -m "Release v${{ steps.version-label.outputs.version }}" 80 | git push origin --tags 81 | 82 | - name: Publish release 83 | env: 84 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 85 | run: | 86 | gh release create v${{ steps.version-label.outputs.version }} --generate-notes 87 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | test: 8 | strategy: 9 | fail-fast: false 10 | matrix: 11 | os: 12 | - ubuntu-latest 13 | - macos-latest 14 | - windows-latest 15 | ruby-version: [3.1, 3.0.6] 16 | 17 | runs-on: ${{ matrix.os }} 18 | 19 | steps: 20 | - uses: actions/checkout@v2 21 | with: 22 | submodules: recursive 23 | 24 | - name: Set up Ruby ${{ matrix.ruby-version }} 25 | uses: ruby/setup-ruby@v1 26 | with: 27 | ruby-version: ${{ matrix.ruby-version }} 28 | bundler-cache: true # 'bundle install' and cache 29 | 30 | - name: Run ${{ matrix.os }} tests 31 | shell: bash 32 | run: script/cibuild 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.so 2 | *.rbc 3 | *.bundle 4 | /.bundle 5 | /.config 6 | /coverage/ 7 | /.idea 8 | /InstalledFiles 9 | /pkg/ 10 | /spec/reports/ 11 | /test/tmp/ 12 | /test/version_tmp/ 13 | /tmp/ 14 | /vendor/gems 15 | /vendor/cache 16 | Gemfile.lock 17 | 18 | ## Specific to RubyMotion: 19 | .dat* 20 | .repl_history 21 | build/ 22 | 23 | ## Documentation cache and generated files: 24 | /.yardoc/ 25 | /_yardoc/ 26 | /docs/ 27 | /rdoc/ 28 | 29 | ## Environment normalisation: 30 | /lib/bundler/man/ 31 | 32 | # for a library or gem, you might want to ignore these files since the code is 33 | # intended to run in multiple environments; otherwise, check them in: 34 | # .ruby-version 35 | # .ruby-gemset 36 | 37 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 38 | .rvmrc 39 | 40 | actual.txt 41 | test.txt 42 | test/progit 43 | test/benchinput.md 44 | 45 | .vscode 46 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ext/commonmarker/cmark-upstream"] 2 | path = ext/qiita_marker/cmark-upstream 3 | url = https://github.com/github/cmark-gfm.git 4 | ignore = dirty 5 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: .rubocop_todo.yml 2 | 3 | inherit_gem: 4 | rubocop-standard: 5 | - config/default.yml 6 | - config/minitest.yml 7 | 8 | AllCops: 9 | TargetRubyVersion: 3.0 10 | Exclude: 11 | - "ext/**/*" 12 | - "vendor/**/*" 13 | - "tmp/**/*" 14 | - "test/progit/**/*" 15 | -------------------------------------------------------------------------------- /.rubocop_todo.yml: -------------------------------------------------------------------------------- 1 | # This configuration was generated by 2 | # `rubocop --auto-gen-config --exclude-limit 999999` 3 | # on 2022-11-10 05:33:40 UTC using RuboCop version 1.38.0. 4 | # The point is for the user to remove these configuration records 5 | # one by one as the offenses are removed from the code base. 6 | # Note that changes in the inspected code, or installation of new 7 | # versions of RuboCop, may require this file to be generated again. 8 | 9 | # Offense count: 42 10 | # This cop supports safe autocorrection (--autocorrect). 11 | Minitest/EmptyLineBeforeAssertionMethods: 12 | Exclude: 13 | - 'test/test_basics.rb' 14 | - 'test/test_commands.rb' 15 | - 'test/test_doc.rb' 16 | - 'test/test_encoding.rb' 17 | - 'test/test_extensions.rb' 18 | - 'test/test_gc.rb' 19 | - 'test/test_linebreaks.rb' 20 | - 'test/test_maliciousness.rb' 21 | - 'test/test_node.rb' 22 | - 'test/test_pathological_inputs.rb' 23 | - 'test/test_renderer.rb' 24 | - 'test/test_smartpunct.rb' 25 | - 'test/test_spec.rb' 26 | - 'test/test_tasklists.rb' 27 | 28 | # Offense count: 3 29 | # This cop supports safe autocorrection (--autocorrect). 30 | # Configuration parameters: EnforcedStyle. 31 | # SupportedStyles: def_self, self_class 32 | Style/ClassMethodsDefinitions: 33 | Exclude: 34 | - 'lib/qiita_marker.rb' 35 | - 'lib/qiita_marker/config.rb' 36 | 37 | # Offense count: 2 38 | # This cop supports safe autocorrection (--autocorrect). 39 | # Configuration parameters: EnforcedStyleForMultiline. 40 | # SupportedStylesForMultiline: comma, consistent_comma, no_comma 41 | Style/TrailingCommaInArguments: 42 | Exclude: 43 | - 'lib/qiita_marker/renderer.rb' 44 | - 'test/test_commonmark.rb' 45 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Unreleased 4 | 5 | ### Added 6 | 7 | ### Changed 8 | 9 | ## 0.23.9.0 - 2023-05-16 10 | 11 | ### Changed 12 | 13 | - [#40](https://github.com/increments/qiita_marker/pull/40) 14 | - Update base CommonMarker version to `v0.23.9`. 15 | - Drop Ruby 2.7 support 16 | 17 | ## 0.23.6.2 - 2022-11-18 18 | 19 | ### Changed 20 | 21 | - [#38](https://github.com/increments/qiita_marker/pull/38): Drop Ruby 2.6 support 22 | 23 | ## 0.23.6.1 - 2022-11-10 24 | 25 | - Finish testing phase and release official version. 26 | 27 | ## 0.23.6.0 - 2022-10-04 28 | 29 | ### Changed 30 | 31 | - [#33](https://github.com/increments/qiita_marker/pull/33): Update base CommonMarker version to `v0.23.6`. 32 | 33 | ## 0.23.5.1 - 2022-06-21 34 | 35 | ### Changed 36 | 37 | - [#28](https://github.com/increments/qiita_marker/pull/28): Fix a bug that sourcepos of custom_block is wrong. 38 | - [#30](https://github.com/increments/qiita_marker/pull/30): Fix a bug that contains custom block fence in `data-metadata` when custom block is used in backquote. 39 | 40 | ## 0.23.5.0 - 2022-06-01 41 | 42 | ### Changed 43 | 44 | - [#26](https://github.com/increments/qiita_marker/pull/26): Update base CommonMarker version to `v0.23.5`. 45 | 46 | ## 0.23.2.3 - 2022-03-04 47 | 48 | ### Changed 49 | 50 | - [#23](https://github.com/increments/qiita_marker/pull/23): Fixed unintentional license change issue 51 | 52 | ## 0.23.2.2 - 2022-01-11 53 | 54 | ### Added 55 | 56 | - [#21](https://github.com/increments/qiita_marker/pull/21): Add `AUTOLINK_CLASS_NAME` option for autolink extension. 57 | 58 | ### Changed 59 | 60 | - [#19](https://github.com/increments/qiita_marker/pull/19): Allow a custom block to contain any node type. 61 | - [#20](https://github.com/increments/qiita_marker/pull/20): Allow `data-metadata` to accept strings up to the end of the line. 62 | 63 | ## 0.23.2.1 - 2021-12-08 64 | 65 | ### Added 66 | 67 | - [#9](https://github.com/increments/qiita_marker/pull/9): Add `CODE_DATA_METADATA` option. 68 | - [#10](https://github.com/increments/qiita_marker/pull/10): Add `custom_block` extension. 69 | - [#11](https://github.com/increments/qiita_marker/pull/11): Add `MENTION_NO_EMPHASIS` option. 70 | 71 | ## 0.23.2.0 - 2021-11-26 72 | 73 | - Initial release 74 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | We as members, contributors, and leaders pledge to make participation in our 7 | community a harassment-free experience for everyone, regardless of age, body 8 | size, visible or invisible disability, ethnicity, sex characteristics, gender 9 | identity and expression, level of experience, education, socio-economic status, 10 | nationality, personal appearance, race, religion, or sexual identity 11 | and orientation. 12 | 13 | We pledge to act and interact in ways that contribute to an open, welcoming, 14 | diverse, inclusive, and healthy community. 15 | 16 | ## Our Standards 17 | 18 | Examples of behavior that contributes to a positive environment for our 19 | community include: 20 | 21 | * Demonstrating empathy and kindness toward other people 22 | * Being respectful of differing opinions, viewpoints, and experiences 23 | * Giving and gracefully accepting constructive feedback 24 | * Accepting responsibility and apologizing to those affected by our mistakes, 25 | and learning from the experience 26 | * Focusing on what is best not just for us as individuals, but for the 27 | overall community 28 | 29 | Examples of unacceptable behavior include: 30 | 31 | * The use of sexualized language or imagery, and sexual attention or 32 | advances of any kind 33 | * Trolling, insulting or derogatory comments, and personal or political attacks 34 | * Public or private harassment 35 | * Publishing others' private information, such as a physical or email 36 | address, without their explicit permission 37 | * Other conduct which could reasonably be considered inappropriate in a 38 | professional setting 39 | 40 | ## Enforcement Responsibilities 41 | 42 | Community leaders are responsible for clarifying and enforcing our standards of 43 | acceptable behavior and will take appropriate and fair corrective action in 44 | response to any behavior that they deem inappropriate, threatening, offensive, 45 | or harmful. 46 | 47 | Community leaders have the right and responsibility to remove, edit, or reject 48 | comments, commits, code, wiki edits, issues, and other contributions that are 49 | not aligned to this Code of Conduct, and will communicate reasons for moderation 50 | decisions when appropriate. 51 | 52 | ## Scope 53 | 54 | This Code of Conduct applies within all community spaces, and also applies when 55 | an individual is officially representing the community in public spaces. 56 | Examples of representing our community include using an official e-mail address, 57 | posting via an official social media account, or acting as an appointed 58 | representative at an online or offline event. 59 | 60 | ## Enforcement 61 | 62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 63 | reported to the community leaders responsible for enforcement at 64 | [Qiita Support](https://support.qiita.com/hc/ja/requests/new). 65 | All complaints will be reviewed and investigated promptly and fairly. 66 | 67 | All community leaders are obligated to respect the privacy and security of the 68 | reporter of any incident. 69 | 70 | ## Enforcement Guidelines 71 | 72 | Community leaders will follow these Community Impact Guidelines in determining 73 | the consequences for any action they deem in violation of this Code of Conduct: 74 | 75 | ### 1. Correction 76 | 77 | **Community Impact**: Use of inappropriate language or other behavior deemed 78 | unprofessional or unwelcome in the community. 79 | 80 | **Consequence**: A private, written warning from community leaders, providing 81 | clarity around the nature of the violation and an explanation of why the 82 | behavior was inappropriate. A public apology may be requested. 83 | 84 | ### 2. Warning 85 | 86 | **Community Impact**: A violation through a single incident or series 87 | of actions. 88 | 89 | **Consequence**: A warning with consequences for continued behavior. No 90 | interaction with the people involved, including unsolicited interaction with 91 | those enforcing the Code of Conduct, for a specified period of time. This 92 | includes avoiding interactions in community spaces as well as external channels 93 | like social media. Violating these terms may lead to a temporary or 94 | permanent ban. 95 | 96 | ### 3. Temporary Ban 97 | 98 | **Community Impact**: A serious violation of community standards, including 99 | sustained inappropriate behavior. 100 | 101 | **Consequence**: A temporary ban from any sort of interaction or public 102 | communication with the community for a specified period of time. No public or 103 | private interaction with the people involved, including unsolicited interaction 104 | with those enforcing the Code of Conduct, is allowed during this period. 105 | Violating these terms may lead to a permanent ban. 106 | 107 | ### 4. Permanent Ban 108 | 109 | **Community Impact**: Demonstrating a pattern of violation of community 110 | standards, including sustained inappropriate behavior, harassment of an 111 | individual, or aggression toward or disparagement of classes of individuals. 112 | 113 | **Consequence**: A permanent ban from any sort of public interaction within 114 | the community. 115 | 116 | ## Attribution 117 | 118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 119 | version 2.0, available at 120 | [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. 121 | 122 | Community Impact Guidelines were inspired by 123 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 124 | 125 | For answers to common questions about this code of conduct, see the FAQ at 126 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available 127 | at [https://www.contributor-covenant.org/translations][translations]. 128 | 129 | [homepage]: https://www.contributor-covenant.org 130 | [v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html 131 | [Mozilla CoC]: https://github.com/mozilla/diversity 132 | [FAQ]: https://www.contributor-covenant.org/faq 133 | [translations]: https://www.contributor-covenant.org/translations 134 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:3.0.6 2 | 3 | ENV RE2C_VERSION 1.3 4 | 5 | RUN apt update \ 6 | && apt install -y \ 7 | clang-format \ 8 | cmake \ 9 | wget \ 10 | && apt clean 11 | 12 | # Install re2c 13 | RUN wget https://github.com/skvadrik/re2c/releases/download/${RE2C_VERSION}/re2c-${RE2C_VERSION}.tar.xz \ 14 | && tar xf re2c-${RE2C_VERSION}.tar.xz \ 15 | && cd re2c-${RE2C_VERSION} \ 16 | && ./configure \ 17 | && make \ 18 | && make install \ 19 | && cd .. \ 20 | && rm -rf re2c-${RE2C_VERSION} 21 | 22 | WORKDIR /work 23 | 24 | COPY . /work/ 25 | 26 | RUN bundle config set clean 'true' 27 | RUN bundle config set path 'vendor/gems' 28 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org/" 4 | 5 | gemspec 6 | 7 | group :benchmark do 8 | gem "benchmark-ips" 9 | gem "kramdown" 10 | gem "redcarpet" 11 | end 12 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Garen J. Torikian 2 | Copyright (c) 2021 Qiita Inc. 3 | 4 | MIT License 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | C_SOURCES = $(wildcard ext/qiita_marker/*.[ch]) 2 | 3 | update-c-sources: build-upstream $(C_SOURCES) 4 | 5 | .PHONY: build-upstream 6 | 7 | build-upstream: 8 | cd ext/qiita_marker/cmark-upstream && make 9 | 10 | ext/qiita_marker/%: ext/qiita_marker/cmark-upstream/src/% 11 | cp $< $@ 12 | 13 | ext/qiita_marker/%: ext/qiita_marker/cmark-upstream/extensions/% 14 | cp $< $@ 15 | 16 | ext/qiita_marker/config.h: ext/qiita_marker/cmark-upstream/build/src/config.h 17 | cp $< $@ 18 | 19 | ext/qiita_marker/cmark-gfm_export.h: ext/qiita_marker/cmark-upstream/build/src/cmark-gfm_export.h 20 | cp $< $@ 21 | 22 | ext/qiita_marker/cmark-gfm_version.h: ext/qiita_marker/cmark-upstream/build/src/cmark-gfm_version.h 23 | cp $< $@ 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Qiita Marker 2 | 3 | [![Build Status](https://github.com/increments/qiita-marker/actions/workflows/test.yml/badge.svg)](https://github.com/increments/qiita-marker/actions/workflows/test.yml) [![Gem Version](https://badge.fury.io/rb/qiita_marker.svg)](https://badge.fury.io/rb/qiita_marker) 4 | 5 | Qiita Marker is a Ruby library for Markdown processing, based on [CommonMarker](https://github.com/gjtorikian/commonmarker). 6 | It will be a core module of [Qiita Markdown](https://github.com/increments/qiita-markdown) gem and not intended for direct use. If you are looking for Qiita-specified markdown processor, use [Qiita Markdown](https://github.com/increments/qiita-markdown) gem. 7 | 8 | ## Usage 9 | 10 | Please see [CommonMarker's Usage](https://github.com/gjtorikian/commonmarker#usage). 11 | 12 | In addition to CommonMarker's options and extensions, the following are available in Qiita Marker. 13 | 14 | ### Original options 15 | 16 | #### Parse options 17 | 18 | | Name | Description | 19 | | --- | --- | 20 | | `:MENTION_NO_EMPHASIS` | Prevent parsing mentions as emphasis. | 21 | | `:AUTOLINK_CLASS_NAME` | Append `class="autolink"` to extension's autolinks. | 22 | 23 | #### Render options 24 | 25 | | Name | Description | 26 | | --- | --- | 27 | | `:CODE_DATA_METADATA` | Use `` for fenced code blocks. | 28 | | `:MENTION_NO_EMPHASIS` | Prevent parsing mentions as emphasis. | 29 | | `:AUTOLINK_CLASS_NAME` | Append `class="autolink"` to extension's autolinks. | 30 | 31 | ### Original extensions 32 | 33 | - `:custom_block` - This provides support for customizable blocks. 34 | 35 | ## Contributing 36 | 37 | If you have suggestion or modification to this repository, please create an Issue or Pull Request. 38 | 39 | ### How to develop 40 | 41 | ``` 42 | # Clone repository 43 | $ git clone git@github.com:increments/qiita_marker.git 44 | $ cd qiita_marker 45 | 46 | # Setup development environment 47 | $ ./script/bootstrap 48 | 49 | # Run test 50 | $ bundle exec rake test 51 | ``` 52 | 53 | #### with Docker 54 | 55 | ``` 56 | # Clone repository 57 | $ git clone git@github.com:increments/qiita_marker.git 58 | $ cd qiita_marker 59 | 60 | # Setup development environment 61 | $ docker compose build 62 | $ docker compose up -d 63 | $ docker compose run --rm app ./script/bootstrap 64 | 65 | # Run test 66 | $ docker compose run --rm rake test 67 | ``` 68 | 69 | ### Versioning policy 70 | 71 | Qiita Marker follows CommonMarker's updates by merging the upstream changes. 72 | The version format is `MAJOR.MINOR.PATCH.FORK`. `MAJOR.MINOR.PATCH` is the same as the version of CommonMarker that Qiita Marker is based on. `FORK` is incremented on each release of Qiita Marker itself and reset to zero when any of `MAJOR.MINOR.PATCH` is bumped. 73 | 74 | ## License 75 | 76 | Please see [LICENSE.txt](/LICENSE.txt). 77 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "date" 4 | require "rake/clean" 5 | require "rake/extensiontask" 6 | require "digest/md5" 7 | 8 | host_os = RbConfig::CONFIG["host_os"] 9 | require "devkit" if host_os == "mingw32" 10 | 11 | task default: [:test] 12 | 13 | # Gem Spec 14 | gem_spec = Gem::Specification.load("qiita_marker.gemspec") 15 | 16 | # Ruby Extension 17 | Rake::ExtensionTask.new("qiita_marker", gem_spec) do |ext| 18 | ext.lib_dir = File.join("lib", "qiita_marker") 19 | end 20 | 21 | # Packaging 22 | require "bundler/gem_tasks" 23 | 24 | # Testing 25 | require "rake/testtask" 26 | 27 | Rake::TestTask.new("test:unit") do |t| 28 | t.libs << "lib" 29 | t.libs << "test" 30 | t.pattern = "test/test_*.rb" 31 | t.verbose = true 32 | t.warning = false 33 | end 34 | 35 | desc "Run unit tests" 36 | task "test:unit" => :compile 37 | 38 | desc "Run unit and conformance tests" 39 | task test: ["test:unit"] 40 | 41 | require "rubocop/rake_task" 42 | 43 | RuboCop::RakeTask.new(:rubocop) 44 | 45 | desc "Run benchmarks" 46 | task :benchmark do 47 | if ENV["FETCH_PROGIT"] 48 | %x(rm -rf test/progit) 49 | %x(git clone https://github.com/progit/progit.git test/progit) 50 | langs = ["ar", "az", "be", "ca", "cs", "de", "en", "eo", "es", "es-ni", "fa", "fi", "fr", "hi", "hu", "id", "it", "ja", "ko", "mk", "nl", "no-nb", "pl", "pt-br", "ro", "ru", "sr", "th", "tr", "uk", "vi", "zh", "zh-tw"] 51 | langs.each do |lang| 52 | %x(cat test/progit/#{lang}/*/*.markdown >> test/benchinput.md) 53 | end 54 | end 55 | $LOAD_PATH.unshift("lib") 56 | load "test/benchmark.rb" 57 | end 58 | 59 | desc "Match C style of cmark" 60 | task :format do 61 | sh "clang-format -style llvm -i ext/qiita_marker/*.c ext/qiita_marker/*.h" 62 | end 63 | 64 | # Documentation 65 | require "rdoc/task" 66 | 67 | desc "Generate API documentation" 68 | RDoc::Task.new do |rd| 69 | rd.rdoc_dir = "docs" 70 | rd.main = "README.md" 71 | rd.rdoc_files.include("README.md", "lib/**/*.rb", "ext/qiita_marker/qiita_marker.c") 72 | 73 | rd.options << "--markup tomdoc" 74 | rd.options << "--inline-source" 75 | rd.options << "--line-numbers" 76 | rd.options << "--all" 77 | rd.options << "--fileboxes" 78 | end 79 | 80 | desc "Generate the documentation and run a web server" 81 | task serve: [:rdoc] do 82 | require "webrick" 83 | 84 | puts "Navigate to http://localhost:3000 to see the docs" 85 | 86 | server = WEBrick::HTTPServer.new(Port: 3000) 87 | server.mount("/", WEBrick::HTTPServlet::FileHandler, "docs") 88 | trap("INT") { server.stop } 89 | server.start 90 | end 91 | 92 | desc "Generate and publish docs to gh-pages" 93 | task publish: [:rdoc] do 94 | require "tmpdir" 95 | require "shellwords" 96 | 97 | Dir.mktmpdir do |tmp| 98 | system "mv docs/* #{tmp}" 99 | system "git checkout origin/gh-pages" 100 | system "rm -rf *" 101 | system "mv #{tmp}/* ." 102 | message = Shellwords.escape("Site updated at #{Time.now.utc}") 103 | system "git add ." 104 | system "git commit -am #{message}" 105 | system "git push origin gh-pages --force" 106 | system "git checkout master" 107 | system "echo yolo" 108 | end 109 | end 110 | 111 | # Custom tasks 112 | Dir.glob("tasks/*.rake").each do |f| 113 | load f 114 | end 115 | -------------------------------------------------------------------------------- /bin/qiita_marker: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require 'optparse' 5 | 6 | $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib') 7 | require 'qiita_marker' 8 | 9 | root = File.expand_path('..', __dir__) 10 | $LOAD_PATH.unshift File.expand_path('lib', root) 11 | 12 | def parse_options 13 | options = Struct.new(:active_extensions, :active_parse_options, :active_render_options, :output_format, :renderer) 14 | .new([], [:DEFAULT], [:DEFAULT], :html) 15 | extensions = QiitaMarker.extensions 16 | parse_options = QiitaMarker::Config::OPTS.fetch(:parse) 17 | render_options = QiitaMarker::Config::OPTS.fetch(:render) 18 | format_options = QiitaMarker::Config::OPTS.fetch(:format) 19 | 20 | option_parser = OptionParser.new do |opts| 21 | opts.banner = 'Usage: qiita_marker [--html-renderer] [--extension=EXTENSION]' 22 | opts.separator ' [--to=FORMAT]' 23 | opts.separator ' [--parse-option=OPTION]' 24 | opts.separator ' [--render-option=OPTION]' 25 | opts.separator ' [FILE..]' 26 | opts.separator '' 27 | opts.separator 'Convert one or more CommonMark files to HTML and write to standard output.' 28 | opts.separator 'If no FILE argument is provided, text will be read from STDIN.' 29 | opts.separator '' 30 | 31 | opts.on('--extension=EXTENSION', Array, 'Use EXTENSION for parsing and HTML output (unless --html-renderer is specified)') do |values| 32 | values.each do |value| 33 | if extensions.include?(value) 34 | options.active_extensions << value.to_sym 35 | else 36 | abort("extension '#{value}' not found") 37 | end 38 | end 39 | end 40 | 41 | opts.on('-h', '--help', 'Prints this help') do 42 | puts opts 43 | puts 44 | puts "Available formats: #{format_options.join(', ')}" 45 | puts "Available extentions: #{extensions.join(', ')}" 46 | puts "Available parse options: #{parse_options.keys.join(', ')}" 47 | puts "Available render options: #{render_options.keys.join(', ')}" 48 | puts 49 | puts 'See the README for more information on these.' 50 | exit 51 | end 52 | 53 | opts.on('-tFORMAT', '--to=FORMAT', String, 'Specify output FORMAT') do |value| 54 | value = value.to_sym 55 | if format_options.include?(value) 56 | options.output_format = value 57 | else 58 | abort("format '#{value}' not found") 59 | end 60 | end 61 | 62 | opts.on('--html-renderer', 'Use the HtmlRenderer renderer rather than the native C renderer (only valid when format is html)') do 63 | options.renderer = true 64 | end 65 | 66 | opts.on('--parse-option=OPTION', Array, 'OPTION passed during parsing') do |values| 67 | values.each do |value| 68 | if parse_options.key?(value.to_sym) 69 | options.active_parse_options << value.to_sym 70 | else 71 | abort("parse-option '#{value}' not found") 72 | end 73 | end 74 | end 75 | 76 | opts.on('--render-option=OPTION', Array, 'OPTION passed during rendering') do |values| 77 | values.each do |value| 78 | if render_options.key?(value.to_sym) 79 | options.active_render_options << value.to_sym 80 | else 81 | abort("render-option '#{value}' not found") 82 | end 83 | end 84 | end 85 | 86 | opts.on('-v', '--version', 'Version information') do 87 | puts "qiita_marker #{QiitaMarker::VERSION}" 88 | exit 89 | end 90 | end 91 | 92 | option_parser.parse! 93 | 94 | options 95 | end 96 | 97 | options = parse_options 98 | 99 | abort("format '#{options.output_format}' does not support using the HtmlRenderer renderer") if 100 | options.renderer && options.output_format != :html 101 | 102 | doc = QiitaMarker.render_doc(ARGF.read, options.active_parse_options, options.active_extensions) 103 | 104 | case options.output_format 105 | when :html 106 | if options.renderer 107 | renderer = QiitaMarker::HtmlRenderer.new(options: options.active_render_options, extensions: options.active_extensions) 108 | $stdout.write(renderer.render(doc)) 109 | else 110 | $stdout.write(doc.to_html(options.active_render_options, options.active_extensions)) 111 | end 112 | when :xml 113 | $stdout.write(doc.to_xml(options.active_render_options)) 114 | when :commonmark 115 | $stdout.write(doc.to_commonmark(options.active_render_options)) 116 | when :plaintext 117 | $stdout.write(doc.to_plaintext(options.active_render_options)) 118 | end 119 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | app: &base 4 | build: . 5 | volumes: 6 | - .:/work/ 7 | - vendor-gems:/work/vendor/gems 8 | tty: true 9 | stdin_open: true 10 | rake: 11 | <<: *base 12 | entrypoint: bundle exec rake 13 | volumes: 14 | vendor-gems: 15 | -------------------------------------------------------------------------------- /ext/qiita_marker/arena.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "cmark-gfm.h" 5 | #include "cmark-gfm-extension_api.h" 6 | 7 | static struct arena_chunk { 8 | size_t sz, used; 9 | uint8_t push_point; 10 | void *ptr; 11 | struct arena_chunk *prev; 12 | } *A = NULL; 13 | 14 | static struct arena_chunk *alloc_arena_chunk(size_t sz, struct arena_chunk *prev) { 15 | struct arena_chunk *c = (struct arena_chunk *)calloc(1, sizeof(*c)); 16 | if (!c) 17 | abort(); 18 | c->sz = sz; 19 | c->ptr = calloc(1, sz); 20 | if (!c->ptr) 21 | abort(); 22 | c->prev = prev; 23 | return c; 24 | } 25 | 26 | void cmark_arena_push(void) { 27 | if (!A) 28 | return; 29 | A->push_point = 1; 30 | A = alloc_arena_chunk(10240, A); 31 | } 32 | 33 | int cmark_arena_pop(void) { 34 | if (!A) 35 | return 0; 36 | while (A && !A->push_point) { 37 | free(A->ptr); 38 | struct arena_chunk *n = A->prev; 39 | free(A); 40 | A = n; 41 | } 42 | if (A) 43 | A->push_point = 0; 44 | return 1; 45 | } 46 | 47 | static void init_arena(void) { 48 | A = alloc_arena_chunk(4 * 1048576, NULL); 49 | } 50 | 51 | void cmark_arena_reset(void) { 52 | while (A) { 53 | free(A->ptr); 54 | struct arena_chunk *n = A->prev; 55 | free(A); 56 | A = n; 57 | } 58 | } 59 | 60 | static void *arena_calloc(size_t nmem, size_t size) { 61 | if (!A) 62 | init_arena(); 63 | 64 | size_t sz = nmem * size + sizeof(size_t); 65 | 66 | // Round allocation sizes to largest integer size to 67 | // ensure returned memory is correctly aligned 68 | const size_t align = sizeof(size_t) - 1; 69 | sz = (sz + align) & ~align; 70 | 71 | struct arena_chunk *chunk; 72 | if (sz > A->sz) { 73 | A->prev = chunk = alloc_arena_chunk(sz, A->prev); 74 | } else if (sz > A->sz - A->used) { 75 | A = chunk = alloc_arena_chunk(A->sz + A->sz / 2, A); 76 | } else { 77 | chunk = A; 78 | } 79 | void *ptr = (uint8_t *) chunk->ptr + chunk->used; 80 | chunk->used += sz; 81 | *((size_t *) ptr) = sz - sizeof(size_t); 82 | return (uint8_t *) ptr + sizeof(size_t); 83 | } 84 | 85 | static void *arena_realloc(void *ptr, size_t size) { 86 | if (!A) 87 | init_arena(); 88 | 89 | void *new_ptr = arena_calloc(1, size); 90 | if (ptr) 91 | memcpy(new_ptr, ptr, ((size_t *) ptr)[-1]); 92 | return new_ptr; 93 | } 94 | 95 | static void arena_free(void *ptr) { 96 | (void) ptr; 97 | /* no-op */ 98 | } 99 | 100 | cmark_mem CMARK_ARENA_MEM_ALLOCATOR = {arena_calloc, arena_realloc, arena_free}; 101 | 102 | cmark_mem *cmark_get_arena_mem_allocator(void) { 103 | return &CMARK_ARENA_MEM_ALLOCATOR; 104 | } 105 | -------------------------------------------------------------------------------- /ext/qiita_marker/autolink.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_GFM_AUTOLINK_H 2 | #define CMARK_GFM_AUTOLINK_H 3 | 4 | #include "cmark-gfm-core-extensions.h" 5 | 6 | cmark_syntax_extension *create_autolink_extension(void); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /ext/qiita_marker/buffer.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_BUFFER_H 2 | #define CMARK_BUFFER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "config.h" 10 | #include "cmark-gfm.h" 11 | 12 | #ifdef __cplusplus 13 | extern "C" { 14 | #endif 15 | 16 | typedef struct { 17 | cmark_mem *mem; 18 | unsigned char *ptr; 19 | bufsize_t asize, size; 20 | } cmark_strbuf; 21 | 22 | extern unsigned char cmark_strbuf__initbuf[]; 23 | 24 | #define CMARK_BUF_INIT(mem) \ 25 | { mem, cmark_strbuf__initbuf, 0, 0 } 26 | 27 | /** 28 | * Initialize a cmark_strbuf structure. 29 | * 30 | * For the cases where CMARK_BUF_INIT cannot be used to do static 31 | * initialization. 32 | */ 33 | CMARK_GFM_EXPORT 34 | void cmark_strbuf_init(cmark_mem *mem, cmark_strbuf *buf, 35 | bufsize_t initial_size); 36 | 37 | /** 38 | * Grow the buffer to hold at least `target_size` bytes. 39 | */ 40 | CMARK_GFM_EXPORT 41 | void cmark_strbuf_grow(cmark_strbuf *buf, bufsize_t target_size); 42 | 43 | CMARK_GFM_EXPORT 44 | void cmark_strbuf_free(cmark_strbuf *buf); 45 | 46 | CMARK_GFM_EXPORT 47 | void cmark_strbuf_swap(cmark_strbuf *buf_a, cmark_strbuf *buf_b); 48 | 49 | CMARK_GFM_EXPORT 50 | bufsize_t cmark_strbuf_len(const cmark_strbuf *buf); 51 | 52 | CMARK_GFM_EXPORT 53 | int cmark_strbuf_cmp(const cmark_strbuf *a, const cmark_strbuf *b); 54 | 55 | CMARK_GFM_EXPORT 56 | unsigned char *cmark_strbuf_detach(cmark_strbuf *buf); 57 | 58 | CMARK_GFM_EXPORT 59 | void cmark_strbuf_copy_cstr(char *data, bufsize_t datasize, 60 | const cmark_strbuf *buf); 61 | 62 | static CMARK_INLINE const char *cmark_strbuf_cstr(const cmark_strbuf *buf) { 63 | return (char *)buf->ptr; 64 | } 65 | 66 | #define cmark_strbuf_at(buf, n) ((buf)->ptr[n]) 67 | 68 | CMARK_GFM_EXPORT 69 | void cmark_strbuf_set(cmark_strbuf *buf, const unsigned char *data, 70 | bufsize_t len); 71 | 72 | CMARK_GFM_EXPORT 73 | void cmark_strbuf_sets(cmark_strbuf *buf, const char *string); 74 | 75 | CMARK_GFM_EXPORT 76 | void cmark_strbuf_putc(cmark_strbuf *buf, int c); 77 | 78 | CMARK_GFM_EXPORT 79 | void cmark_strbuf_put(cmark_strbuf *buf, const unsigned char *data, 80 | bufsize_t len); 81 | 82 | CMARK_GFM_EXPORT 83 | void cmark_strbuf_puts(cmark_strbuf *buf, const char *string); 84 | 85 | CMARK_GFM_EXPORT 86 | void cmark_strbuf_clear(cmark_strbuf *buf); 87 | 88 | CMARK_GFM_EXPORT 89 | bufsize_t cmark_strbuf_strchr(const cmark_strbuf *buf, int c, bufsize_t pos); 90 | 91 | CMARK_GFM_EXPORT 92 | bufsize_t cmark_strbuf_strrchr(const cmark_strbuf *buf, int c, bufsize_t pos); 93 | 94 | CMARK_GFM_EXPORT 95 | void cmark_strbuf_drop(cmark_strbuf *buf, bufsize_t n); 96 | 97 | CMARK_GFM_EXPORT 98 | void cmark_strbuf_truncate(cmark_strbuf *buf, bufsize_t len); 99 | 100 | CMARK_GFM_EXPORT 101 | void cmark_strbuf_rtrim(cmark_strbuf *buf); 102 | 103 | CMARK_GFM_EXPORT 104 | void cmark_strbuf_trim(cmark_strbuf *buf); 105 | 106 | CMARK_GFM_EXPORT 107 | void cmark_strbuf_normalize_whitespace(cmark_strbuf *s); 108 | 109 | CMARK_GFM_EXPORT 110 | void cmark_strbuf_unescape(cmark_strbuf *s); 111 | 112 | #ifdef __cplusplus 113 | } 114 | #endif 115 | 116 | #endif 117 | -------------------------------------------------------------------------------- /ext/qiita_marker/chunk.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_CHUNK_H 2 | #define CMARK_CHUNK_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "cmark-gfm.h" 8 | #include "buffer.h" 9 | #include "cmark_ctype.h" 10 | 11 | #define CMARK_CHUNK_EMPTY \ 12 | { NULL, 0, 0 } 13 | 14 | typedef struct cmark_chunk { 15 | unsigned char *data; 16 | bufsize_t len; 17 | bufsize_t alloc; // also implies a NULL-terminated string 18 | } cmark_chunk; 19 | 20 | static CMARK_INLINE void cmark_chunk_free(cmark_mem *mem, cmark_chunk *c) { 21 | if (c->alloc) 22 | mem->free(c->data); 23 | 24 | c->data = NULL; 25 | c->alloc = 0; 26 | c->len = 0; 27 | } 28 | 29 | static CMARK_INLINE void cmark_chunk_ltrim(cmark_chunk *c) { 30 | assert(!c->alloc); 31 | 32 | while (c->len && cmark_isspace(c->data[0])) { 33 | c->data++; 34 | c->len--; 35 | } 36 | } 37 | 38 | static CMARK_INLINE void cmark_chunk_rtrim(cmark_chunk *c) { 39 | assert(!c->alloc); 40 | 41 | while (c->len > 0) { 42 | if (!cmark_isspace(c->data[c->len - 1])) 43 | break; 44 | 45 | c->len--; 46 | } 47 | } 48 | 49 | static CMARK_INLINE void cmark_chunk_trim(cmark_chunk *c) { 50 | cmark_chunk_ltrim(c); 51 | cmark_chunk_rtrim(c); 52 | } 53 | 54 | static CMARK_INLINE bufsize_t cmark_chunk_strchr(cmark_chunk *ch, int c, 55 | bufsize_t offset) { 56 | const unsigned char *p = 57 | (unsigned char *)memchr(ch->data + offset, c, ch->len - offset); 58 | return p ? (bufsize_t)(p - ch->data) : ch->len; 59 | } 60 | 61 | static CMARK_INLINE const char *cmark_chunk_to_cstr(cmark_mem *mem, 62 | cmark_chunk *c) { 63 | unsigned char *str; 64 | 65 | if (c->alloc) { 66 | return (char *)c->data; 67 | } 68 | str = (unsigned char *)mem->calloc(c->len + 1, 1); 69 | if (c->len > 0) { 70 | memcpy(str, c->data, c->len); 71 | } 72 | str[c->len] = 0; 73 | c->data = str; 74 | c->alloc = 1; 75 | 76 | return (char *)str; 77 | } 78 | 79 | static CMARK_INLINE void cmark_chunk_set_cstr(cmark_mem *mem, cmark_chunk *c, 80 | const char *str) { 81 | unsigned char *old = c->alloc ? c->data : NULL; 82 | if (str == NULL) { 83 | c->len = 0; 84 | c->data = NULL; 85 | c->alloc = 0; 86 | } else { 87 | c->len = (bufsize_t)strlen(str); 88 | c->data = (unsigned char *)mem->calloc(c->len + 1, 1); 89 | c->alloc = 1; 90 | memcpy(c->data, str, c->len + 1); 91 | } 92 | if (old != NULL) { 93 | mem->free(old); 94 | } 95 | } 96 | 97 | static CMARK_INLINE cmark_chunk cmark_chunk_literal(const char *data) { 98 | bufsize_t len = data ? (bufsize_t)strlen(data) : 0; 99 | cmark_chunk c = {(unsigned char *)data, len, 0}; 100 | return c; 101 | } 102 | 103 | static CMARK_INLINE cmark_chunk cmark_chunk_dup(const cmark_chunk *ch, 104 | bufsize_t pos, bufsize_t len) { 105 | cmark_chunk c = {ch->data + pos, len, 0}; 106 | return c; 107 | } 108 | 109 | static CMARK_INLINE cmark_chunk cmark_chunk_buf_detach(cmark_strbuf *buf) { 110 | cmark_chunk c; 111 | 112 | c.len = buf->size; 113 | c.data = cmark_strbuf_detach(buf); 114 | c.alloc = 1; 115 | 116 | return c; 117 | } 118 | 119 | /* trim_new variants are to be used when the source chunk may or may not be 120 | * allocated; forces a newly allocated chunk. */ 121 | static CMARK_INLINE cmark_chunk cmark_chunk_ltrim_new(cmark_mem *mem, cmark_chunk *c) { 122 | cmark_chunk r = cmark_chunk_dup(c, 0, c->len); 123 | cmark_chunk_ltrim(&r); 124 | cmark_chunk_to_cstr(mem, &r); 125 | return r; 126 | } 127 | 128 | static CMARK_INLINE cmark_chunk cmark_chunk_rtrim_new(cmark_mem *mem, cmark_chunk *c) { 129 | cmark_chunk r = cmark_chunk_dup(c, 0, c->len); 130 | cmark_chunk_rtrim(&r); 131 | cmark_chunk_to_cstr(mem, &r); 132 | return r; 133 | } 134 | 135 | #endif 136 | -------------------------------------------------------------------------------- /ext/qiita_marker/cmark-gfm-core-extensions.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_GFM_CORE_EXTENSIONS_H 2 | #define CMARK_GFM_CORE_EXTENSIONS_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include "cmark-gfm-extension_api.h" 9 | #include "cmark-gfm_export.h" 10 | #include 11 | #include 12 | 13 | CMARK_GFM_EXPORT 14 | void cmark_gfm_core_extensions_ensure_registered(void); 15 | 16 | CMARK_GFM_EXPORT 17 | uint16_t cmark_gfm_extensions_get_table_columns(cmark_node *node); 18 | 19 | /** Sets the number of columns for the table, returning 1 on success and 0 on error. 20 | */ 21 | CMARK_GFM_EXPORT 22 | int cmark_gfm_extensions_set_table_columns(cmark_node *node, uint16_t n_columns); 23 | 24 | CMARK_GFM_EXPORT 25 | uint8_t *cmark_gfm_extensions_get_table_alignments(cmark_node *node); 26 | 27 | /** Sets the alignments for the table, returning 1 on success and 0 on error. 28 | */ 29 | CMARK_GFM_EXPORT 30 | int cmark_gfm_extensions_set_table_alignments(cmark_node *node, uint16_t ncols, uint8_t *alignments); 31 | 32 | CMARK_GFM_EXPORT 33 | int cmark_gfm_extensions_get_table_row_is_header(cmark_node *node); 34 | 35 | /** Sets whether the node is a table header row, returning 1 on success and 0 on error. 36 | */ 37 | CMARK_GFM_EXPORT 38 | int cmark_gfm_extensions_set_table_row_is_header(cmark_node *node, int is_header); 39 | 40 | CMARK_GFM_EXPORT 41 | bool cmark_gfm_extensions_get_tasklist_item_checked(cmark_node *node); 42 | /* For backwards compatibility */ 43 | #define cmark_gfm_extensions_tasklist_is_checked cmark_gfm_extensions_get_tasklist_item_checked 44 | 45 | /** Sets whether a tasklist item is "checked" (completed), returning 1 on success and 0 on error. 46 | */ 47 | CMARK_GFM_EXPORT 48 | int cmark_gfm_extensions_set_tasklist_item_checked(cmark_node *node, bool is_checked); 49 | 50 | #ifdef __cplusplus 51 | } 52 | #endif 53 | 54 | #endif 55 | -------------------------------------------------------------------------------- /ext/qiita_marker/cmark-gfm-extensions_export.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef CMARK_GFM_EXTENSIONS_EXPORT_H 3 | #define CMARK_GFM_EXTENSIONS_EXPORT_H 4 | 5 | #ifdef CMARK_GFM_EXTENSIONS_STATIC_DEFINE 6 | # define CMARK_GFM_EXTENSIONS_EXPORT 7 | # define CMARK_GFM_EXTENSIONS_NO_EXPORT 8 | #else 9 | # ifndef CMARK_GFM_EXTENSIONS_EXPORT 10 | # ifdef libcmark_gfm_extensions_EXPORTS 11 | /* We are building this library */ 12 | # define CMARK_GFM_EXTENSIONS_EXPORT __attribute__((visibility("default"))) 13 | # else 14 | /* We are using this library */ 15 | # define CMARK_GFM_EXTENSIONS_EXPORT __attribute__((visibility("default"))) 16 | # endif 17 | # endif 18 | 19 | # ifndef CMARK_GFM_EXTENSIONS_NO_EXPORT 20 | # define CMARK_GFM_EXTENSIONS_NO_EXPORT __attribute__((visibility("hidden"))) 21 | # endif 22 | #endif 23 | 24 | #ifndef CMARK_GFM_EXTENSIONS_DEPRECATED 25 | # define CMARK_GFM_EXTENSIONS_DEPRECATED __attribute__ ((__deprecated__)) 26 | #endif 27 | 28 | #ifndef CMARK_GFM_EXTENSIONS_DEPRECATED_EXPORT 29 | # define CMARK_GFM_EXTENSIONS_DEPRECATED_EXPORT CMARK_GFM_EXTENSIONS_EXPORT CMARK_GFM_EXTENSIONS_DEPRECATED 30 | #endif 31 | 32 | #ifndef CMARK_GFM_EXTENSIONS_DEPRECATED_NO_EXPORT 33 | # define CMARK_GFM_EXTENSIONS_DEPRECATED_NO_EXPORT CMARK_GFM_EXTENSIONS_NO_EXPORT CMARK_GFM_EXTENSIONS_DEPRECATED 34 | #endif 35 | 36 | #if 0 /* DEFINE_NO_DEPRECATED */ 37 | # ifndef CMARK_GFM_EXTENSIONS_NO_DEPRECATED 38 | # define CMARK_GFM_EXTENSIONS_NO_DEPRECATED 39 | # endif 40 | #endif 41 | 42 | #endif /* CMARK_GFM_EXTENSIONS_EXPORT_H */ 43 | -------------------------------------------------------------------------------- /ext/qiita_marker/cmark-gfm_export.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef CMARK_GFM_EXPORT_H 3 | #define CMARK_GFM_EXPORT_H 4 | 5 | #ifdef CMARK_GFM_STATIC_DEFINE 6 | # define CMARK_GFM_EXPORT 7 | # define CMARK_GFM_NO_EXPORT 8 | #else 9 | # ifndef CMARK_GFM_EXPORT 10 | # ifdef libcmark_gfm_EXPORTS 11 | /* We are building this library */ 12 | # define CMARK_GFM_EXPORT __attribute__((visibility("default"))) 13 | # else 14 | /* We are using this library */ 15 | # define CMARK_GFM_EXPORT __attribute__((visibility("default"))) 16 | # endif 17 | # endif 18 | 19 | # ifndef CMARK_GFM_NO_EXPORT 20 | # define CMARK_GFM_NO_EXPORT __attribute__((visibility("hidden"))) 21 | # endif 22 | #endif 23 | 24 | #ifndef CMARK_GFM_DEPRECATED 25 | # define CMARK_GFM_DEPRECATED __attribute__ ((__deprecated__)) 26 | #endif 27 | 28 | #ifndef CMARK_GFM_DEPRECATED_EXPORT 29 | # define CMARK_GFM_DEPRECATED_EXPORT CMARK_GFM_EXPORT CMARK_GFM_DEPRECATED 30 | #endif 31 | 32 | #ifndef CMARK_GFM_DEPRECATED_NO_EXPORT 33 | # define CMARK_GFM_DEPRECATED_NO_EXPORT CMARK_GFM_NO_EXPORT CMARK_GFM_DEPRECATED 34 | #endif 35 | 36 | #if 0 /* DEFINE_NO_DEPRECATED */ 37 | # ifndef CMARK_GFM_NO_DEPRECATED 38 | # define CMARK_GFM_NO_DEPRECATED 39 | # endif 40 | #endif 41 | 42 | #endif /* CMARK_GFM_EXPORT_H */ 43 | -------------------------------------------------------------------------------- /ext/qiita_marker/cmark-gfm_version.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_GFM_VERSION_H 2 | #define CMARK_GFM_VERSION_H 3 | 4 | #define CMARK_GFM_VERSION ((0 << 24) | (29 << 16) | (0 << 8) | 11) 5 | #define CMARK_GFM_VERSION_STRING "0.29.0.gfm.11" 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /ext/qiita_marker/cmark.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "registry.h" 5 | #include "node.h" 6 | #include "houdini.h" 7 | #include "cmark-gfm.h" 8 | #include "buffer.h" 9 | 10 | cmark_node_type CMARK_NODE_LAST_BLOCK = CMARK_NODE_FOOTNOTE_DEFINITION; 11 | cmark_node_type CMARK_NODE_LAST_INLINE = CMARK_NODE_FOOTNOTE_REFERENCE; 12 | 13 | int cmark_version(void) { return CMARK_GFM_VERSION; } 14 | 15 | const char *cmark_version_string(void) { return CMARK_GFM_VERSION_STRING; } 16 | 17 | static void *xcalloc(size_t nmem, size_t size) { 18 | void *ptr = calloc(nmem, size); 19 | if (!ptr) { 20 | fprintf(stderr, "[cmark] calloc returned null pointer, aborting\n"); 21 | abort(); 22 | } 23 | return ptr; 24 | } 25 | 26 | static void *xrealloc(void *ptr, size_t size) { 27 | void *new_ptr = realloc(ptr, size); 28 | if (!new_ptr) { 29 | fprintf(stderr, "[cmark] realloc returned null pointer, aborting\n"); 30 | abort(); 31 | } 32 | return new_ptr; 33 | } 34 | 35 | static void xfree(void *ptr) { 36 | free(ptr); 37 | } 38 | 39 | cmark_mem CMARK_DEFAULT_MEM_ALLOCATOR = {xcalloc, xrealloc, xfree}; 40 | 41 | cmark_mem *cmark_get_default_mem_allocator(void) { 42 | return &CMARK_DEFAULT_MEM_ALLOCATOR; 43 | } 44 | 45 | char *cmark_markdown_to_html(const char *text, size_t len, int options) { 46 | cmark_node *doc; 47 | char *result; 48 | 49 | doc = cmark_parse_document(text, len, options); 50 | 51 | result = cmark_render_html(doc, options, NULL); 52 | cmark_node_free(doc); 53 | 54 | return result; 55 | } 56 | -------------------------------------------------------------------------------- /ext/qiita_marker/cmark_ctype.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "cmark_ctype.h" 4 | 5 | /** 1 = space, 2 = punct, 3 = digit, 4 = alpha, 0 = other 6 | */ 7 | static const uint8_t cmark_ctype_class[256] = { 8 | /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ 9 | /* 0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 10 | /* 1 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11 | /* 2 */ 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 12 | /* 3 */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 13 | /* 4 */ 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 14 | /* 5 */ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 2, 2, 2, 2, 2, 15 | /* 6 */ 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 16 | /* 7 */ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 2, 2, 2, 2, 0, 17 | /* 8 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18 | /* 9 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19 | /* a */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20 | /* b */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 21 | /* c */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22 | /* d */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23 | /* e */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24 | /* f */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 25 | 26 | /** 27 | * Returns 1 if c is a "whitespace" character as defined by the spec. 28 | */ 29 | int cmark_isspace(char c) { return cmark_ctype_class[(uint8_t)c] == 1; } 30 | 31 | /** 32 | * Returns 1 if c is an ascii punctuation character. 33 | */ 34 | int cmark_ispunct(char c) { return cmark_ctype_class[(uint8_t)c] == 2; } 35 | 36 | int cmark_isalnum(char c) { 37 | uint8_t result; 38 | result = cmark_ctype_class[(uint8_t)c]; 39 | return (result == 3 || result == 4); 40 | } 41 | 42 | int cmark_isdigit(char c) { return cmark_ctype_class[(uint8_t)c] == 3; } 43 | 44 | int cmark_isalpha(char c) { return cmark_ctype_class[(uint8_t)c] == 4; } 45 | -------------------------------------------------------------------------------- /ext/qiita_marker/cmark_ctype.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_CMARK_CTYPE_H 2 | #define CMARK_CMARK_CTYPE_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include "cmark-gfm_export.h" 9 | 10 | /** Locale-independent versions of functions from ctype.h. 11 | * We want cmark to behave the same no matter what the system locale. 12 | */ 13 | 14 | CMARK_GFM_EXPORT 15 | int cmark_isspace(char c); 16 | 17 | CMARK_GFM_EXPORT 18 | int cmark_ispunct(char c); 19 | 20 | CMARK_GFM_EXPORT 21 | int cmark_isalnum(char c); 22 | 23 | CMARK_GFM_EXPORT 24 | int cmark_isdigit(char c); 25 | 26 | CMARK_GFM_EXPORT 27 | int cmark_isalpha(char c); 28 | 29 | #ifdef __cplusplus 30 | } 31 | #endif 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /ext/qiita_marker/config.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_CONFIG_H 2 | #define CMARK_CONFIG_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #define HAVE_STDBOOL_H 9 | 10 | #ifdef HAVE_STDBOOL_H 11 | #include 12 | #elif !defined(__cplusplus) 13 | typedef char bool; 14 | #endif 15 | 16 | #define HAVE___BUILTIN_EXPECT 17 | 18 | #define HAVE___ATTRIBUTE__ 19 | 20 | #ifdef HAVE___ATTRIBUTE__ 21 | #define CMARK_ATTRIBUTE(list) __attribute__ (list) 22 | #else 23 | #define CMARK_ATTRIBUTE(list) 24 | #endif 25 | 26 | #ifndef CMARK_INLINE 27 | #if defined(_MSC_VER) && !defined(__cplusplus) 28 | #define CMARK_INLINE __inline 29 | #else 30 | #define CMARK_INLINE inline 31 | #endif 32 | #endif 33 | 34 | /* snprintf and vsnprintf fallbacks for MSVC before 2015, 35 | due to Valentin Milea http://stackoverflow.com/questions/2915672/ 36 | */ 37 | 38 | #if defined(_MSC_VER) && _MSC_VER < 1900 39 | 40 | #include 41 | #include 42 | 43 | #define snprintf c99_snprintf 44 | #define vsnprintf c99_vsnprintf 45 | 46 | CMARK_INLINE int c99_vsnprintf(char *outBuf, size_t size, const char *format, va_list ap) 47 | { 48 | int count = -1; 49 | 50 | if (size != 0) 51 | count = _vsnprintf_s(outBuf, size, _TRUNCATE, format, ap); 52 | if (count == -1) 53 | count = _vscprintf(format, ap); 54 | 55 | return count; 56 | } 57 | 58 | CMARK_INLINE int c99_snprintf(char *outBuf, size_t size, const char *format, ...) 59 | { 60 | int count; 61 | va_list ap; 62 | 63 | va_start(ap, format); 64 | count = c99_vsnprintf(outBuf, size, format, ap); 65 | va_end(ap); 66 | 67 | return count; 68 | } 69 | 70 | #endif 71 | 72 | #ifdef __cplusplus 73 | } 74 | #endif 75 | 76 | #endif 77 | -------------------------------------------------------------------------------- /ext/qiita_marker/core-extensions.c: -------------------------------------------------------------------------------- 1 | #include "cmark-gfm-core-extensions.h" 2 | #include "autolink.h" 3 | #include "strikethrough.h" 4 | #include "table.h" 5 | #include "tagfilter.h" 6 | #include "tasklist.h" 7 | #include "registry.h" 8 | #include "plugin.h" 9 | #include "qfm_custom_block.h" 10 | 11 | static int core_extensions_registration(cmark_plugin *plugin) { 12 | cmark_plugin_register_syntax_extension(plugin, create_table_extension()); 13 | cmark_plugin_register_syntax_extension(plugin, 14 | create_strikethrough_extension()); 15 | cmark_plugin_register_syntax_extension(plugin, create_autolink_extension()); 16 | cmark_plugin_register_syntax_extension(plugin, create_tagfilter_extension()); 17 | cmark_plugin_register_syntax_extension(plugin, create_tasklist_extension()); 18 | cmark_plugin_register_syntax_extension(plugin, create_qfm_custom_block_extension()); 19 | return 1; 20 | } 21 | 22 | void cmark_gfm_core_extensions_ensure_registered(void) { 23 | static int registered = 0; 24 | 25 | if (!registered) { 26 | cmark_register_plugin(core_extensions_registration); 27 | registered = 1; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ext/qiita_marker/ext_scanners.h: -------------------------------------------------------------------------------- 1 | #include "chunk.h" 2 | #include "cmark-gfm.h" 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | bufsize_t _ext_scan_at(bufsize_t (*scanner)(const unsigned char *), 9 | unsigned char *ptr, int len, bufsize_t offset); 10 | bufsize_t _scan_table_start(const unsigned char *p); 11 | bufsize_t _scan_table_cell(const unsigned char *p); 12 | bufsize_t _scan_table_cell_end(const unsigned char *p); 13 | bufsize_t _scan_table_row_end(const unsigned char *p); 14 | bufsize_t _scan_tasklist(const unsigned char *p); 15 | 16 | #define scan_table_start(c, l, n) _ext_scan_at(&_scan_table_start, c, l, n) 17 | #define scan_table_cell(c, l, n) _ext_scan_at(&_scan_table_cell, c, l, n) 18 | #define scan_table_cell_end(c, l, n) _ext_scan_at(&_scan_table_cell_end, c, l, n) 19 | #define scan_table_row_end(c, l, n) _ext_scan_at(&_scan_table_row_end, c, l, n) 20 | #define scan_tasklist(c, l, n) _ext_scan_at(&_scan_tasklist, c, l, n) 21 | 22 | #ifdef __cplusplus 23 | } 24 | #endif 25 | -------------------------------------------------------------------------------- /ext/qiita_marker/extconf.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'mkmf' 4 | 5 | $CFLAGS << ' -std=c99' 6 | 7 | create_makefile('qiita_marker/qiita_marker') 8 | -------------------------------------------------------------------------------- /ext/qiita_marker/footnotes.c: -------------------------------------------------------------------------------- 1 | #include "cmark-gfm.h" 2 | #include "parser.h" 3 | #include "footnotes.h" 4 | #include "inlines.h" 5 | #include "chunk.h" 6 | 7 | static void footnote_free(cmark_map *map, cmark_map_entry *_ref) { 8 | cmark_footnote *ref = (cmark_footnote *)_ref; 9 | cmark_mem *mem = map->mem; 10 | if (ref != NULL) { 11 | mem->free(ref->entry.label); 12 | if (ref->node) 13 | cmark_node_free(ref->node); 14 | mem->free(ref); 15 | } 16 | } 17 | 18 | void cmark_footnote_create(cmark_map *map, cmark_node *node) { 19 | cmark_footnote *ref; 20 | unsigned char *reflabel = normalize_map_label(map->mem, &node->as.literal); 21 | 22 | /* empty footnote name, or composed from only whitespace */ 23 | if (reflabel == NULL) 24 | return; 25 | 26 | assert(map->sorted == NULL); 27 | 28 | ref = (cmark_footnote *)map->mem->calloc(1, sizeof(*ref)); 29 | ref->entry.label = reflabel; 30 | ref->node = node; 31 | ref->entry.age = map->size; 32 | ref->entry.next = map->refs; 33 | 34 | map->refs = (cmark_map_entry *)ref; 35 | map->size++; 36 | } 37 | 38 | cmark_map *cmark_footnote_map_new(cmark_mem *mem) { 39 | return cmark_map_new(mem, footnote_free); 40 | } 41 | 42 | // Before calling `cmark_map_free` on a map with `cmark_footnotes`, first 43 | // unlink all of the footnote nodes before freeing their memory. 44 | // 45 | // Sometimes, two (unused) footnote nodes can end up referencing each other, 46 | // which as they get freed up by calling `cmark_map_free` -> `footnote_free` -> 47 | // etc, can lead to a use-after-free error. 48 | // 49 | // Better to `unlink` every footnote node first, setting their next, prev, and 50 | // parent pointers to NULL, and only then walk thru & free them up. 51 | void cmark_unlink_footnotes_map(cmark_map *map) { 52 | cmark_map_entry *ref; 53 | cmark_map_entry *next; 54 | 55 | ref = map->refs; 56 | while(ref) { 57 | next = ref->next; 58 | if (((cmark_footnote *)ref)->node) { 59 | cmark_node_unlink(((cmark_footnote *)ref)->node); 60 | } 61 | ref = next; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /ext/qiita_marker/footnotes.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_FOOTNOTES_H 2 | #define CMARK_FOOTNOTES_H 3 | 4 | #include "map.h" 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | struct cmark_footnote { 11 | cmark_map_entry entry; 12 | cmark_node *node; 13 | unsigned int ix; 14 | }; 15 | 16 | typedef struct cmark_footnote cmark_footnote; 17 | 18 | void cmark_footnote_create(cmark_map *map, cmark_node *node); 19 | cmark_map *cmark_footnote_map_new(cmark_mem *mem); 20 | 21 | void cmark_unlink_footnotes_map(cmark_map *map); 22 | 23 | #ifdef __cplusplus 24 | } 25 | #endif 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /ext/qiita_marker/houdini.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_HOUDINI_H 2 | #define CMARK_HOUDINI_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include 9 | #include "config.h" 10 | #include "buffer.h" 11 | 12 | #ifdef HAVE___BUILTIN_EXPECT 13 | #define likely(x) __builtin_expect((x), 1) 14 | #define unlikely(x) __builtin_expect((x), 0) 15 | #else 16 | #define likely(x) (x) 17 | #define unlikely(x) (x) 18 | #endif 19 | 20 | #ifdef HOUDINI_USE_LOCALE 21 | #define _isxdigit(c) isxdigit(c) 22 | #define _isdigit(c) isdigit(c) 23 | #else 24 | /* 25 | * Helper _isdigit methods -- do not trust the current locale 26 | * */ 27 | #define _isxdigit(c) (strchr("0123456789ABCDEFabcdef", (c)) != NULL) 28 | #define _isdigit(c) ((c) >= '0' && (c) <= '9') 29 | #endif 30 | 31 | #define HOUDINI_ESCAPED_SIZE(x) (((x)*12) / 10) 32 | #define HOUDINI_UNESCAPED_SIZE(x) (x) 33 | 34 | CMARK_GFM_EXPORT 35 | bufsize_t houdini_unescape_ent(cmark_strbuf *ob, const uint8_t *src, 36 | bufsize_t size); 37 | CMARK_GFM_EXPORT 38 | int houdini_escape_html(cmark_strbuf *ob, const uint8_t *src, 39 | bufsize_t size); 40 | CMARK_GFM_EXPORT 41 | int houdini_escape_html0(cmark_strbuf *ob, const uint8_t *src, 42 | bufsize_t size, int secure); 43 | CMARK_GFM_EXPORT 44 | int houdini_unescape_html(cmark_strbuf *ob, const uint8_t *src, 45 | bufsize_t size); 46 | CMARK_GFM_EXPORT 47 | void houdini_unescape_html_f(cmark_strbuf *ob, const uint8_t *src, 48 | bufsize_t size); 49 | CMARK_GFM_EXPORT 50 | int houdini_escape_href(cmark_strbuf *ob, const uint8_t *src, 51 | bufsize_t size); 52 | 53 | #ifdef __cplusplus 54 | } 55 | #endif 56 | 57 | #endif 58 | -------------------------------------------------------------------------------- /ext/qiita_marker/houdini_href_e.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "houdini.h" 6 | 7 | /* 8 | * The following characters will not be escaped: 9 | * 10 | * -_.+!*'(),%#@?=;:/,+&$~ alphanum 11 | * 12 | * Note that this character set is the addition of: 13 | * 14 | * - The characters which are safe to be in an URL 15 | * - The characters which are *not* safe to be in 16 | * an URL because they are RESERVED characters. 17 | * 18 | * We assume (lazily) that any RESERVED char that 19 | * appears inside an URL is actually meant to 20 | * have its native function (i.e. as an URL 21 | * component/separator) and hence needs no escaping. 22 | * 23 | * There are two exceptions: the chacters & (amp) 24 | * and ' (single quote) do not appear in the table. 25 | * They are meant to appear in the URL as components, 26 | * yet they require special HTML-entity escaping 27 | * to generate valid HTML markup. 28 | * 29 | * All other characters will be escaped to %XX. 30 | * 31 | */ 32 | static const char HREF_SAFE[] = { 33 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 35 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 36 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 37 | 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 38 | 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 39 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 41 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44 | }; 45 | 46 | int houdini_escape_href(cmark_strbuf *ob, const uint8_t *src, bufsize_t size) { 47 | static const uint8_t hex_chars[] = "0123456789ABCDEF"; 48 | bufsize_t i = 0, org; 49 | uint8_t hex_str[3]; 50 | 51 | hex_str[0] = '%'; 52 | 53 | while (i < size) { 54 | org = i; 55 | while (i < size && HREF_SAFE[src[i]] != 0) 56 | i++; 57 | 58 | if (likely(i > org)) 59 | cmark_strbuf_put(ob, src + org, i - org); 60 | 61 | /* escaping */ 62 | if (i >= size) 63 | break; 64 | 65 | switch (src[i]) { 66 | /* amp appears all the time in URLs, but needs 67 | * HTML-entity escaping to be inside an href */ 68 | case '&': 69 | cmark_strbuf_puts(ob, "&"); 70 | break; 71 | 72 | /* the single quote is a valid URL character 73 | * according to the standard; it needs HTML 74 | * entity escaping too */ 75 | case '\'': 76 | cmark_strbuf_puts(ob, "'"); 77 | break; 78 | 79 | /* the space can be escaped to %20 or a plus 80 | * sign. we're going with the generic escape 81 | * for now. the plus thing is more commonly seen 82 | * when building GET strings */ 83 | #if 0 84 | case ' ': 85 | cmark_strbuf_putc(ob, '+'); 86 | break; 87 | #endif 88 | 89 | /* every other character goes with a %XX escaping */ 90 | default: 91 | hex_str[1] = hex_chars[(src[i] >> 4) & 0xF]; 92 | hex_str[2] = hex_chars[src[i] & 0xF]; 93 | cmark_strbuf_put(ob, hex_str, 3); 94 | } 95 | 96 | i++; 97 | } 98 | 99 | return 1; 100 | } 101 | -------------------------------------------------------------------------------- /ext/qiita_marker/houdini_html_e.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "houdini.h" 6 | 7 | /** 8 | * According to the OWASP rules: 9 | * 10 | * & --> & 11 | * < --> < 12 | * > --> > 13 | * " --> " 14 | * ' --> ' ' is not recommended 15 | * / --> / forward slash is included as it helps end an HTML entity 16 | * 17 | */ 18 | static const char HTML_ESCAPE_TABLE[] = { 19 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 4, 21 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 25 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 26 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30 | }; 31 | 32 | static const char *HTML_ESCAPES[] = {"", """, "&", "'", 33 | "/", "<", ">"}; 34 | 35 | int houdini_escape_html0(cmark_strbuf *ob, const uint8_t *src, bufsize_t size, 36 | int secure) { 37 | bufsize_t i = 0, org, esc = 0; 38 | 39 | while (i < size) { 40 | org = i; 41 | while (i < size && (esc = HTML_ESCAPE_TABLE[src[i]]) == 0) 42 | i++; 43 | 44 | if (i > org) 45 | cmark_strbuf_put(ob, src + org, i - org); 46 | 47 | /* escaping */ 48 | if (unlikely(i >= size)) 49 | break; 50 | 51 | /* The forward slash and single quote are only escaped in secure mode */ 52 | if ((src[i] == '/' || src[i] == '\'') && !secure) { 53 | cmark_strbuf_putc(ob, src[i]); 54 | } else { 55 | cmark_strbuf_puts(ob, HTML_ESCAPES[esc]); 56 | } 57 | 58 | i++; 59 | } 60 | 61 | return 1; 62 | } 63 | 64 | int houdini_escape_html(cmark_strbuf *ob, const uint8_t *src, bufsize_t size) { 65 | return houdini_escape_html0(ob, src, size, 1); 66 | } 67 | -------------------------------------------------------------------------------- /ext/qiita_marker/houdini_html_u.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "buffer.h" 6 | #include "houdini.h" 7 | #include "utf8.h" 8 | #include "entities.inc" 9 | 10 | /* Binary tree lookup code for entities added by JGM */ 11 | 12 | static const unsigned char *S_lookup(int i, int low, int hi, 13 | const unsigned char *s, int len) { 14 | int j; 15 | int cmp = 16 | strncmp((const char *)s, (const char *)cmark_entities[i].entity, len); 17 | if (cmp == 0 && cmark_entities[i].entity[len] == 0) { 18 | return (const unsigned char *)cmark_entities[i].bytes; 19 | } else if (cmp <= 0 && i > low) { 20 | j = i - ((i - low) / 2); 21 | if (j == i) 22 | j -= 1; 23 | return S_lookup(j, low, i - 1, s, len); 24 | } else if (cmp > 0 && i < hi) { 25 | j = i + ((hi - i) / 2); 26 | if (j == i) 27 | j += 1; 28 | return S_lookup(j, i + 1, hi, s, len); 29 | } else { 30 | return NULL; 31 | } 32 | } 33 | 34 | static const unsigned char *S_lookup_entity(const unsigned char *s, int len) { 35 | return S_lookup(CMARK_NUM_ENTITIES / 2, 0, CMARK_NUM_ENTITIES - 1, s, len); 36 | } 37 | 38 | bufsize_t houdini_unescape_ent(cmark_strbuf *ob, const uint8_t *src, 39 | bufsize_t size) { 40 | bufsize_t i = 0; 41 | 42 | if (size >= 3 && src[0] == '#') { 43 | int codepoint = 0; 44 | int num_digits = 0; 45 | 46 | if (_isdigit(src[1])) { 47 | for (i = 1; i < size && _isdigit(src[i]); ++i) { 48 | codepoint = (codepoint * 10) + (src[i] - '0'); 49 | 50 | if (codepoint >= 0x110000) { 51 | // Keep counting digits but 52 | // avoid integer overflow. 53 | codepoint = 0x110000; 54 | } 55 | } 56 | 57 | num_digits = i - 1; 58 | } 59 | 60 | else if (src[1] == 'x' || src[1] == 'X') { 61 | for (i = 2; i < size && _isxdigit(src[i]); ++i) { 62 | codepoint = (codepoint * 16) + ((src[i] | 32) % 39 - 9); 63 | 64 | if (codepoint >= 0x110000) { 65 | // Keep counting digits but 66 | // avoid integer overflow. 67 | codepoint = 0x110000; 68 | } 69 | } 70 | 71 | num_digits = i - 2; 72 | } 73 | 74 | if (num_digits >= 1 && num_digits <= 8 && i < size && src[i] == ';') { 75 | if (codepoint == 0 || (codepoint >= 0xD800 && codepoint < 0xE000) || 76 | codepoint >= 0x110000) { 77 | codepoint = 0xFFFD; 78 | } 79 | cmark_utf8proc_encode_char(codepoint, ob); 80 | return i + 1; 81 | } 82 | } 83 | 84 | else { 85 | if (size > CMARK_ENTITY_MAX_LENGTH) 86 | size = CMARK_ENTITY_MAX_LENGTH; 87 | 88 | for (i = CMARK_ENTITY_MIN_LENGTH; i < size; ++i) { 89 | if (src[i] == ' ') 90 | break; 91 | 92 | if (src[i] == ';') { 93 | const unsigned char *entity = S_lookup_entity(src, i); 94 | 95 | if (entity != NULL) { 96 | cmark_strbuf_puts(ob, (const char *)entity); 97 | return i + 1; 98 | } 99 | 100 | break; 101 | } 102 | } 103 | } 104 | 105 | return 0; 106 | } 107 | 108 | int houdini_unescape_html(cmark_strbuf *ob, const uint8_t *src, 109 | bufsize_t size) { 110 | bufsize_t i = 0, org, ent; 111 | 112 | while (i < size) { 113 | org = i; 114 | while (i < size && src[i] != '&') 115 | i++; 116 | 117 | if (likely(i > org)) { 118 | if (unlikely(org == 0)) { 119 | if (i >= size) 120 | return 0; 121 | 122 | cmark_strbuf_grow(ob, HOUDINI_UNESCAPED_SIZE(size)); 123 | } 124 | 125 | cmark_strbuf_put(ob, src + org, i - org); 126 | } 127 | 128 | /* escaping */ 129 | if (i >= size) 130 | break; 131 | 132 | i++; 133 | 134 | ent = houdini_unescape_ent(ob, src + i, size - i); 135 | i += ent; 136 | 137 | /* not really an entity */ 138 | if (ent == 0) 139 | cmark_strbuf_putc(ob, '&'); 140 | } 141 | 142 | return 1; 143 | } 144 | 145 | void houdini_unescape_html_f(cmark_strbuf *ob, const uint8_t *src, 146 | bufsize_t size) { 147 | if (!houdini_unescape_html(ob, src, size)) 148 | cmark_strbuf_put(ob, src, size); 149 | } 150 | -------------------------------------------------------------------------------- /ext/qiita_marker/html.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_HTML_H 2 | #define CMARK_HTML_H 3 | 4 | #include "buffer.h" 5 | #include "node.h" 6 | 7 | CMARK_INLINE 8 | static void cmark_html_render_cr(cmark_strbuf *html) { 9 | if (html->size && html->ptr[html->size - 1] != '\n') 10 | cmark_strbuf_putc(html, '\n'); 11 | } 12 | 13 | #define BUFFER_SIZE 100 14 | 15 | CMARK_INLINE 16 | static void cmark_html_render_sourcepos(cmark_node *node, cmark_strbuf *html, int options) { 17 | char buffer[BUFFER_SIZE]; 18 | if (CMARK_OPT_SOURCEPOS & options) { 19 | snprintf(buffer, BUFFER_SIZE, " data-sourcepos=\"%d:%d-%d:%d\"", 20 | cmark_node_get_start_line(node), cmark_node_get_start_column(node), 21 | cmark_node_get_end_line(node), cmark_node_get_end_column(node)); 22 | cmark_strbuf_puts(html, buffer); 23 | } 24 | } 25 | 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /ext/qiita_marker/inlines.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_INLINES_H 2 | #define CMARK_INLINES_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include "references.h" 9 | 10 | cmark_chunk cmark_clean_url(cmark_mem *mem, cmark_chunk *url); 11 | cmark_chunk cmark_clean_title(cmark_mem *mem, cmark_chunk *title); 12 | 13 | CMARK_GFM_EXPORT 14 | void cmark_parse_inlines(cmark_parser *parser, 15 | cmark_node *parent, 16 | cmark_map *refmap, 17 | int options); 18 | 19 | bufsize_t cmark_parse_reference_inline(cmark_mem *mem, cmark_chunk *input, 20 | cmark_map *refmap); 21 | 22 | void cmark_inlines_add_special_character(unsigned char c, bool emphasis); 23 | void cmark_inlines_remove_special_character(unsigned char c, bool emphasis); 24 | 25 | #ifdef __cplusplus 26 | } 27 | #endif 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /ext/qiita_marker/iterator.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "config.h" 5 | #include "node.h" 6 | #include "cmark-gfm.h" 7 | #include "iterator.h" 8 | 9 | cmark_iter *cmark_iter_new(cmark_node *root) { 10 | if (root == NULL) { 11 | return NULL; 12 | } 13 | cmark_mem *mem = root->content.mem; 14 | cmark_iter *iter = (cmark_iter *)mem->calloc(1, sizeof(cmark_iter)); 15 | iter->mem = mem; 16 | iter->root = root; 17 | iter->cur.ev_type = CMARK_EVENT_NONE; 18 | iter->cur.node = NULL; 19 | iter->next.ev_type = CMARK_EVENT_ENTER; 20 | iter->next.node = root; 21 | return iter; 22 | } 23 | 24 | void cmark_iter_free(cmark_iter *iter) { iter->mem->free(iter); } 25 | 26 | static bool S_is_leaf(cmark_node *node) { 27 | switch (node->type) { 28 | case CMARK_NODE_HTML_BLOCK: 29 | case CMARK_NODE_THEMATIC_BREAK: 30 | case CMARK_NODE_CODE_BLOCK: 31 | case CMARK_NODE_TEXT: 32 | case CMARK_NODE_SOFTBREAK: 33 | case CMARK_NODE_LINEBREAK: 34 | case CMARK_NODE_CODE: 35 | case CMARK_NODE_HTML_INLINE: 36 | return 1; 37 | } 38 | return 0; 39 | } 40 | 41 | cmark_event_type cmark_iter_next(cmark_iter *iter) { 42 | cmark_event_type ev_type = iter->next.ev_type; 43 | cmark_node *node = iter->next.node; 44 | 45 | iter->cur.ev_type = ev_type; 46 | iter->cur.node = node; 47 | 48 | if (ev_type == CMARK_EVENT_DONE) { 49 | return ev_type; 50 | } 51 | 52 | /* roll forward to next item, setting both fields */ 53 | if (ev_type == CMARK_EVENT_ENTER && !S_is_leaf(node)) { 54 | if (node->first_child == NULL) { 55 | /* stay on this node but exit */ 56 | iter->next.ev_type = CMARK_EVENT_EXIT; 57 | } else { 58 | iter->next.ev_type = CMARK_EVENT_ENTER; 59 | iter->next.node = node->first_child; 60 | } 61 | } else if (node == iter->root) { 62 | /* don't move past root */ 63 | iter->next.ev_type = CMARK_EVENT_DONE; 64 | iter->next.node = NULL; 65 | } else if (node->next) { 66 | iter->next.ev_type = CMARK_EVENT_ENTER; 67 | iter->next.node = node->next; 68 | } else if (node->parent) { 69 | iter->next.ev_type = CMARK_EVENT_EXIT; 70 | iter->next.node = node->parent; 71 | } else { 72 | assert(false); 73 | iter->next.ev_type = CMARK_EVENT_DONE; 74 | iter->next.node = NULL; 75 | } 76 | 77 | return ev_type; 78 | } 79 | 80 | void cmark_iter_reset(cmark_iter *iter, cmark_node *current, 81 | cmark_event_type event_type) { 82 | iter->next.ev_type = event_type; 83 | iter->next.node = current; 84 | cmark_iter_next(iter); 85 | } 86 | 87 | cmark_node *cmark_iter_get_node(cmark_iter *iter) { return iter->cur.node; } 88 | 89 | cmark_event_type cmark_iter_get_event_type(cmark_iter *iter) { 90 | return iter->cur.ev_type; 91 | } 92 | 93 | cmark_node *cmark_iter_get_root(cmark_iter *iter) { return iter->root; } 94 | 95 | void cmark_consolidate_text_nodes(cmark_node *root) { 96 | if (root == NULL) { 97 | return; 98 | } 99 | cmark_iter *iter = cmark_iter_new(root); 100 | cmark_strbuf buf = CMARK_BUF_INIT(iter->mem); 101 | cmark_event_type ev_type; 102 | cmark_node *cur, *tmp, *next; 103 | 104 | while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) { 105 | cur = cmark_iter_get_node(iter); 106 | if (ev_type == CMARK_EVENT_ENTER && cur->type == CMARK_NODE_TEXT && 107 | cur->next && cur->next->type == CMARK_NODE_TEXT) { 108 | cmark_strbuf_clear(&buf); 109 | cmark_strbuf_put(&buf, cur->as.literal.data, cur->as.literal.len); 110 | tmp = cur->next; 111 | while (tmp && tmp->type == CMARK_NODE_TEXT) { 112 | cmark_iter_next(iter); // advance pointer 113 | cmark_strbuf_put(&buf, tmp->as.literal.data, tmp->as.literal.len); 114 | cur->end_column = tmp->end_column; 115 | next = tmp->next; 116 | cmark_node_free(tmp); 117 | tmp = next; 118 | } 119 | cmark_chunk_free(iter->mem, &cur->as.literal); 120 | cur->as.literal = cmark_chunk_buf_detach(&buf); 121 | } 122 | } 123 | 124 | cmark_strbuf_free(&buf); 125 | cmark_iter_free(iter); 126 | } 127 | 128 | void cmark_node_own(cmark_node *root) { 129 | if (root == NULL) { 130 | return; 131 | } 132 | cmark_iter *iter = cmark_iter_new(root); 133 | cmark_event_type ev_type; 134 | cmark_node *cur; 135 | 136 | while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) { 137 | cur = cmark_iter_get_node(iter); 138 | if (ev_type == CMARK_EVENT_ENTER) { 139 | switch (cur->type) { 140 | case CMARK_NODE_TEXT: 141 | case CMARK_NODE_HTML_INLINE: 142 | case CMARK_NODE_CODE: 143 | case CMARK_NODE_HTML_BLOCK: 144 | cmark_chunk_to_cstr(iter->mem, &cur->as.literal); 145 | break; 146 | case CMARK_NODE_LINK: 147 | cmark_chunk_to_cstr(iter->mem, &cur->as.link.url); 148 | cmark_chunk_to_cstr(iter->mem, &cur->as.link.title); 149 | break; 150 | case CMARK_NODE_CUSTOM_INLINE: 151 | cmark_chunk_to_cstr(iter->mem, &cur->as.custom.on_enter); 152 | cmark_chunk_to_cstr(iter->mem, &cur->as.custom.on_exit); 153 | break; 154 | } 155 | } 156 | } 157 | 158 | cmark_iter_free(iter); 159 | } 160 | -------------------------------------------------------------------------------- /ext/qiita_marker/iterator.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_ITERATOR_H 2 | #define CMARK_ITERATOR_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include "cmark-gfm.h" 9 | 10 | typedef struct { 11 | cmark_event_type ev_type; 12 | cmark_node *node; 13 | } cmark_iter_state; 14 | 15 | struct cmark_iter { 16 | cmark_mem *mem; 17 | cmark_node *root; 18 | cmark_iter_state cur; 19 | cmark_iter_state next; 20 | }; 21 | 22 | #ifdef __cplusplus 23 | } 24 | #endif 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /ext/qiita_marker/linked_list.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "cmark-gfm.h" 4 | 5 | cmark_llist *cmark_llist_append(cmark_mem *mem, cmark_llist *head, void *data) { 6 | cmark_llist *tmp; 7 | cmark_llist *new_node = (cmark_llist *) mem->calloc(1, sizeof(cmark_llist)); 8 | 9 | new_node->data = data; 10 | new_node->next = NULL; 11 | 12 | if (!head) 13 | return new_node; 14 | 15 | for (tmp = head; tmp->next; tmp=tmp->next); 16 | 17 | tmp->next = new_node; 18 | 19 | return head; 20 | } 21 | 22 | void cmark_llist_free_full(cmark_mem *mem, cmark_llist *head, cmark_free_func free_func) { 23 | cmark_llist *tmp, *prev; 24 | 25 | for (tmp = head; tmp;) { 26 | if (free_func) 27 | free_func(mem, tmp->data); 28 | 29 | prev = tmp; 30 | tmp = tmp->next; 31 | mem->free(prev); 32 | } 33 | } 34 | 35 | void cmark_llist_free(cmark_mem *mem, cmark_llist *head) { 36 | cmark_llist_free_full(mem, head, NULL); 37 | } 38 | -------------------------------------------------------------------------------- /ext/qiita_marker/map.c: -------------------------------------------------------------------------------- 1 | #include "map.h" 2 | #include "utf8.h" 3 | #include "parser.h" 4 | 5 | // normalize map label: collapse internal whitespace to single space, 6 | // remove leading/trailing whitespace, case fold 7 | // Return NULL if the label is actually empty (i.e. composed solely from 8 | // whitespace) 9 | unsigned char *normalize_map_label(cmark_mem *mem, cmark_chunk *ref) { 10 | cmark_strbuf normalized = CMARK_BUF_INIT(mem); 11 | unsigned char *result; 12 | 13 | if (ref == NULL) 14 | return NULL; 15 | 16 | if (ref->len == 0) 17 | return NULL; 18 | 19 | cmark_utf8proc_case_fold(&normalized, ref->data, ref->len); 20 | cmark_strbuf_trim(&normalized); 21 | cmark_strbuf_normalize_whitespace(&normalized); 22 | 23 | result = cmark_strbuf_detach(&normalized); 24 | assert(result); 25 | 26 | if (result[0] == '\0') { 27 | mem->free(result); 28 | return NULL; 29 | } 30 | 31 | return result; 32 | } 33 | 34 | static int 35 | labelcmp(const unsigned char *a, const unsigned char *b) { 36 | return strcmp((const char *)a, (const char *)b); 37 | } 38 | 39 | static int 40 | refcmp(const void *p1, const void *p2) { 41 | cmark_map_entry *r1 = *(cmark_map_entry **)p1; 42 | cmark_map_entry *r2 = *(cmark_map_entry **)p2; 43 | int res = labelcmp(r1->label, r2->label); 44 | return res ? res : ((int)r1->age - (int)r2->age); 45 | } 46 | 47 | static int 48 | refsearch(const void *label, const void *p2) { 49 | cmark_map_entry *ref = *(cmark_map_entry **)p2; 50 | return labelcmp((const unsigned char *)label, ref->label); 51 | } 52 | 53 | static void sort_map(cmark_map *map) { 54 | size_t i = 0, last = 0, size = map->size; 55 | cmark_map_entry *r = map->refs, **sorted = NULL; 56 | 57 | sorted = (cmark_map_entry **)map->mem->calloc(size, sizeof(cmark_map_entry *)); 58 | while (r) { 59 | sorted[i++] = r; 60 | r = r->next; 61 | } 62 | 63 | qsort(sorted, size, sizeof(cmark_map_entry *), refcmp); 64 | 65 | for (i = 1; i < size; i++) { 66 | if (labelcmp(sorted[i]->label, sorted[last]->label) != 0) 67 | sorted[++last] = sorted[i]; 68 | } 69 | 70 | map->sorted = sorted; 71 | map->size = last + 1; 72 | } 73 | 74 | cmark_map_entry *cmark_map_lookup(cmark_map *map, cmark_chunk *label) { 75 | cmark_map_entry **ref = NULL; 76 | cmark_map_entry *r = NULL; 77 | unsigned char *norm; 78 | 79 | if (label->len < 1 || label->len > MAX_LINK_LABEL_LENGTH) 80 | return NULL; 81 | 82 | if (map == NULL || !map->size) 83 | return NULL; 84 | 85 | norm = normalize_map_label(map->mem, label); 86 | if (norm == NULL) 87 | return NULL; 88 | 89 | if (!map->sorted) 90 | sort_map(map); 91 | 92 | ref = (cmark_map_entry **)bsearch(norm, map->sorted, map->size, sizeof(cmark_map_entry *), refsearch); 93 | map->mem->free(norm); 94 | 95 | if (ref != NULL) { 96 | r = ref[0]; 97 | /* Check for expansion limit */ 98 | if (r->size > map->max_ref_size - map->ref_size) 99 | return NULL; 100 | map->ref_size += r->size; 101 | } 102 | 103 | return r; 104 | } 105 | 106 | void cmark_map_free(cmark_map *map) { 107 | cmark_map_entry *ref; 108 | 109 | if (map == NULL) 110 | return; 111 | 112 | ref = map->refs; 113 | while (ref) { 114 | cmark_map_entry *next = ref->next; 115 | map->free(map, ref); 116 | ref = next; 117 | } 118 | 119 | map->mem->free(map->sorted); 120 | map->mem->free(map); 121 | } 122 | 123 | cmark_map *cmark_map_new(cmark_mem *mem, cmark_map_free_f free) { 124 | cmark_map *map = (cmark_map *)mem->calloc(1, sizeof(cmark_map)); 125 | map->mem = mem; 126 | map->free = free; 127 | map->max_ref_size = UINT_MAX; 128 | return map; 129 | } 130 | -------------------------------------------------------------------------------- /ext/qiita_marker/map.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_MAP_H 2 | #define CMARK_MAP_H 3 | 4 | #include "chunk.h" 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | struct cmark_map_entry { 11 | struct cmark_map_entry *next; 12 | unsigned char *label; 13 | size_t age; 14 | size_t size; 15 | }; 16 | 17 | typedef struct cmark_map_entry cmark_map_entry; 18 | 19 | struct cmark_map; 20 | 21 | typedef void (*cmark_map_free_f)(struct cmark_map *, cmark_map_entry *); 22 | 23 | struct cmark_map { 24 | cmark_mem *mem; 25 | cmark_map_entry *refs; 26 | cmark_map_entry **sorted; 27 | size_t size; 28 | size_t ref_size; 29 | size_t max_ref_size; 30 | cmark_map_free_f free; 31 | }; 32 | 33 | typedef struct cmark_map cmark_map; 34 | 35 | unsigned char *normalize_map_label(cmark_mem *mem, cmark_chunk *ref); 36 | cmark_map *cmark_map_new(cmark_mem *mem, cmark_map_free_f free); 37 | void cmark_map_free(cmark_map *map); 38 | cmark_map_entry *cmark_map_lookup(cmark_map *map, cmark_chunk *label); 39 | 40 | #ifdef __cplusplus 41 | } 42 | #endif 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /ext/qiita_marker/node.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_NODE_H 2 | #define CMARK_NODE_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include 9 | #include 10 | 11 | #include "cmark-gfm.h" 12 | #include "cmark-gfm-extension_api.h" 13 | #include "buffer.h" 14 | #include "chunk.h" 15 | 16 | typedef struct { 17 | cmark_list_type list_type; 18 | int marker_offset; 19 | int padding; 20 | int start; 21 | cmark_delim_type delimiter; 22 | unsigned char bullet_char; 23 | bool tight; 24 | bool checked; // For task list extension 25 | } cmark_list; 26 | 27 | typedef struct { 28 | cmark_chunk info; 29 | cmark_chunk literal; 30 | uint8_t fence_length; 31 | uint8_t fence_offset; 32 | unsigned char fence_char; 33 | int8_t fenced; 34 | } cmark_code; 35 | 36 | typedef struct { 37 | int level; 38 | bool setext; 39 | } cmark_heading; 40 | 41 | typedef struct { 42 | cmark_chunk url; 43 | cmark_chunk title; 44 | } cmark_link; 45 | 46 | typedef struct { 47 | cmark_chunk on_enter; 48 | cmark_chunk on_exit; 49 | } cmark_custom; 50 | 51 | enum cmark_node__internal_flags { 52 | CMARK_NODE__OPEN = (1 << 0), 53 | CMARK_NODE__LAST_LINE_BLANK = (1 << 1), 54 | CMARK_NODE__LAST_LINE_CHECKED = (1 << 2), 55 | 56 | // Extensions can register custom flags by calling `cmark_register_node_flag`. 57 | // This is the starting value for the custom flags. 58 | CMARK_NODE__REGISTER_FIRST = (1 << 3), 59 | }; 60 | 61 | typedef uint16_t cmark_node_internal_flags; 62 | 63 | struct cmark_node { 64 | cmark_strbuf content; 65 | 66 | struct cmark_node *next; 67 | struct cmark_node *prev; 68 | struct cmark_node *parent; 69 | struct cmark_node *first_child; 70 | struct cmark_node *last_child; 71 | 72 | void *user_data; 73 | cmark_free_func user_data_free_func; 74 | 75 | int start_line; 76 | int start_column; 77 | int end_line; 78 | int end_column; 79 | int internal_offset; 80 | uint16_t type; 81 | cmark_node_internal_flags flags; 82 | 83 | cmark_syntax_extension *extension; 84 | 85 | /** 86 | * Used during cmark_render() to cache the most recent non-NULL 87 | * extension, if you go up the parent chain like this: 88 | * 89 | * node->parent->...parent->extension 90 | */ 91 | cmark_syntax_extension *ancestor_extension; 92 | 93 | union { 94 | int ref_ix; 95 | int def_count; 96 | } footnote; 97 | 98 | cmark_node *parent_footnote_def; 99 | 100 | union { 101 | cmark_chunk literal; 102 | cmark_list list; 103 | cmark_code code; 104 | cmark_heading heading; 105 | cmark_link link; 106 | cmark_custom custom; 107 | int html_block_type; 108 | void *opaque; 109 | } as; 110 | }; 111 | 112 | /** 113 | * Syntax extensions can use this function to register a custom node 114 | * flag. The flags are stored in the `flags` field of the `cmark_node` 115 | * struct. The `flags` parameter should be the address of a global variable 116 | * which will store the flag value. 117 | */ 118 | CMARK_GFM_EXPORT 119 | void cmark_register_node_flag(cmark_node_internal_flags *flags); 120 | 121 | /** 122 | * DEPRECATED. 123 | * 124 | * This function was added in cmark-gfm version 0.29.0.gfm.7, and was 125 | * required to be called at program start time, which caused 126 | * backwards-compatibility issues in applications that use cmark-gfm as a 127 | * library. It is now a no-op. 128 | */ 129 | CMARK_GFM_EXPORT 130 | void cmark_init_standard_node_flags(void); 131 | 132 | static CMARK_INLINE cmark_mem *cmark_node_mem(cmark_node *node) { 133 | return node->content.mem; 134 | } 135 | CMARK_GFM_EXPORT int cmark_node_check(cmark_node *node, FILE *out); 136 | 137 | static CMARK_INLINE bool CMARK_NODE_TYPE_BLOCK_P(cmark_node_type node_type) { 138 | return (node_type & CMARK_NODE_TYPE_MASK) == CMARK_NODE_TYPE_BLOCK; 139 | } 140 | 141 | static CMARK_INLINE bool CMARK_NODE_BLOCK_P(cmark_node *node) { 142 | return node != NULL && CMARK_NODE_TYPE_BLOCK_P((cmark_node_type) node->type); 143 | } 144 | 145 | static CMARK_INLINE bool CMARK_NODE_TYPE_INLINE_P(cmark_node_type node_type) { 146 | return (node_type & CMARK_NODE_TYPE_MASK) == CMARK_NODE_TYPE_INLINE; 147 | } 148 | 149 | static CMARK_INLINE bool CMARK_NODE_INLINE_P(cmark_node *node) { 150 | return node != NULL && CMARK_NODE_TYPE_INLINE_P((cmark_node_type) node->type); 151 | } 152 | 153 | CMARK_GFM_EXPORT bool cmark_node_can_contain_type(cmark_node *node, cmark_node_type child_type); 154 | 155 | /** 156 | * Enable (or disable) extra safety checks. These extra checks cause 157 | * extra performance overhead (in some cases quadratic), so they are only 158 | * intended to be used during testing. 159 | */ 160 | CMARK_GFM_EXPORT void cmark_enable_safety_checks(bool enable); 161 | 162 | #ifdef __cplusplus 163 | } 164 | #endif 165 | 166 | #endif 167 | -------------------------------------------------------------------------------- /ext/qiita_marker/parser.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_PARSER_H 2 | #define CMARK_PARSER_H 3 | 4 | #include 5 | #include "references.h" 6 | #include "node.h" 7 | #include "buffer.h" 8 | 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif 12 | 13 | #define MAX_LINK_LABEL_LENGTH 1000 14 | 15 | struct cmark_parser { 16 | struct cmark_mem *mem; 17 | /* A hashtable of urls in the current document for cross-references */ 18 | struct cmark_map *refmap; 19 | /* The root node of the parser, always a CMARK_NODE_DOCUMENT */ 20 | struct cmark_node *root; 21 | /* The last open block after a line is fully processed */ 22 | struct cmark_node *current; 23 | /* See the documentation for cmark_parser_get_line_number() in cmark.h */ 24 | int line_number; 25 | /* See the documentation for cmark_parser_get_offset() in cmark.h */ 26 | bufsize_t offset; 27 | /* See the documentation for cmark_parser_get_column() in cmark.h */ 28 | bufsize_t column; 29 | /* See the documentation for cmark_parser_get_first_nonspace() in cmark.h */ 30 | bufsize_t first_nonspace; 31 | /* See the documentation for cmark_parser_get_first_nonspace_column() in cmark.h */ 32 | bufsize_t first_nonspace_column; 33 | bufsize_t thematic_break_kill_pos; 34 | /* See the documentation for cmark_parser_get_indent() in cmark.h */ 35 | int indent; 36 | /* See the documentation for cmark_parser_is_blank() in cmark.h */ 37 | bool blank; 38 | /* See the documentation for cmark_parser_has_partially_consumed_tab() in cmark.h */ 39 | bool partially_consumed_tab; 40 | /* Contains the currently processed line */ 41 | cmark_strbuf curline; 42 | /* See the documentation for cmark_parser_get_last_line_length() in cmark.h */ 43 | bufsize_t last_line_length; 44 | /* FIXME: not sure about the difference with curline */ 45 | cmark_strbuf linebuf; 46 | /* Options set by the user, see the Options section in cmark.h */ 47 | int options; 48 | bool last_buffer_ended_with_cr; 49 | size_t total_size; 50 | cmark_llist *syntax_extensions; 51 | cmark_llist *inline_syntax_extensions; 52 | cmark_ispunct_func backslash_ispunct; 53 | }; 54 | 55 | #ifdef __cplusplus 56 | } 57 | #endif 58 | 59 | #endif 60 | -------------------------------------------------------------------------------- /ext/qiita_marker/plaintext.c: -------------------------------------------------------------------------------- 1 | #include "node.h" 2 | #include "syntax_extension.h" 3 | #include "render.h" 4 | 5 | #define OUT(s, wrap, escaping) renderer->out(renderer, node, s, wrap, escaping) 6 | #define LIT(s) renderer->out(renderer, node, s, false, LITERAL) 7 | #define CR() renderer->cr(renderer) 8 | #define BLANKLINE() renderer->blankline(renderer) 9 | #define LISTMARKER_SIZE 20 10 | 11 | // Functions to convert cmark_nodes to plain text strings. 12 | 13 | static CMARK_INLINE void outc(cmark_renderer *renderer, cmark_node *node, 14 | cmark_escaping escape, 15 | int32_t c, unsigned char nextc) { 16 | cmark_render_code_point(renderer, c); 17 | } 18 | 19 | static int S_render_node(cmark_renderer *renderer, cmark_node *node, 20 | cmark_event_type ev_type, int options) { 21 | int list_number; 22 | cmark_delim_type list_delim; 23 | int i; 24 | bool entering = (ev_type == CMARK_EVENT_ENTER); 25 | char listmarker[LISTMARKER_SIZE]; 26 | bool first_in_list_item; 27 | bufsize_t marker_width; 28 | bool allow_wrap = renderer->width > 0 && !(CMARK_OPT_NOBREAKS & options) && 29 | !(CMARK_OPT_HARDBREAKS & options); 30 | 31 | // Don't adjust tight list status til we've started the list. 32 | // Otherwise we loose the blank line between a paragraph and 33 | // a following list. 34 | if (entering) { 35 | if (node->parent && node->parent->type == CMARK_NODE_ITEM) { 36 | renderer->in_tight_list_item = node->parent->parent->as.list.tight; 37 | } 38 | } else { 39 | if (node->type == CMARK_NODE_LIST) { 40 | renderer->in_tight_list_item = 41 | node->parent && 42 | node->parent->type == CMARK_NODE_ITEM && 43 | node->parent->parent->as.list.tight; 44 | } 45 | } 46 | 47 | if (node->extension && node->extension->plaintext_render_func) { 48 | node->extension->plaintext_render_func(node->extension, renderer, node, ev_type, options); 49 | return 1; 50 | } 51 | 52 | switch (node->type) { 53 | case CMARK_NODE_DOCUMENT: 54 | break; 55 | 56 | case CMARK_NODE_BLOCK_QUOTE: 57 | break; 58 | 59 | case CMARK_NODE_LIST: 60 | if (!entering && node->next && (node->next->type == CMARK_NODE_CODE_BLOCK || 61 | node->next->type == CMARK_NODE_LIST)) { 62 | CR(); 63 | } 64 | break; 65 | 66 | case CMARK_NODE_ITEM: 67 | if (cmark_node_get_list_type(node->parent) == CMARK_BULLET_LIST) { 68 | marker_width = 4; 69 | } else { 70 | list_number = cmark_node_get_item_index(node); 71 | list_delim = cmark_node_get_list_delim(node->parent); 72 | // we ensure a width of at least 4 so 73 | // we get nice transition from single digits 74 | // to double 75 | snprintf(listmarker, LISTMARKER_SIZE, "%d%s%s", list_number, 76 | list_delim == CMARK_PAREN_DELIM ? ")" : ".", 77 | list_number < 10 ? " " : " "); 78 | marker_width = (bufsize_t)strlen(listmarker); 79 | } 80 | if (entering) { 81 | if (cmark_node_get_list_type(node->parent) == CMARK_BULLET_LIST) { 82 | LIT(" - "); 83 | renderer->begin_content = true; 84 | } else { 85 | LIT(listmarker); 86 | renderer->begin_content = true; 87 | } 88 | for (i = marker_width; i--;) { 89 | cmark_strbuf_putc(renderer->prefix, ' '); 90 | } 91 | } else { 92 | cmark_strbuf_truncate(renderer->prefix, 93 | renderer->prefix->size - marker_width); 94 | CR(); 95 | } 96 | break; 97 | 98 | case CMARK_NODE_HEADING: 99 | if (entering) { 100 | renderer->begin_content = true; 101 | renderer->no_linebreaks = true; 102 | } else { 103 | renderer->no_linebreaks = false; 104 | BLANKLINE(); 105 | } 106 | break; 107 | 108 | case CMARK_NODE_CODE_BLOCK: 109 | first_in_list_item = node->prev == NULL && node->parent && 110 | node->parent->type == CMARK_NODE_ITEM; 111 | 112 | if (!first_in_list_item) { 113 | BLANKLINE(); 114 | } 115 | OUT(cmark_node_get_literal(node), false, LITERAL); 116 | BLANKLINE(); 117 | break; 118 | 119 | case CMARK_NODE_HTML_BLOCK: 120 | break; 121 | 122 | case CMARK_NODE_CUSTOM_BLOCK: 123 | break; 124 | 125 | case CMARK_NODE_THEMATIC_BREAK: 126 | BLANKLINE(); 127 | break; 128 | 129 | case CMARK_NODE_PARAGRAPH: 130 | if (!entering) { 131 | BLANKLINE(); 132 | } 133 | break; 134 | 135 | case CMARK_NODE_TEXT: 136 | OUT(cmark_node_get_literal(node), allow_wrap, NORMAL); 137 | break; 138 | 139 | case CMARK_NODE_LINEBREAK: 140 | CR(); 141 | break; 142 | 143 | case CMARK_NODE_SOFTBREAK: 144 | if (CMARK_OPT_HARDBREAKS & options) { 145 | CR(); 146 | } else if (!renderer->no_linebreaks && renderer->width == 0 && 147 | !(CMARK_OPT_HARDBREAKS & options) && 148 | !(CMARK_OPT_NOBREAKS & options)) { 149 | CR(); 150 | } else { 151 | OUT(" ", allow_wrap, LITERAL); 152 | } 153 | break; 154 | 155 | case CMARK_NODE_CODE: 156 | OUT(cmark_node_get_literal(node), allow_wrap, LITERAL); 157 | break; 158 | 159 | case CMARK_NODE_HTML_INLINE: 160 | break; 161 | 162 | case CMARK_NODE_CUSTOM_INLINE: 163 | break; 164 | 165 | case CMARK_NODE_STRONG: 166 | break; 167 | 168 | case CMARK_NODE_EMPH: 169 | break; 170 | 171 | case CMARK_NODE_LINK: 172 | break; 173 | 174 | case CMARK_NODE_IMAGE: 175 | break; 176 | 177 | case CMARK_NODE_FOOTNOTE_REFERENCE: 178 | if (entering) { 179 | LIT("[^"); 180 | OUT(cmark_chunk_to_cstr(renderer->mem, &node->as.literal), false, LITERAL); 181 | LIT("]"); 182 | } 183 | break; 184 | 185 | case CMARK_NODE_FOOTNOTE_DEFINITION: 186 | if (entering) { 187 | renderer->footnote_ix += 1; 188 | LIT("[^"); 189 | char n[32]; 190 | snprintf(n, sizeof(n), "%d", renderer->footnote_ix); 191 | OUT(n, false, LITERAL); 192 | LIT("]: "); 193 | 194 | cmark_strbuf_puts(renderer->prefix, " "); 195 | } else { 196 | cmark_strbuf_truncate(renderer->prefix, renderer->prefix->size - 4); 197 | } 198 | break; 199 | default: 200 | assert(false); 201 | break; 202 | } 203 | 204 | return 1; 205 | } 206 | 207 | char *cmark_render_plaintext(cmark_node *root, int options, int width) { 208 | return cmark_render_plaintext_with_mem(root, options, width, cmark_node_mem(root)); 209 | } 210 | 211 | char *cmark_render_plaintext_with_mem(cmark_node *root, int options, int width, cmark_mem *mem) { 212 | if (options & CMARK_OPT_HARDBREAKS) { 213 | // disable breaking on width, since it has 214 | // a different meaning with OPT_HARDBREAKS 215 | width = 0; 216 | } 217 | return cmark_render(mem, root, options, width, outc, S_render_node); 218 | } 219 | -------------------------------------------------------------------------------- /ext/qiita_marker/plugin.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "plugin.h" 4 | 5 | extern cmark_mem CMARK_DEFAULT_MEM_ALLOCATOR; 6 | 7 | int cmark_plugin_register_syntax_extension(cmark_plugin * plugin, 8 | cmark_syntax_extension * extension) { 9 | plugin->syntax_extensions = cmark_llist_append(&CMARK_DEFAULT_MEM_ALLOCATOR, plugin->syntax_extensions, extension); 10 | return 1; 11 | } 12 | 13 | cmark_plugin * 14 | cmark_plugin_new(void) { 15 | cmark_plugin *res = (cmark_plugin *) CMARK_DEFAULT_MEM_ALLOCATOR.calloc(1, sizeof(cmark_plugin)); 16 | 17 | res->syntax_extensions = NULL; 18 | 19 | return res; 20 | } 21 | 22 | void 23 | cmark_plugin_free(cmark_plugin *plugin) { 24 | cmark_llist_free_full(&CMARK_DEFAULT_MEM_ALLOCATOR, 25 | plugin->syntax_extensions, 26 | (cmark_free_func) cmark_syntax_extension_free); 27 | CMARK_DEFAULT_MEM_ALLOCATOR.free(plugin); 28 | } 29 | 30 | cmark_llist * 31 | cmark_plugin_steal_syntax_extensions(cmark_plugin *plugin) { 32 | cmark_llist *res = plugin->syntax_extensions; 33 | 34 | plugin->syntax_extensions = NULL; 35 | return res; 36 | } 37 | -------------------------------------------------------------------------------- /ext/qiita_marker/plugin.h: -------------------------------------------------------------------------------- 1 | #ifndef CMARK_PLUGIN_H 2 | #define CMARK_PLUGIN_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include "cmark-gfm.h" 9 | #include "cmark-gfm-extension_api.h" 10 | 11 | /** 12 | * cmark_plugin: 13 | * 14 | * A plugin structure, which should be filled by plugin's 15 | * init functions. 16 | */ 17 | struct cmark_plugin { 18 | cmark_llist *syntax_extensions; 19 | }; 20 | 21 | cmark_llist * 22 | cmark_plugin_steal_syntax_extensions(cmark_plugin *plugin); 23 | 24 | cmark_plugin * 25 | cmark_plugin_new(void); 26 | 27 | void 28 | cmark_plugin_free(cmark_plugin *plugin); 29 | 30 | #ifdef __cplusplus 31 | } 32 | #endif 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /ext/qiita_marker/qfm.h: -------------------------------------------------------------------------------- 1 | #ifndef QFM_H 2 | #define QFM_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | /** Use
 tags for code blocks instead of 
. **/
10 | #define CMARK_OPT_CODE_DATA_METADATA (1 << 25)
11 | 
12 | /* Prevent parsing Qiita-style Mentions as emphasis. */
13 | #define CMARK_OPT_MENTION_NO_EMPHASIS (1 << 26)
14 | 
15 | /* Render autolinks with class name  */
16 | #define CMARK_OPT_AUTOLINK_CLASS_NAME (1 << 27)
17 | 
18 | #ifdef __cplusplus
19 | }
20 | #endif
21 | 
22 | #endif
23 | 


--------------------------------------------------------------------------------
/ext/qiita_marker/qfm_custom_block.h:
--------------------------------------------------------------------------------
 1 | #ifndef QFM_CUSTOM_BLOCK_H
 2 | #define QFM_CUSTOM_BLOCK_H
 3 | 
 4 | #include "cmark-gfm-core-extensions.h"
 5 | 
 6 | extern cmark_node_type CMARK_NODE_QFM_CUSTOM_BLOCK;
 7 | 
 8 | cmark_syntax_extension *create_qfm_custom_block_extension(void);
 9 | 
10 | #endif
11 | 


--------------------------------------------------------------------------------
/ext/qiita_marker/qfm_mention_no_emphasis.c:
--------------------------------------------------------------------------------
 1 | #include "cmark-gfm.h"
 2 | #include "cmark_ctype.h"
 3 | #include "config.h"
 4 | 
 5 | static bool is_wordchar(char c) {
 6 |   return cmark_isalnum(c) || c == '_' || c == '-';
 7 | }
 8 | 
 9 | bool is_part_of_mention(unsigned char *data, bufsize_t offset) {
10 |   int i;
11 |   int lookbehind_limit = (int)-offset;
12 |   char character;
13 | 
14 |   for (i = 0; i >= lookbehind_limit; i--) {
15 |     character = data[i];
16 | 
17 |     if (is_wordchar(character)) {
18 |       // Continue lookbehind.
19 |     } else if (character == '@') {
20 |       if (i == offset) {
21 |         // The "@" is at beginning of the text. (e.g. "@foo")
22 |         return true;
23 |       } else {
24 |         // Check if the previous character of the "@" is alphanumeric or not.
25 |         //   " @foo" and "あ@foo" are mentions.
26 |         //   "a@foo" is not a mention.
27 |         char prev_character = data[i - 1];
28 |         return !cmark_isalnum(prev_character);
29 |       }
30 |     } else {
31 |       // Found non-mention character, so this is not a mention.
32 |       return false;
33 |     }
34 |   }
35 | 
36 |   return false;
37 | }
38 | 


--------------------------------------------------------------------------------
/ext/qiita_marker/qfm_mention_no_emphasis.h:
--------------------------------------------------------------------------------
1 | #ifndef QFM_MENTION_NO_EMPHASIS
2 | #define QFM_MENTION_NO_EMPHASIS
3 | 
4 | #include "cmark-gfm.h"
5 | 
6 | bool is_part_of_mention(unsigned char *data, bufsize_t offset);
7 | 
8 | #endif
9 | 


--------------------------------------------------------------------------------
/ext/qiita_marker/qfm_scanners.h:
--------------------------------------------------------------------------------
 1 | #include "chunk.h"
 2 | #include "cmark-gfm.h"
 3 | 
 4 | #ifdef __cplusplus
 5 | extern "C" {
 6 | #endif
 7 | 
 8 | bufsize_t _qfm_scan_at(bufsize_t (*scanner)(const unsigned char *),
 9 |                        unsigned char *ptr, int len, bufsize_t offset);
10 | bufsize_t _scan_open_qfm_custom_block_fence(const unsigned char *p);
11 | bufsize_t _scan_close_qfm_custom_block_fence(const unsigned char *p);
12 | 
13 | #define scan_open_qfm_custom_block_fence(c, l, n)                              \
14 |   _qfm_scan_at(&_scan_open_qfm_custom_block_fence, c, l, n)
15 | #define scan_close_qfm_custom_block_fence(c, l, n)                             \
16 |   _qfm_scan_at(&_scan_close_qfm_custom_block_fence, c, l, n)
17 | 
18 | #ifdef __cplusplus
19 | }
20 | #endif
21 | 


--------------------------------------------------------------------------------
/ext/qiita_marker/qfm_scanners.re:
--------------------------------------------------------------------------------
 1 | /*!re2c re2c:flags:no-debug-info = 1; */
 2 | /*!re2c re2c:indent:string = '  '; */
 3 | 
 4 | #include "qfm_scanners.h"
 5 | #include 
 6 | 
 7 | bufsize_t _qfm_scan_at(bufsize_t (*scanner)(const unsigned char *),
 8 |                        unsigned char *ptr, int len, bufsize_t offset) {
 9 |   bufsize_t res;
10 | 
11 |   if (ptr == NULL || offset >= len) {
12 |     return 0;
13 |   } else {
14 |     unsigned char lim = ptr[len];
15 | 
16 |     ptr[len] = '\0';
17 |     res = scanner(ptr + offset);
18 |     ptr[len] = lim;
19 |   }
20 | 
21 |   return res;
22 | }
23 | 
24 | /*!re2c
25 |   re2c:define:YYCTYPE  = "unsigned char";
26 |   re2c:define:YYCURSOR = p;
27 |   re2c:define:YYMARKER = marker;
28 |   re2c:define:YYCTXMARKER = marker;
29 |   re2c:yyfill:enable = 0;
30 | */
31 | 
32 | // Scan an opening qfm_custom_block fence.
33 | bufsize_t _scan_open_qfm_custom_block_fence(const unsigned char *p) {
34 |   const unsigned char *marker = NULL;
35 |   const unsigned char *start = p;
36 |   /*!re2c
37 |     [:]{3,} / [^:\r\n\x00]*[\r\n] { return (bufsize_t)(p - start); }
38 |     * { return 0; }
39 |   */
40 | }
41 | 
42 | // Scan a closing qfm_custom_block fence with length at least len.
43 | bufsize_t _scan_close_qfm_custom_block_fence(const unsigned char *p) {
44 |   const unsigned char *marker = NULL;
45 |   const unsigned char *start = p;
46 |   /*!re2c
47 |     [:]{3,} / [ \t]*[\r\n] { return (bufsize_t)(p - start); }
48 |     * { return 0; }
49 |   */
50 | }
51 | 


--------------------------------------------------------------------------------
/ext/qiita_marker/qiita_marker.h:
--------------------------------------------------------------------------------
 1 | #ifndef QIITA_MARKER_H
 2 | #define QIITA_MARKER_H
 3 | 
 4 | #ifndef __MSXML_LIBRARY_DEFINED__
 5 | #define __MSXML_LIBRARY_DEFINED__
 6 | #endif
 7 | 
 8 | #include "cmark-gfm.h"
 9 | #include "ruby.h"
10 | #include "ruby/encoding.h"
11 | 
12 | #define CSTR2SYM(s) (ID2SYM(rb_intern((s))))
13 | 
14 | void Init_qiita_marker();
15 | 
16 | #endif
17 | 


--------------------------------------------------------------------------------
/ext/qiita_marker/references.c:
--------------------------------------------------------------------------------
 1 | #include "cmark-gfm.h"
 2 | #include "parser.h"
 3 | #include "references.h"
 4 | #include "inlines.h"
 5 | #include "chunk.h"
 6 | 
 7 | static void reference_free(cmark_map *map, cmark_map_entry *_ref) {
 8 |   cmark_reference *ref = (cmark_reference *)_ref;
 9 |   cmark_mem *mem = map->mem;
10 |   if (ref != NULL) {
11 |     mem->free(ref->entry.label);
12 |     cmark_chunk_free(mem, &ref->url);
13 |     cmark_chunk_free(mem, &ref->title);
14 |     mem->free(ref);
15 |   }
16 | }
17 | 
18 | void cmark_reference_create(cmark_map *map, cmark_chunk *label,
19 |                             cmark_chunk *url, cmark_chunk *title) {
20 |   cmark_reference *ref;
21 |   unsigned char *reflabel = normalize_map_label(map->mem, label);
22 | 
23 |   /* empty reference name, or composed from only whitespace */
24 |   if (reflabel == NULL)
25 |     return;
26 | 
27 |   assert(map->sorted == NULL);
28 | 
29 |   ref = (cmark_reference *)map->mem->calloc(1, sizeof(*ref));
30 |   ref->entry.label = reflabel;
31 |   ref->url = cmark_clean_url(map->mem, url);
32 |   ref->title = cmark_clean_title(map->mem, title);
33 |   ref->entry.age = map->size;
34 |   ref->entry.next = map->refs;
35 |   ref->entry.size = ref->url.len + ref->title.len;
36 | 
37 |   map->refs = (cmark_map_entry *)ref;
38 |   map->size++;
39 | }
40 | 
41 | cmark_map *cmark_reference_map_new(cmark_mem *mem) {
42 |   return cmark_map_new(mem, reference_free);
43 | }
44 | 


--------------------------------------------------------------------------------
/ext/qiita_marker/references.h:
--------------------------------------------------------------------------------
 1 | #ifndef CMARK_REFERENCES_H
 2 | #define CMARK_REFERENCES_H
 3 | 
 4 | #include "map.h"
 5 | 
 6 | #ifdef __cplusplus
 7 | extern "C" {
 8 | #endif
 9 | 
10 | struct cmark_reference {
11 |   cmark_map_entry entry;
12 |   cmark_chunk url;
13 |   cmark_chunk title;
14 | };
15 | 
16 | typedef struct cmark_reference cmark_reference;
17 | 
18 | void cmark_reference_create(cmark_map *map, cmark_chunk *label,
19 |                             cmark_chunk *url, cmark_chunk *title);
20 | cmark_map *cmark_reference_map_new(cmark_mem *mem);
21 | 
22 | #ifdef __cplusplus
23 | }
24 | #endif
25 | 
26 | #endif
27 | 


--------------------------------------------------------------------------------
/ext/qiita_marker/registry.c:
--------------------------------------------------------------------------------
 1 | #include 
 2 | #include 
 3 | #include 
 4 | 
 5 | #include "config.h"
 6 | #include "cmark-gfm.h"
 7 | #include "syntax_extension.h"
 8 | #include "registry.h"
 9 | #include "plugin.h"
10 | 
11 | extern cmark_mem CMARK_DEFAULT_MEM_ALLOCATOR;
12 | 
13 | static cmark_llist *syntax_extensions = NULL;
14 | 
15 | void cmark_register_plugin(cmark_plugin_init_func reg_fn) {
16 |   cmark_plugin *plugin = cmark_plugin_new();
17 | 
18 |   if (!reg_fn(plugin)) {
19 |     cmark_plugin_free(plugin);
20 |     return;
21 |   }
22 | 
23 |   cmark_llist *syntax_extensions_list = cmark_plugin_steal_syntax_extensions(plugin),
24 |               *it;
25 | 
26 |   for (it = syntax_extensions_list; it; it = it->next) {
27 |     syntax_extensions = cmark_llist_append(&CMARK_DEFAULT_MEM_ALLOCATOR, syntax_extensions, it->data);
28 |   }
29 | 
30 |   cmark_llist_free(&CMARK_DEFAULT_MEM_ALLOCATOR, syntax_extensions_list);
31 |   cmark_plugin_free(plugin);
32 | }
33 | 
34 | void cmark_release_plugins(void) {
35 |   if (syntax_extensions) {
36 |     cmark_llist_free_full(
37 |         &CMARK_DEFAULT_MEM_ALLOCATOR,
38 |         syntax_extensions,
39 |         (cmark_free_func) cmark_syntax_extension_free);
40 |     syntax_extensions = NULL;
41 |   }
42 | }
43 | 
44 | cmark_llist *cmark_list_syntax_extensions(cmark_mem *mem) {
45 |   cmark_llist *it;
46 |   cmark_llist *res = NULL;
47 | 
48 |   for (it = syntax_extensions; it; it = it->next) {
49 |     res = cmark_llist_append(mem, res, it->data);
50 |   }
51 |   return res;
52 | }
53 | 
54 | cmark_syntax_extension *cmark_find_syntax_extension(const char *name) {
55 |   cmark_llist *tmp;
56 | 
57 |   for (tmp = syntax_extensions; tmp; tmp = tmp->next) {
58 |     cmark_syntax_extension *ext = (cmark_syntax_extension *) tmp->data;
59 |     if (!strcmp(ext->name, name))
60 |       return ext;
61 |   }
62 |   return NULL;
63 | }
64 | 


--------------------------------------------------------------------------------
/ext/qiita_marker/registry.h:
--------------------------------------------------------------------------------
 1 | #ifndef CMARK_REGISTRY_H
 2 | #define CMARK_REGISTRY_H
 3 | 
 4 | #ifdef __cplusplus
 5 | extern "C" {
 6 | #endif
 7 | 
 8 | #include "cmark-gfm.h"
 9 | #include "plugin.h"
10 | 
11 | CMARK_GFM_EXPORT
12 | void cmark_register_plugin(cmark_plugin_init_func reg_fn);
13 | 
14 | CMARK_GFM_EXPORT
15 | void cmark_release_plugins(void);
16 | 
17 | CMARK_GFM_EXPORT
18 | cmark_llist *cmark_list_syntax_extensions(cmark_mem *mem);
19 | 
20 | #ifdef __cplusplus
21 | }
22 | #endif
23 | 
24 | #endif
25 | 


--------------------------------------------------------------------------------
/ext/qiita_marker/render.h:
--------------------------------------------------------------------------------
 1 | #ifndef CMARK_RENDER_H
 2 | #define CMARK_RENDER_H
 3 | 
 4 | #ifdef __cplusplus
 5 | extern "C" {
 6 | #endif
 7 | 
 8 | #include 
 9 | #include "buffer.h"
10 | #include "chunk.h"
11 | 
12 | typedef enum { LITERAL, NORMAL, TITLE, URL } cmark_escaping;
13 | 
14 | struct cmark_renderer {
15 |   cmark_mem *mem;
16 |   cmark_strbuf *buffer;
17 |   cmark_strbuf *prefix;
18 |   int column;
19 |   int width;
20 |   int need_cr;
21 |   bufsize_t last_breakable;
22 |   bool begin_line;
23 |   bool begin_content;
24 |   bool no_linebreaks;
25 |   bool in_tight_list_item;
26 |   void (*outc)(struct cmark_renderer *, cmark_node *, cmark_escaping, int32_t, unsigned char);
27 |   void (*cr)(struct cmark_renderer *);
28 |   void (*blankline)(struct cmark_renderer *);
29 |   void (*out)(struct cmark_renderer *, cmark_node *, const char *, bool, cmark_escaping);
30 |   unsigned int footnote_ix;
31 | };
32 | 
33 | typedef struct cmark_renderer cmark_renderer;
34 | 
35 | struct cmark_html_renderer {
36 |   cmark_strbuf *html;
37 |   cmark_node *plain;
38 |   cmark_llist *filter_extensions;
39 |   unsigned int footnote_ix;
40 |   unsigned int written_footnote_ix;
41 |   void *opaque;
42 | };
43 | 
44 | typedef struct cmark_html_renderer cmark_html_renderer;
45 | 
46 | void cmark_render_ascii(cmark_renderer *renderer, const char *s);
47 | 
48 | void cmark_render_code_point(cmark_renderer *renderer, uint32_t c);
49 | 
50 | char *cmark_render(cmark_mem *mem, cmark_node *root, int options, int width,
51 |                    void (*outc)(cmark_renderer *, cmark_node *,
52 |                                 cmark_escaping, int32_t,
53 |                                 unsigned char),
54 |                    int (*render_node)(cmark_renderer *renderer,
55 |                                       cmark_node *node,
56 |                                       cmark_event_type ev_type, int options));
57 | 
58 | #ifdef __cplusplus
59 | }
60 | #endif
61 | 
62 | #endif
63 | 


--------------------------------------------------------------------------------
/ext/qiita_marker/scanners.h:
--------------------------------------------------------------------------------
 1 | #ifndef CMARK_SCANNERS_H
 2 | #define CMARK_SCANNERS_H
 3 | 
 4 | #include "cmark-gfm.h"
 5 | #include "chunk.h"
 6 | 
 7 | #ifdef __cplusplus
 8 | extern "C" {
 9 | #endif
10 | 
11 | bufsize_t _scan_at(bufsize_t (*scanner)(const unsigned char *), cmark_chunk *c,
12 |                    bufsize_t offset);
13 | bufsize_t _scan_scheme(const unsigned char *p);
14 | bufsize_t _scan_autolink_uri(const unsigned char *p);
15 | bufsize_t _scan_autolink_email(const unsigned char *p);
16 | bufsize_t _scan_html_tag(const unsigned char *p);
17 | bufsize_t _scan_liberal_html_tag(const unsigned char *p);
18 | bufsize_t _scan_html_comment(const unsigned char *p);
19 | bufsize_t _scan_html_pi(const unsigned char *p);
20 | bufsize_t _scan_html_declaration(const unsigned char *p);
21 | bufsize_t _scan_html_cdata(const unsigned char *p);
22 | bufsize_t _scan_html_block_start(const unsigned char *p);
23 | bufsize_t _scan_html_block_start_7(const unsigned char *p);
24 | bufsize_t _scan_html_block_end_1(const unsigned char *p);
25 | bufsize_t _scan_html_block_end_2(const unsigned char *p);
26 | bufsize_t _scan_html_block_end_3(const unsigned char *p);
27 | bufsize_t _scan_html_block_end_4(const unsigned char *p);
28 | bufsize_t _scan_html_block_end_5(const unsigned char *p);
29 | bufsize_t _scan_link_title(const unsigned char *p);
30 | bufsize_t _scan_spacechars(const unsigned char *p);
31 | bufsize_t _scan_atx_heading_start(const unsigned char *p);
32 | bufsize_t _scan_setext_heading_line(const unsigned char *p);
33 | bufsize_t _scan_open_code_fence(const unsigned char *p);
34 | bufsize_t _scan_close_code_fence(const unsigned char *p);
35 | bufsize_t _scan_entity(const unsigned char *p);
36 | bufsize_t _scan_dangerous_url(const unsigned char *p);
37 | bufsize_t _scan_footnote_definition(const unsigned char *p);
38 | 
39 | #define scan_scheme(c, n) _scan_at(&_scan_scheme, c, n)
40 | #define scan_autolink_uri(c, n) _scan_at(&_scan_autolink_uri, c, n)
41 | #define scan_autolink_email(c, n) _scan_at(&_scan_autolink_email, c, n)
42 | #define scan_html_tag(c, n) _scan_at(&_scan_html_tag, c, n)
43 | #define scan_liberal_html_tag(c, n) _scan_at(&_scan_liberal_html_tag, c, n)
44 | #define scan_html_comment(c, n) _scan_at(&_scan_html_comment, c, n)
45 | #define scan_html_pi(c, n) _scan_at(&_scan_html_pi, c, n)
46 | #define scan_html_declaration(c, n) _scan_at(&_scan_html_declaration, c, n)
47 | #define scan_html_cdata(c, n) _scan_at(&_scan_html_cdata, c, n)
48 | #define scan_html_block_start(c, n) _scan_at(&_scan_html_block_start, c, n)
49 | #define scan_html_block_start_7(c, n) _scan_at(&_scan_html_block_start_7, c, n)
50 | #define scan_html_block_end_1(c, n) _scan_at(&_scan_html_block_end_1, c, n)
51 | #define scan_html_block_end_2(c, n) _scan_at(&_scan_html_block_end_2, c, n)
52 | #define scan_html_block_end_3(c, n) _scan_at(&_scan_html_block_end_3, c, n)
53 | #define scan_html_block_end_4(c, n) _scan_at(&_scan_html_block_end_4, c, n)
54 | #define scan_html_block_end_5(c, n) _scan_at(&_scan_html_block_end_5, c, n)
55 | #define scan_link_title(c, n) _scan_at(&_scan_link_title, c, n)
56 | #define scan_spacechars(c, n) _scan_at(&_scan_spacechars, c, n)
57 | #define scan_atx_heading_start(c, n) _scan_at(&_scan_atx_heading_start, c, n)
58 | #define scan_setext_heading_line(c, n)                                         \
59 |   _scan_at(&_scan_setext_heading_line, c, n)
60 | #define scan_open_code_fence(c, n) _scan_at(&_scan_open_code_fence, c, n)
61 | #define scan_close_code_fence(c, n) _scan_at(&_scan_close_code_fence, c, n)
62 | #define scan_entity(c, n) _scan_at(&_scan_entity, c, n)
63 | #define scan_dangerous_url(c, n) _scan_at(&_scan_dangerous_url, c, n)
64 | #define scan_footnote_definition(c, n) _scan_at(&_scan_footnote_definition, c, n)
65 | 
66 | #ifdef __cplusplus
67 | }
68 | #endif
69 | 
70 | #endif
71 | 


--------------------------------------------------------------------------------
/ext/qiita_marker/strikethrough.c:
--------------------------------------------------------------------------------
  1 | #include "strikethrough.h"
  2 | #include 
  3 | #include 
  4 | 
  5 | cmark_node_type CMARK_NODE_STRIKETHROUGH;
  6 | 
  7 | static cmark_node *match(cmark_syntax_extension *self, cmark_parser *parser,
  8 |                          cmark_node *parent, unsigned char character,
  9 |                          cmark_inline_parser *inline_parser) {
 10 |   cmark_node *res = NULL;
 11 |   int left_flanking, right_flanking, punct_before, punct_after, delims;
 12 |   char buffer[101];
 13 | 
 14 |   if (character != '~')
 15 |     return NULL;
 16 | 
 17 |   delims = cmark_inline_parser_scan_delimiters(
 18 |       inline_parser, sizeof(buffer) - 1, '~',
 19 |       &left_flanking,
 20 |       &right_flanking, &punct_before, &punct_after);
 21 | 
 22 |   memset(buffer, '~', delims);
 23 |   buffer[delims] = 0;
 24 | 
 25 |   res = cmark_node_new_with_mem(CMARK_NODE_TEXT, parser->mem);
 26 |   cmark_node_set_literal(res, buffer);
 27 |   res->start_line = res->end_line = cmark_inline_parser_get_line(inline_parser);
 28 |   res->start_column = cmark_inline_parser_get_column(inline_parser) - delims;
 29 | 
 30 |   if ((left_flanking || right_flanking) &&
 31 |       (delims == 2 || (!(parser->options & CMARK_OPT_STRIKETHROUGH_DOUBLE_TILDE) && delims == 1))) {
 32 |     cmark_inline_parser_push_delimiter(inline_parser, character, left_flanking,
 33 |                                        right_flanking, res);
 34 |   }
 35 | 
 36 |   return res;
 37 | }
 38 | 
 39 | static delimiter *insert(cmark_syntax_extension *self, cmark_parser *parser,
 40 |                          cmark_inline_parser *inline_parser, delimiter *opener,
 41 |                          delimiter *closer) {
 42 |   cmark_node *strikethrough;
 43 |   cmark_node *tmp, *next;
 44 |   delimiter *delim, *tmp_delim;
 45 |   delimiter *res = closer->next;
 46 | 
 47 |   strikethrough = opener->inl_text;
 48 | 
 49 |   if (opener->inl_text->as.literal.len != closer->inl_text->as.literal.len)
 50 |     goto done;
 51 | 
 52 |   if (!cmark_node_set_type(strikethrough, CMARK_NODE_STRIKETHROUGH))
 53 |     goto done;
 54 | 
 55 |   cmark_node_set_syntax_extension(strikethrough, self);
 56 | 
 57 |   tmp = cmark_node_next(opener->inl_text);
 58 | 
 59 |   while (tmp) {
 60 |     if (tmp == closer->inl_text)
 61 |       break;
 62 |     next = cmark_node_next(tmp);
 63 |     cmark_node_append_child(strikethrough, tmp);
 64 |     tmp = next;
 65 |   }
 66 | 
 67 |   strikethrough->end_column = closer->inl_text->start_column + closer->inl_text->as.literal.len - 1;
 68 |   cmark_node_free(closer->inl_text);
 69 | 
 70 | done:
 71 |   delim = closer;
 72 |   while (delim != NULL && delim != opener) {
 73 |     tmp_delim = delim->previous;
 74 |     cmark_inline_parser_remove_delimiter(inline_parser, delim);
 75 |     delim = tmp_delim;
 76 |   }
 77 | 
 78 |   cmark_inline_parser_remove_delimiter(inline_parser, opener);
 79 | 
 80 |   return res;
 81 | }
 82 | 
 83 | static const char *get_type_string(cmark_syntax_extension *extension,
 84 |                                    cmark_node *node) {
 85 |   return node->type == CMARK_NODE_STRIKETHROUGH ? "strikethrough" : "";
 86 | }
 87 | 
 88 | static int can_contain(cmark_syntax_extension *extension, cmark_node *node,
 89 |                        cmark_node_type child_type) {
 90 |   if (node->type != CMARK_NODE_STRIKETHROUGH)
 91 |     return false;
 92 | 
 93 |   return CMARK_NODE_TYPE_INLINE_P(child_type);
 94 | }
 95 | 
 96 | static void commonmark_render(cmark_syntax_extension *extension,
 97 |                               cmark_renderer *renderer, cmark_node *node,
 98 |                               cmark_event_type ev_type, int options) {
 99 |   renderer->out(renderer, node, "~~", false, LITERAL);
100 | }
101 | 
102 | static void latex_render(cmark_syntax_extension *extension,
103 |                          cmark_renderer *renderer, cmark_node *node,
104 |                          cmark_event_type ev_type, int options) {
105 |   // requires \usepackage{ulem}
106 |   bool entering = (ev_type == CMARK_EVENT_ENTER);
107 |   if (entering) {
108 |     renderer->out(renderer, node, "\\sout{", false, LITERAL);
109 |   } else {
110 |     renderer->out(renderer, node, "}", false, LITERAL);
111 |   }
112 | }
113 | 
114 | static void man_render(cmark_syntax_extension *extension,
115 |                        cmark_renderer *renderer, cmark_node *node,
116 |                        cmark_event_type ev_type, int options) {
117 |   bool entering = (ev_type == CMARK_EVENT_ENTER);
118 |   if (entering) {
119 |     renderer->cr(renderer);
120 |     renderer->out(renderer, node, ".ST \"", false, LITERAL);
121 |   } else {
122 |     renderer->out(renderer, node, "\"", false, LITERAL);
123 |     renderer->cr(renderer);
124 |   }
125 | }
126 | 
127 | static void html_render(cmark_syntax_extension *extension,
128 |                         cmark_html_renderer *renderer, cmark_node *node,
129 |                         cmark_event_type ev_type, int options) {
130 |   bool entering = (ev_type == CMARK_EVENT_ENTER);
131 |   if (entering) {
132 |     cmark_strbuf_puts(renderer->html, "");
133 |   } else {
134 |     cmark_strbuf_puts(renderer->html, "");
135 |   }
136 | }
137 | 
138 | static void plaintext_render(cmark_syntax_extension *extension,
139 |                              cmark_renderer *renderer, cmark_node *node,
140 |                              cmark_event_type ev_type, int options) {
141 |   renderer->out(renderer, node, "~", false, LITERAL);
142 | }
143 | 
144 | cmark_syntax_extension *create_strikethrough_extension(void) {
145 |   cmark_syntax_extension *ext = cmark_syntax_extension_new("strikethrough");
146 |   cmark_llist *special_chars = NULL;
147 | 
148 |   cmark_syntax_extension_set_get_type_string_func(ext, get_type_string);
149 |   cmark_syntax_extension_set_can_contain_func(ext, can_contain);
150 |   cmark_syntax_extension_set_commonmark_render_func(ext, commonmark_render);
151 |   cmark_syntax_extension_set_latex_render_func(ext, latex_render);
152 |   cmark_syntax_extension_set_man_render_func(ext, man_render);
153 |   cmark_syntax_extension_set_html_render_func(ext, html_render);
154 |   cmark_syntax_extension_set_plaintext_render_func(ext, plaintext_render);
155 |   CMARK_NODE_STRIKETHROUGH = cmark_syntax_extension_add_node(1);
156 | 
157 |   cmark_syntax_extension_set_match_inline_func(ext, match);
158 |   cmark_syntax_extension_set_inline_from_delim_func(ext, insert);
159 | 
160 |   cmark_mem *mem = cmark_get_default_mem_allocator();
161 |   special_chars = cmark_llist_append(mem, special_chars, (void *)'~');
162 |   cmark_syntax_extension_set_special_inline_chars(ext, special_chars);
163 | 
164 |   cmark_syntax_extension_set_emphasis(ext, 1);
165 | 
166 |   return ext;
167 | }
168 | 


--------------------------------------------------------------------------------
/ext/qiita_marker/strikethrough.h:
--------------------------------------------------------------------------------
 1 | #ifndef CMARK_GFM_STRIKETHROUGH_H
 2 | #define CMARK_GFM_STRIKETHROUGH_H
 3 | 
 4 | #include "cmark-gfm-core-extensions.h"
 5 | 
 6 | extern cmark_node_type CMARK_NODE_STRIKETHROUGH;
 7 | cmark_syntax_extension *create_strikethrough_extension(void);
 8 | 
 9 | #endif
10 | 


--------------------------------------------------------------------------------
/ext/qiita_marker/syntax_extension.c:
--------------------------------------------------------------------------------
  1 | #include 
  2 | #include 
  3 | 
  4 | #include "cmark-gfm.h"
  5 | #include "syntax_extension.h"
  6 | #include "buffer.h"
  7 | 
  8 | extern cmark_mem CMARK_DEFAULT_MEM_ALLOCATOR;
  9 | 
 10 | static cmark_mem *_mem = &CMARK_DEFAULT_MEM_ALLOCATOR;
 11 | 
 12 | void cmark_syntax_extension_free(cmark_mem *mem, cmark_syntax_extension *extension) {
 13 |   if (extension->free_function && extension->priv) {
 14 |     extension->free_function(mem, extension->priv);
 15 |   }
 16 | 
 17 |   cmark_llist_free(mem, extension->special_inline_chars);
 18 |   mem->free(extension->name);
 19 |   mem->free(extension);
 20 | }
 21 | 
 22 | cmark_syntax_extension *cmark_syntax_extension_new(const char *name) {
 23 |   cmark_syntax_extension *res = (cmark_syntax_extension *) _mem->calloc(1, sizeof(cmark_syntax_extension));
 24 |   res->name = (char *) _mem->calloc(1, sizeof(char) * (strlen(name)) + 1);
 25 |   strcpy(res->name, name);
 26 |   return res;
 27 | }
 28 | 
 29 | cmark_node_type cmark_syntax_extension_add_node(int is_inline) {
 30 |   cmark_node_type *ref = !is_inline ? &CMARK_NODE_LAST_BLOCK : &CMARK_NODE_LAST_INLINE;
 31 | 
 32 |   if ((*ref & CMARK_NODE_VALUE_MASK) == CMARK_NODE_VALUE_MASK) {
 33 |     assert(false);
 34 |     return (cmark_node_type) 0;
 35 |   }
 36 | 
 37 |   return *ref = (cmark_node_type) ((int) *ref + 1);
 38 | }
 39 | 
 40 | void cmark_syntax_extension_set_emphasis(cmark_syntax_extension *extension,
 41 |                                          int emphasis) {
 42 |   extension->emphasis = emphasis == 1;
 43 | }
 44 | 
 45 | void cmark_syntax_extension_set_open_block_func(cmark_syntax_extension *extension,
 46 |                                                 cmark_open_block_func func) {
 47 |   extension->try_opening_block = func;
 48 | }
 49 | 
 50 | void cmark_syntax_extension_set_match_block_func(cmark_syntax_extension *extension,
 51 |                                                  cmark_match_block_func func) {
 52 |   extension->last_block_matches = func;
 53 | }
 54 | 
 55 | void cmark_syntax_extension_set_match_inline_func(cmark_syntax_extension *extension,
 56 |                                                   cmark_match_inline_func func) {
 57 |   extension->match_inline = func;
 58 | }
 59 | 
 60 | void cmark_syntax_extension_set_inline_from_delim_func(cmark_syntax_extension *extension,
 61 |                                                        cmark_inline_from_delim_func func) {
 62 |   extension->insert_inline_from_delim = func;
 63 | }
 64 | 
 65 | void cmark_syntax_extension_set_special_inline_chars(cmark_syntax_extension *extension,
 66 |                                                      cmark_llist *special_chars) {
 67 |   extension->special_inline_chars = special_chars;
 68 | }
 69 | 
 70 | void cmark_syntax_extension_set_get_type_string_func(cmark_syntax_extension *extension,
 71 |                                                      cmark_get_type_string_func func) {
 72 |   extension->get_type_string_func = func;
 73 | }
 74 | 
 75 | void cmark_syntax_extension_set_can_contain_func(cmark_syntax_extension *extension,
 76 |                                                  cmark_can_contain_func func) {
 77 |   extension->can_contain_func = func;
 78 | }
 79 | 
 80 | void cmark_syntax_extension_set_contains_inlines_func(cmark_syntax_extension *extension,
 81 |                                                       cmark_contains_inlines_func func) {
 82 |   extension->contains_inlines_func = func;
 83 | }
 84 | 
 85 | void cmark_syntax_extension_set_commonmark_render_func(cmark_syntax_extension *extension,
 86 |                                                        cmark_common_render_func func) {
 87 |   extension->commonmark_render_func = func;
 88 | }
 89 | 
 90 | void cmark_syntax_extension_set_plaintext_render_func(cmark_syntax_extension *extension,
 91 |                                                       cmark_common_render_func func) {
 92 |   extension->plaintext_render_func = func;
 93 | }
 94 | 
 95 | void cmark_syntax_extension_set_latex_render_func(cmark_syntax_extension *extension,
 96 |                                                   cmark_common_render_func func) {
 97 |   extension->latex_render_func = func;
 98 | }
 99 | 
100 | void cmark_syntax_extension_set_xml_attr_func(cmark_syntax_extension *extension,
101 |                                               cmark_xml_attr_func func) {
102 |   extension->xml_attr_func = func;
103 | }
104 | 
105 | void cmark_syntax_extension_set_man_render_func(cmark_syntax_extension *extension,
106 |                                                 cmark_common_render_func func) {
107 |   extension->man_render_func = func;
108 | }
109 | 
110 | void cmark_syntax_extension_set_html_render_func(cmark_syntax_extension *extension,
111 |                                                  cmark_html_render_func func) {
112 |   extension->html_render_func = func;
113 | }
114 | 
115 | void cmark_syntax_extension_set_html_filter_func(cmark_syntax_extension *extension,
116 |                                                  cmark_html_filter_func func) {
117 |   extension->html_filter_func = func;
118 | }
119 | 
120 | void cmark_syntax_extension_set_postprocess_func(cmark_syntax_extension *extension,
121 |                                                  cmark_postprocess_func func) {
122 |   extension->postprocess_func = func;
123 | }
124 | 
125 | void cmark_syntax_extension_set_private(cmark_syntax_extension *extension,
126 |                                         void *priv,
127 |                                         cmark_free_func free_func) {
128 |   extension->priv = priv;
129 |   extension->free_function = free_func;
130 | }
131 | 
132 | void *cmark_syntax_extension_get_private(cmark_syntax_extension *extension) {
133 |     return extension->priv;
134 | }
135 | 
136 | void cmark_syntax_extension_set_opaque_alloc_func(cmark_syntax_extension *extension,
137 |                                                   cmark_opaque_alloc_func func) {
138 |   extension->opaque_alloc_func = func;
139 | }
140 | 
141 | void cmark_syntax_extension_set_opaque_free_func(cmark_syntax_extension *extension,
142 |                                                  cmark_opaque_free_func func) {
143 |   extension->opaque_free_func = func;
144 | }
145 | 
146 | void cmark_syntax_extension_set_commonmark_escape_func(cmark_syntax_extension *extension,
147 |                                                        cmark_commonmark_escape_func func) {
148 |   extension->commonmark_escape_func = func;
149 | }
150 | 


--------------------------------------------------------------------------------
/ext/qiita_marker/syntax_extension.h:
--------------------------------------------------------------------------------
 1 | #ifndef CMARK_SYNTAX_EXTENSION_H
 2 | #define CMARK_SYNTAX_EXTENSION_H
 3 | 
 4 | #include "cmark-gfm.h"
 5 | #include "cmark-gfm-extension_api.h"
 6 | #include "config.h"
 7 | 
 8 | struct cmark_syntax_extension {
 9 |   cmark_match_block_func          last_block_matches;
10 |   cmark_open_block_func           try_opening_block;
11 |   cmark_match_inline_func         match_inline;
12 |   cmark_inline_from_delim_func    insert_inline_from_delim;
13 |   cmark_llist                   * special_inline_chars;
14 |   char                          * name;
15 |   void                          * priv;
16 |   bool                            emphasis;
17 |   cmark_free_func                 free_function;
18 |   cmark_get_type_string_func      get_type_string_func;
19 |   cmark_can_contain_func          can_contain_func;
20 |   cmark_contains_inlines_func     contains_inlines_func;
21 |   cmark_common_render_func        commonmark_render_func;
22 |   cmark_common_render_func        plaintext_render_func;
23 |   cmark_common_render_func        latex_render_func;
24 |   cmark_xml_attr_func             xml_attr_func;
25 |   cmark_common_render_func        man_render_func;
26 |   cmark_html_render_func          html_render_func;
27 |   cmark_html_filter_func          html_filter_func;
28 |   cmark_postprocess_func          postprocess_func;
29 |   cmark_opaque_alloc_func         opaque_alloc_func;
30 |   cmark_opaque_free_func          opaque_free_func;
31 |   cmark_commonmark_escape_func    commonmark_escape_func;
32 | };
33 | 
34 | #endif
35 | 


--------------------------------------------------------------------------------
/ext/qiita_marker/table.h:
--------------------------------------------------------------------------------
 1 | #ifndef CMARK_GFM_TABLE_H
 2 | #define CMARK_GFM_TABLE_H
 3 | 
 4 | #include "cmark-gfm-core-extensions.h"
 5 | 
 6 | 
 7 | extern cmark_node_type CMARK_NODE_TABLE, CMARK_NODE_TABLE_ROW,
 8 |     CMARK_NODE_TABLE_CELL;
 9 | 
10 | cmark_syntax_extension *create_table_extension(void);
11 | 
12 | #endif
13 | 


--------------------------------------------------------------------------------
/ext/qiita_marker/tagfilter.c:
--------------------------------------------------------------------------------
 1 | #include "tagfilter.h"
 2 | #include 
 3 | #include 
 4 | 
 5 | static const char *blacklist[] = {
 6 |     "title",   "textarea", "style",  "xmp",       "iframe",
 7 |     "noembed", "noframes", "script", "plaintext", NULL,
 8 | };
 9 | 
10 | static int is_tag(const unsigned char *tag_data, size_t tag_size,
11 |                   const char *tagname) {
12 |   size_t i;
13 | 
14 |   if (tag_size < 3 || tag_data[0] != '<')
15 |     return 0;
16 | 
17 |   i = 1;
18 | 
19 |   if (tag_data[i] == '/') {
20 |     i++;
21 |   }
22 | 
23 |   for (; i < tag_size; ++i, ++tagname) {
24 |     if (*tagname == 0)
25 |       break;
26 | 
27 |     if (tolower(tag_data[i]) != *tagname)
28 |       return 0;
29 |   }
30 | 
31 |   if (i == tag_size)
32 |     return 0;
33 | 
34 |   if (cmark_isspace(tag_data[i]) || tag_data[i] == '>')
35 |     return 1;
36 | 
37 |   if (tag_data[i] == '/' && tag_size >= i + 2 && tag_data[i + 1] == '>')
38 |     return 1;
39 | 
40 |   return 0;
41 | }
42 | 
43 | static int filter(cmark_syntax_extension *ext, const unsigned char *tag,
44 |                   size_t tag_len) {
45 |   const char **it;
46 | 
47 |   for (it = blacklist; *it; ++it) {
48 |     if (is_tag(tag, tag_len, *it)) {
49 |       return 0;
50 |     }
51 |   }
52 | 
53 |   return 1;
54 | }
55 | 
56 | cmark_syntax_extension *create_tagfilter_extension(void) {
57 |   cmark_syntax_extension *ext = cmark_syntax_extension_new("tagfilter");
58 |   cmark_syntax_extension_set_html_filter_func(ext, filter);
59 |   return ext;
60 | }
61 | 


--------------------------------------------------------------------------------
/ext/qiita_marker/tagfilter.h:
--------------------------------------------------------------------------------
1 | #ifndef CMARK_GFM_TAGFILTER_H
2 | #define CMARK_GFM_TAGFILTER_H
3 | 
4 | #include "cmark-gfm-core-extensions.h"
5 | 
6 | cmark_syntax_extension *create_tagfilter_extension(void);
7 | 
8 | #endif
9 | 


--------------------------------------------------------------------------------
/ext/qiita_marker/tasklist.c:
--------------------------------------------------------------------------------
  1 | #include "tasklist.h"
  2 | #include 
  3 | #include 
  4 | #include 
  5 | #include "ext_scanners.h"
  6 | 
  7 | typedef enum {
  8 |   CMARK_TASKLIST_NOCHECKED,
  9 |   CMARK_TASKLIST_CHECKED,
 10 | } cmark_tasklist_type;
 11 | 
 12 | // Local constants
 13 | static const char *TYPE_STRING = "tasklist";
 14 | 
 15 | static const char *get_type_string(cmark_syntax_extension *extension, cmark_node *node) {
 16 |   return TYPE_STRING;
 17 | }
 18 | 
 19 | 
 20 | // Return 1 if state was set, 0 otherwise
 21 | int cmark_gfm_extensions_set_tasklist_item_checked(cmark_node *node, bool is_checked) {
 22 |   // The node has to exist, and be an extension, and actually be the right type in order to get the value.
 23 |   if (!node || !node->extension || strcmp(cmark_node_get_type_string(node), TYPE_STRING))
 24 |     return 0;
 25 | 
 26 |   node->as.list.checked = is_checked;
 27 |   return 1;
 28 | }
 29 | 
 30 | bool cmark_gfm_extensions_get_tasklist_item_checked(cmark_node *node) {
 31 |   if (!node || !node->extension || strcmp(cmark_node_get_type_string(node), TYPE_STRING))
 32 |     return false;
 33 | 
 34 |   if (node->as.list.checked) {
 35 |     return true;
 36 |   }
 37 |   else {
 38 |     return false;
 39 |   }
 40 | }
 41 | 
 42 | static bool parse_node_item_prefix(cmark_parser *parser, const char *input,
 43 |                                    cmark_node *container) {
 44 |   bool res = false;
 45 | 
 46 |   if (parser->indent >=
 47 |       container->as.list.marker_offset + container->as.list.padding) {
 48 |     cmark_parser_advance_offset(parser, input, container->as.list.marker_offset +
 49 |                                         container->as.list.padding,
 50 |                      true);
 51 |     res = true;
 52 |   } else if (parser->blank && container->first_child != NULL) {
 53 |     // if container->first_child is NULL, then the opening line
 54 |     // of the list item was blank after the list marker; in this
 55 |     // case, we are done with the list item.
 56 |     cmark_parser_advance_offset(parser, input, parser->first_nonspace - parser->offset,
 57 |                      false);
 58 |     res = true;
 59 |   }
 60 |   return res;
 61 | }
 62 | 
 63 | static int matches(cmark_syntax_extension *self, cmark_parser *parser,
 64 |                    unsigned char *input, int len,
 65 |                    cmark_node *parent_container) {
 66 |   return parse_node_item_prefix(parser, (const char*)input, parent_container);
 67 | }
 68 | 
 69 | static int can_contain(cmark_syntax_extension *extension, cmark_node *node,
 70 |                        cmark_node_type child_type) {
 71 |   return (node->type == CMARK_NODE_ITEM) ? 1 : 0;
 72 | }
 73 | 
 74 | static cmark_node *open_tasklist_item(cmark_syntax_extension *self,
 75 |                                       int indented, cmark_parser *parser,
 76 |                                       cmark_node *parent_container,
 77 |                                       unsigned char *input, int len) {
 78 |   cmark_node_type node_type = cmark_node_get_type(parent_container);
 79 |   if (node_type != CMARK_NODE_ITEM) {
 80 |     return NULL;
 81 |   }
 82 | 
 83 |   bufsize_t matched = scan_tasklist(input, len, 0);
 84 |   if (!matched) {
 85 |     return NULL;
 86 |   }
 87 | 
 88 |   cmark_node_set_syntax_extension(parent_container, self);
 89 |   cmark_parser_advance_offset(parser, (char *)input, 3, false);
 90 | 
 91 |   // Either an upper or lower case X means the task is completed.
 92 |   parent_container->as.list.checked = (strstr((char*)input, "[x]") || strstr((char*)input, "[X]"));
 93 | 
 94 |   return NULL;
 95 | }
 96 | 
 97 | static void commonmark_render(cmark_syntax_extension *extension,
 98 |                               cmark_renderer *renderer, cmark_node *node,
 99 |                               cmark_event_type ev_type, int options) {
100 |   bool entering = (ev_type == CMARK_EVENT_ENTER);
101 |   if (entering) {
102 |     renderer->cr(renderer);
103 |     if (node->as.list.checked) {
104 |       renderer->out(renderer, node, "- [x] ", false, LITERAL);
105 |     } else {
106 |       renderer->out(renderer, node, "- [ ] ", false, LITERAL);
107 |     }
108 |     cmark_strbuf_puts(renderer->prefix, "  ");
109 |   } else {
110 |     cmark_strbuf_truncate(renderer->prefix, renderer->prefix->size - 2);
111 |     renderer->cr(renderer);
112 |   }
113 | }
114 | 
115 | static void html_render(cmark_syntax_extension *extension,
116 |                         cmark_html_renderer *renderer, cmark_node *node,
117 |                         cmark_event_type ev_type, int options) {
118 |   bool entering = (ev_type == CMARK_EVENT_ENTER);
119 |   if (entering) {
120 |     cmark_html_render_cr(renderer->html);
121 |     cmark_strbuf_puts(renderer->html, "html, options);
123 |     cmark_strbuf_putc(renderer->html, '>');
124 |     if (node->as.list.checked) {
125 |       cmark_strbuf_puts(renderer->html, " ");
126 |     } else {
127 |       cmark_strbuf_puts(renderer->html, " ");
128 |     }
129 |   } else {
130 |     cmark_strbuf_puts(renderer->html, "\n");
131 |   }
132 | }
133 | 
134 | static const char *xml_attr(cmark_syntax_extension *extension,
135 |                             cmark_node *node) {
136 |   if (node->as.list.checked) {
137 |     return " completed=\"true\"";
138 |   } else {
139 |     return " completed=\"false\"";
140 |   }
141 | }
142 | 
143 | cmark_syntax_extension *create_tasklist_extension(void) {
144 |   cmark_syntax_extension *ext = cmark_syntax_extension_new("tasklist");
145 | 
146 |   cmark_syntax_extension_set_match_block_func(ext, matches);
147 |   cmark_syntax_extension_set_get_type_string_func(ext, get_type_string);
148 |   cmark_syntax_extension_set_open_block_func(ext, open_tasklist_item);
149 |   cmark_syntax_extension_set_can_contain_func(ext, can_contain);
150 |   cmark_syntax_extension_set_commonmark_render_func(ext, commonmark_render);
151 |   cmark_syntax_extension_set_plaintext_render_func(ext, commonmark_render);
152 |   cmark_syntax_extension_set_html_render_func(ext, html_render);
153 |   cmark_syntax_extension_set_xml_attr_func(ext, xml_attr);
154 | 
155 |   return ext;
156 | }
157 | 


--------------------------------------------------------------------------------
/ext/qiita_marker/tasklist.h:
--------------------------------------------------------------------------------
1 | #ifndef TASKLIST_H
2 | #define TASKLIST_H
3 | 
4 | #include "cmark-gfm-core-extensions.h"
5 | 
6 | cmark_syntax_extension *create_tasklist_extension(void);
7 | 
8 | #endif
9 | 


--------------------------------------------------------------------------------
/ext/qiita_marker/utf8.h:
--------------------------------------------------------------------------------
 1 | #ifndef CMARK_UTF8_H
 2 | #define CMARK_UTF8_H
 3 | 
 4 | #include 
 5 | #include "buffer.h"
 6 | 
 7 | #ifdef __cplusplus
 8 | extern "C" {
 9 | #endif
10 | 
11 | CMARK_GFM_EXPORT
12 | void cmark_utf8proc_case_fold(cmark_strbuf *dest, const uint8_t *str,
13 |                               bufsize_t len);
14 | 
15 | CMARK_GFM_EXPORT
16 | void cmark_utf8proc_encode_char(int32_t uc, cmark_strbuf *buf);
17 | 
18 | CMARK_GFM_EXPORT
19 | int cmark_utf8proc_iterate(const uint8_t *str, bufsize_t str_len, int32_t *dst);
20 | 
21 | CMARK_GFM_EXPORT
22 | void cmark_utf8proc_check(cmark_strbuf *dest, const uint8_t *line,
23 |                           bufsize_t size);
24 | 
25 | CMARK_GFM_EXPORT
26 | int cmark_utf8proc_is_space(int32_t uc);
27 | 
28 | CMARK_GFM_EXPORT
29 | int cmark_utf8proc_is_punctuation(int32_t uc);
30 | 
31 | #ifdef __cplusplus
32 | }
33 | #endif
34 | 
35 | #endif
36 | 


--------------------------------------------------------------------------------
/ext/qiita_marker/xml.c:
--------------------------------------------------------------------------------
  1 | #include 
  2 | #include 
  3 | #include 
  4 | #include 
  5 | 
  6 | #include "config.h"
  7 | #include "cmark-gfm.h"
  8 | #include "node.h"
  9 | #include "buffer.h"
 10 | #include "houdini.h"
 11 | #include "syntax_extension.h"
 12 | 
 13 | #define BUFFER_SIZE 100
 14 | #define MAX_INDENT 40
 15 | 
 16 | // Functions to convert cmark_nodes to XML strings.
 17 | 
 18 | static void escape_xml(cmark_strbuf *dest, const unsigned char *source,
 19 |                        bufsize_t length) {
 20 |   houdini_escape_html0(dest, source, length, 0);
 21 | }
 22 | 
 23 | struct render_state {
 24 |   cmark_strbuf *xml;
 25 |   int indent;
 26 | };
 27 | 
 28 | static CMARK_INLINE void indent(struct render_state *state) {
 29 |   int i;
 30 |   for (i = 0; i < state->indent && i < MAX_INDENT; i++) {
 31 |     cmark_strbuf_putc(state->xml, ' ');
 32 |   }
 33 | }
 34 | 
 35 | static int S_render_node(cmark_node *node, cmark_event_type ev_type,
 36 |                          struct render_state *state, int options) {
 37 |   cmark_strbuf *xml = state->xml;
 38 |   bool literal = false;
 39 |   cmark_delim_type delim;
 40 |   bool entering = (ev_type == CMARK_EVENT_ENTER);
 41 |   char buffer[BUFFER_SIZE];
 42 | 
 43 |   if (entering) {
 44 |     indent(state);
 45 |     cmark_strbuf_putc(xml, '<');
 46 |     cmark_strbuf_puts(xml, cmark_node_get_type_string(node));
 47 | 
 48 |     if (options & CMARK_OPT_SOURCEPOS && node->start_line != 0) {
 49 |       snprintf(buffer, BUFFER_SIZE, " sourcepos=\"%d:%d-%d:%d\"",
 50 |                node->start_line, node->start_column, node->end_line,
 51 |                node->end_column);
 52 |       cmark_strbuf_puts(xml, buffer);
 53 |     }
 54 | 
 55 |     if (node->extension && node->extension->xml_attr_func) {
 56 |       const char* r = node->extension->xml_attr_func(node->extension, node);
 57 |       if (r != NULL)
 58 |         cmark_strbuf_puts(xml, r);
 59 |     }
 60 | 
 61 |     literal = false;
 62 | 
 63 |     switch (node->type) {
 64 |     case CMARK_NODE_DOCUMENT:
 65 |       cmark_strbuf_puts(xml, " xmlns=\"http://commonmark.org/xml/1.0\"");
 66 |       break;
 67 |     case CMARK_NODE_TEXT:
 68 |     case CMARK_NODE_CODE:
 69 |     case CMARK_NODE_HTML_BLOCK:
 70 |     case CMARK_NODE_HTML_INLINE:
 71 |       cmark_strbuf_puts(xml, " xml:space=\"preserve\">");
 72 |       escape_xml(xml, node->as.literal.data, node->as.literal.len);
 73 |       cmark_strbuf_puts(xml, "as.heading.level);
103 |       cmark_strbuf_puts(xml, buffer);
104 |       break;
105 |     case CMARK_NODE_CODE_BLOCK:
106 |       if (node->as.code.info.len > 0) {
107 |         cmark_strbuf_puts(xml, " info=\"");
108 |         escape_xml(xml, node->as.code.info.data, node->as.code.info.len);
109 |         cmark_strbuf_putc(xml, '"');
110 |       }
111 |       cmark_strbuf_puts(xml, " xml:space=\"preserve\">");
112 |       escape_xml(xml, node->as.code.literal.data, node->as.code.literal.len);
113 |       cmark_strbuf_puts(xml, "as.custom.on_enter.data,
121 |                  node->as.custom.on_enter.len);
122 |       cmark_strbuf_putc(xml, '"');
123 |       cmark_strbuf_puts(xml, " on_exit=\"");
124 |       escape_xml(xml, node->as.custom.on_exit.data,
125 |                  node->as.custom.on_exit.len);
126 |       cmark_strbuf_putc(xml, '"');
127 |       break;
128 |     case CMARK_NODE_LINK:
129 |     case CMARK_NODE_IMAGE:
130 |       cmark_strbuf_puts(xml, " destination=\"");
131 |       escape_xml(xml, node->as.link.url.data, node->as.link.url.len);
132 |       cmark_strbuf_putc(xml, '"');
133 |       cmark_strbuf_puts(xml, " title=\"");
134 |       escape_xml(xml, node->as.link.title.data, node->as.link.title.len);
135 |       cmark_strbuf_putc(xml, '"');
136 |       break;
137 |     default:
138 |       break;
139 |     }
140 |     if (node->first_child) {
141 |       state->indent += 2;
142 |     } else if (!literal) {
143 |       cmark_strbuf_puts(xml, " /");
144 |     }
145 |     cmark_strbuf_puts(xml, ">\n");
146 | 
147 |   } else if (node->first_child) {
148 |     state->indent -= 2;
149 |     indent(state);
150 |     cmark_strbuf_puts(xml, "\n");
153 |   }
154 | 
155 |   return 1;
156 | }
157 | 
158 | char *cmark_render_xml(cmark_node *root, int options) {
159 |   return cmark_render_xml_with_mem(root, options, cmark_node_mem(root));
160 | }
161 | 
162 | char *cmark_render_xml_with_mem(cmark_node *root, int options, cmark_mem *mem) {
163 |   char *result;
164 |   cmark_strbuf xml = CMARK_BUF_INIT(mem);
165 |   cmark_event_type ev_type;
166 |   cmark_node *cur;
167 |   struct render_state state = {&xml, 0};
168 | 
169 |   cmark_iter *iter = cmark_iter_new(root);
170 | 
171 |   cmark_strbuf_puts(state.xml, "\n");
172 |   cmark_strbuf_puts(state.xml,
173 |                     "\n");
174 |   while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) {
175 |     cur = cmark_iter_get_node(iter);
176 |     S_render_node(cur, ev_type, &state, options);
177 |   }
178 |   result = (char *)cmark_strbuf_detach(&xml);
179 | 
180 |   cmark_iter_free(iter);
181 |   return result;
182 | }
183 | 


--------------------------------------------------------------------------------
/lib/qiita_marker.rb:
--------------------------------------------------------------------------------
 1 | #!/usr/bin/env ruby
 2 | # frozen_string_literal: true
 3 | 
 4 | require "qiita_marker/qiita_marker"
 5 | require "qiita_marker/config"
 6 | require "qiita_marker/node"
 7 | require "qiita_marker/renderer"
 8 | require "qiita_marker/renderer/html_renderer"
 9 | require "qiita_marker/version"
10 | 
11 | begin
12 |   require "awesome_print"
13 | rescue LoadError; end # rubocop:disable Lint/SuppressedException
14 | module QiitaMarker
15 |   class << self
16 |     # Public:  Parses a Markdown string into an HTML string.
17 |     #
18 |     # text - A {String} of text
19 |     # option - Either a {Symbol} or {Array of Symbol}s indicating the render options
20 |     # extensions - An {Array of Symbol}s indicating the extensions to use
21 |     #
22 |     # Returns a {String} of converted HTML.
23 |     def render_html(text, options = :DEFAULT, extensions = [])
24 |       raise TypeError, "text must be a String; got a #{text.class}!" unless text.is_a?(String)
25 | 
26 |       opts = Config.process_options(options, :render)
27 |       Node.markdown_to_html(text.encode("UTF-8"), opts, extensions)
28 |     end
29 | 
30 |     # Public: Parses a Markdown string into a `document` node.
31 |     #
32 |     # string - {String} to be parsed
33 |     # option - A {Symbol} or {Array of Symbol}s indicating the parse options
34 |     # extensions - An {Array of Symbol}s indicating the extensions to use
35 |     #
36 |     # Returns the `document` node.
37 |     def render_doc(text, options = :DEFAULT, extensions = [])
38 |       raise TypeError, "text must be a String; got a #{text.class}!" unless text.is_a?(String)
39 | 
40 |       opts = Config.process_options(options, :parse)
41 |       text = text.encode("UTF-8")
42 |       Node.parse_document(text, text.bytesize, opts, extensions)
43 |     end
44 |   end
45 | end
46 | 


--------------------------------------------------------------------------------
/lib/qiita_marker/config.rb:
--------------------------------------------------------------------------------
 1 | # frozen_string_literal: true
 2 | 
 3 | module QiitaMarker
 4 |   # For Ruby::Enum, these must be classes, not modules
 5 |   module Config
 6 |     # See https://github.com/github/cmark-gfm/blob/master/src/cmark-gfm.h#L673
 7 |     OPTS = {
 8 |       parse: {
 9 |         DEFAULT: 0,
10 |         SOURCEPOS: (1 << 1),
11 |         UNSAFE: (1 << 17),
12 |         VALIDATE_UTF8: (1 << 9),
13 |         SMART: (1 << 10),
14 |         LIBERAL_HTML_TAG: (1 << 12),
15 |         FOOTNOTES: (1 << 13),
16 |         STRIKETHROUGH_DOUBLE_TILDE: (1 << 14),
17 |         MENTION_NO_EMPHASIS: (1 << 26),
18 |         AUTOLINK_CLASS_NAME: (1 << 27),
19 |       }.freeze,
20 |       render: {
21 |         DEFAULT: 0,
22 |         SOURCEPOS: (1 << 1),
23 |         HARDBREAKS: (1 << 2),
24 |         UNSAFE: (1 << 17),
25 |         NOBREAKS: (1 << 4),
26 |         VALIDATE_UTF8: (1 << 9),
27 |         SMART: (1 << 10),
28 |         GITHUB_PRE_LANG: (1 << 11),
29 |         LIBERAL_HTML_TAG: (1 << 12),
30 |         FOOTNOTES: (1 << 13),
31 |         STRIKETHROUGH_DOUBLE_TILDE: (1 << 14),
32 |         TABLE_PREFER_STYLE_ATTRIBUTES: (1 << 15),
33 |         FULL_INFO_STRING: (1 << 16),
34 |         CODE_DATA_METADATA: (1 << 25),
35 |         MENTION_NO_EMPHASIS: (1 << 26),
36 |         AUTOLINK_CLASS_NAME: (1 << 27),
37 |       }.freeze,
38 |       format: [:html, :xml, :commonmark, :plaintext].freeze,
39 |     }.freeze
40 | 
41 |     class << self
42 |       def process_options(option, type)
43 |         case option
44 |         when Symbol
45 |           OPTS.fetch(type).fetch(option)
46 |         when Array
47 |           raise TypeError if option.none?
48 | 
49 |           # neckbearding around. the map will both check the opts and then bitwise-OR it
50 |           OPTS.fetch(type).fetch_values(*option).inject(0, :|)
51 |         else
52 |           raise TypeError, "option type must be a valid symbol or array of symbols within the #{name}::OPTS[:#{type}] context"
53 |         end
54 |       rescue KeyError => e
55 |         raise TypeError, "option ':#{e.key}' does not exist for #{name}::OPTS[:#{type}]"
56 |       end
57 |     end
58 |   end
59 | end
60 | 


--------------------------------------------------------------------------------
/lib/qiita_marker/node.rb:
--------------------------------------------------------------------------------
 1 | # frozen_string_literal: true
 2 | 
 3 | require "qiita_marker/node/inspect"
 4 | 
 5 | module QiitaMarker
 6 |   class Node
 7 |     include Enumerable
 8 |     include Inspect
 9 | 
10 |     # Public: An iterator that "walks the tree," descending into children recursively.
11 |     #
12 |     # blk - A {Proc} representing the action to take for each child
13 |     def walk(&block)
14 |       return enum_for(:walk) unless block
15 | 
16 |       yield self
17 |       each do |child|
18 |         child.walk(&block)
19 |       end
20 |     end
21 | 
22 |     # Public: Convert the node to an HTML string.
23 |     #
24 |     # options - A {Symbol} or {Array of Symbol}s indicating the render options
25 |     # extensions - An {Array of Symbol}s indicating the extensions to use
26 |     #
27 |     # Returns a {String}.
28 |     def to_html(options = :DEFAULT, extensions = [])
29 |       opts = Config.process_options(options, :render)
30 |       _render_html(opts, extensions).force_encoding("utf-8")
31 |     end
32 | 
33 |     # Public: Convert the node to an XML string.
34 |     #
35 |     # options - A {Symbol} or {Array of Symbol}s indicating the render options
36 |     #
37 |     # Returns a {String}.
38 |     def to_xml(options = :DEFAULT)
39 |       opts = Config.process_options(options, :render)
40 |       _render_xml(opts).force_encoding("utf-8")
41 |     end
42 | 
43 |     # Public: Convert the node to a CommonMark string.
44 |     #
45 |     # options - A {Symbol} or {Array of Symbol}s indicating the render options
46 |     # width - Column to wrap the output at
47 |     #
48 |     # Returns a {String}.
49 |     def to_commonmark(options = :DEFAULT, width = 120)
50 |       opts = Config.process_options(options, :render)
51 |       _render_commonmark(opts, width).force_encoding("utf-8")
52 |     end
53 | 
54 |     # Public: Convert the node to a plain text string.
55 |     #
56 |     # options - A {Symbol} or {Array of Symbol}s indicating the render options
57 |     # width - Column to wrap the output at
58 |     #
59 |     # Returns a {String}.
60 |     def to_plaintext(options = :DEFAULT, width = 120)
61 |       opts = Config.process_options(options, :render)
62 |       _render_plaintext(opts, width).force_encoding("utf-8")
63 |     end
64 | 
65 |     # Public: Iterate over the children (if any) of the current pointer.
66 |     def each
67 |       return enum_for(:each) unless block_given?
68 | 
69 |       child = first_child
70 |       while child
71 |         nextchild = child.next
72 |         yield child
73 |         child = nextchild
74 |       end
75 |     end
76 | 
77 |     # Deprecated: Please use `each` instead
78 |     def each_child(&block)
79 |       warn("[DEPRECATION] `each_child` is deprecated.  Please use `each` instead.")
80 |       each(&block)
81 |     end
82 |   end
83 | end
84 | 


--------------------------------------------------------------------------------
/lib/qiita_marker/node/inspect.rb:
--------------------------------------------------------------------------------
 1 | # frozen_string_literal: true
 2 | 
 3 | require "pp"
 4 | 
 5 | module QiitaMarker
 6 |   class Node
 7 |     module Inspect
 8 |       PP_INDENT_SIZE = 2
 9 | 
10 |       def inspect
11 |         PP.pp(self, +"", Float::INFINITY)
12 |       end
13 | 
14 |       # @param printer [PrettyPrint] pp
15 |       def pretty_print(printer)
16 |         printer.group(PP_INDENT_SIZE, "#<#{self.class}(#{type}):", ">") do
17 |           printer.breakable
18 | 
19 |           attrs = [:sourcepos, :string_content, :url, :title, :header_level, :list_type, :list_start, :list_tight, :fence_info].filter_map do |name|
20 |             [name, __send__(name)]
21 |           rescue NodeError
22 |             nil
23 |           end
24 | 
25 |           printer.seplist(attrs) do |name, value|
26 |             printer.text("#{name}=")
27 |             printer.pp(value)
28 |           end
29 | 
30 |           if first_child
31 |             printer.breakable
32 |             printer.group(PP_INDENT_SIZE) do
33 |               children = []
34 |               node = first_child
35 |               while node
36 |                 children << node
37 |                 node = node.next
38 |               end
39 |               printer.text("children=")
40 |               printer.pp(children)
41 |             end
42 |           end
43 |         end
44 |       end
45 |     end
46 |   end
47 | end
48 | 


--------------------------------------------------------------------------------
/lib/qiita_marker/renderer.rb:
--------------------------------------------------------------------------------
  1 | # frozen_string_literal: true
  2 | 
  3 | require "set"
  4 | require "stringio"
  5 | 
  6 | module QiitaMarker
  7 |   class Renderer
  8 |     attr_accessor :in_tight, :warnings, :in_plain
  9 | 
 10 |     def initialize(options: :DEFAULT, extensions: [])
 11 |       @opts = Config.process_options(options, :render)
 12 |       @stream = StringIO.new(+"")
 13 |       @need_blocksep = false
 14 |       @warnings = Set.new([])
 15 |       @in_tight = false
 16 |       @in_plain = false
 17 |       @tagfilter = extensions.include?(:tagfilter)
 18 |     end
 19 | 
 20 |     def out(*args)
 21 |       args.each do |arg|
 22 |         case arg
 23 |         when :children
 24 |           @node.each { |child| out(child) }
 25 |         when Array
 26 |           arg.each { |x| render(x) }
 27 |         when Node
 28 |           render(arg)
 29 |         else
 30 |           @stream.write(arg)
 31 |         end
 32 |       end
 33 |     end
 34 | 
 35 |     def render(node)
 36 |       @node = node
 37 |       if node.type == :document
 38 |         document(node)
 39 |         @stream.string
 40 |       elsif @in_plain && node.type != :text && node.type != :softbreak
 41 |         node.each { |child| render(child) }
 42 |       else
 43 |         begin
 44 |           send(node.type, node)
 45 |         rescue NoMethodError => e
 46 |           @warnings.add("WARNING: #{node.type} not implemented.")
 47 |           raise e
 48 |         end
 49 |       end
 50 |     end
 51 | 
 52 |     def document(_node)
 53 |       out(:children)
 54 |     end
 55 | 
 56 |     def code_block(node)
 57 |       code_block(node)
 58 |     end
 59 | 
 60 |     def reference_def(_node); end
 61 | 
 62 |     def cr
 63 |       return if @stream.string.empty? || @stream.string[-1] == "\n"
 64 | 
 65 |       out("\n")
 66 |     end
 67 | 
 68 |     def blocksep
 69 |       out("\n")
 70 |     end
 71 | 
 72 |     def containersep
 73 |       cr unless @in_tight
 74 |     end
 75 | 
 76 |     def block
 77 |       cr
 78 |       yield
 79 |       cr
 80 |     end
 81 | 
 82 |     def container(starter, ender)
 83 |       out(starter)
 84 |       yield
 85 |       out(ender)
 86 |     end
 87 | 
 88 |     def plain
 89 |       old_in_plain = @in_plain
 90 |       @in_plain = true
 91 |       yield
 92 |       @in_plain = old_in_plain
 93 |     end
 94 | 
 95 |     private
 96 | 
 97 |     def escape_href(str)
 98 |       @node.html_escape_href(str)
 99 |     end
100 | 
101 |     def escape_html(str)
102 |       @node.html_escape_html(str)
103 |     end
104 | 
105 |     def tagfilter(str)
106 |       if @tagfilter
107 |         str.gsub(
108 |           %r{
109 |             <
110 |             (
111 |             title|textarea|style|xmp|iframe|
112 |             noembed|noframes|script|plaintext
113 |             )
114 |             (?=\s|>|/>)
115 |           }xi,
116 |           '<\1',
117 |         )
118 |       else
119 |         str
120 |       end
121 |     end
122 | 
123 |     def sourcepos(node)
124 |       return "" unless option_enabled?(:SOURCEPOS)
125 | 
126 |       s = node.sourcepos
127 |       " data-sourcepos=\"#{s[:start_line]}:#{s[:start_column]}-" \
128 |         "#{s[:end_line]}:#{s[:end_column]}\""
129 |     end
130 | 
131 |     def option_enabled?(opt)
132 |       (@opts & QiitaMarker::Config::OPTS.dig(:render, opt)) != 0
133 |     end
134 |   end
135 | end
136 | 


--------------------------------------------------------------------------------
/lib/qiita_marker/version.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | 
3 | module QiitaMarker
4 |   VERSION = "0.23.9.0"
5 | end
6 | 


--------------------------------------------------------------------------------
/qiita_marker.gemspec:
--------------------------------------------------------------------------------
 1 | # frozen_string_literal: true
 2 | 
 3 | lib = File.expand_path("lib", __dir__)
 4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
 5 | require "qiita_marker/version"
 6 | 
 7 | Gem::Specification.new do |s|
 8 |   s.name = "qiita_marker"
 9 |   s.version = QiitaMarker::VERSION
10 |   s.summary = "Qiita Marker is a Ruby library for Markdown processing, based on CommonMarker."
11 |   s.description = "A Ruby library that is the core module of the Qiita-specified markdown processor."
12 |   s.authors = ["Qiita Inc."]
13 |   s.homepage = "https://github.com/increments/qiita_marker"
14 |   s.license = "MIT"
15 | 
16 |   s.files         = ["LICENSE.txt", "README.md", "Rakefile", "qiita_marker.gemspec", "bin/qiita_marker"]
17 |   s.files        += Dir.glob("lib/**/*.rb")
18 |   s.files        += Dir.glob("ext/qiita_marker/*.*")
19 |   s.extensions    = ["ext/qiita_marker/extconf.rb"]
20 | 
21 |   s.executables = ["qiita_marker"]
22 |   s.require_paths = ["lib", "ext"]
23 |   s.required_ruby_version = [">= 3.0", "< 4.0"]
24 | 
25 |   s.metadata["rubygems_mfa_required"] = "true"
26 | 
27 |   s.rdoc_options += ["-x", "ext/qiita_marker/cmark/.*"]
28 | 
29 |   s.add_development_dependency("awesome_print")
30 |   s.add_development_dependency("json", "~> 2.3")
31 |   s.add_development_dependency("minitest", "~> 5.6")
32 |   s.add_development_dependency("minitest-focus", "~> 1.1")
33 |   s.add_development_dependency("rake")
34 |   s.add_development_dependency("rake-compiler", "~> 0.9")
35 |   s.add_development_dependency("rdoc", "~> 6.2")
36 |   s.add_development_dependency("rubocop")
37 |   s.add_development_dependency("rubocop-standard")
38 | end
39 | 


--------------------------------------------------------------------------------
/script/bootstrap:
--------------------------------------------------------------------------------
 1 | #!/bin/bash
 2 | 
 3 | set -e
 4 | 
 5 | echo "==> Initing Git submodules"
 6 | 
 7 | git submodule update --init --recursive
 8 | 
 9 | echo "==> Installing gem dependencies…"
10 | 
11 | bundle install --path vendor/gems --standalone --clean "$@"
12 | 


--------------------------------------------------------------------------------
/script/changelog:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | 
3 | CHANGELOG_GITHUB_TOKEN="$PUBLIC_GITHUB_TOKEN" github_changelog_generator -u gjtorikian -p qiita_marker
4 | 


--------------------------------------------------------------------------------
/script/cibuild:
--------------------------------------------------------------------------------
 1 | #!/bin/sh
 2 | 
 3 | set -e
 4 | 
 5 | git submodule sync
 6 | git submodule update --init
 7 | bundle
 8 | bundle exec rake clean
 9 | bundle exec rake test
10 | 


--------------------------------------------------------------------------------
/script/single_test:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | 
3 | set -e
4 | 
5 | bundle exec rake compile
6 | bundle exec rake test TEST=test/$1
7 | 


--------------------------------------------------------------------------------
/script/update_submodules:
--------------------------------------------------------------------------------
 1 | #!/bin/bash
 2 | 
 3 | set -e
 4 | 
 5 | if [ -z "$1" ]; then
 6 |   BRANCH="main"
 7 | else
 8 |   BRANCH=$1
 9 | fi
10 | 
11 | echo "Using $BRANCH..."
12 | 
13 | echo "Checking out cmark-upstream"
14 | echo "---------------------"
15 | cd ext/qiita_marker/cmark-upstream
16 | git fetch origin
17 | git checkout $BRANCH && git pull
18 | sha=`git rev-parse HEAD`
19 | cd ../../..
20 | make
21 | cp ext/qiita_marker/cmark-upstream/extensions/*.{c,h} ext/qiita_marker
22 | cp ext/qiita_marker/cmark-upstream/src/*.{inc,c,h} ext/qiita_marker
23 | rm ext/qiita_marker/main.c
24 | git add ext/qiita_marker/cmark-upstream
25 | git add ext/qiita_marker/
26 | git commit -m "Update cmark-upstream to $(git config submodule.ext/qiita_marker/cmark-upstream.url | sed s_.git\$__)/commit/${sha}"
27 | 


--------------------------------------------------------------------------------
/tasks/qiita_marker.rake:
--------------------------------------------------------------------------------
  1 | # frozen_string_literal: true
  2 | 
  3 | require("fileutils")
  4 | 
  5 | namespace :qiita_marker do
  6 |   desc "Rename commonmarker to qiita_marker"
  7 |   task :rename_project do
  8 |     ProjectRenamer.rename
  9 |   end
 10 | 
 11 |   desc "Move submodules of ext/commonmarker to ext/qiita_marker"
 12 |   task :move_submodules do
 13 |     sh "rm -fr ext/qiita_marker/cmark-upstream"
 14 |     sh "git mv ext/commonmarker/cmark-upstream ext/qiita_marker/cmark-upstream"
 15 |   end
 16 | 
 17 |   desc "Format clang files of qfm"
 18 |   task :format_qfm do
 19 |     paths = Dir.glob("ext/qiita_marker/qfm*.{c,h,re}")
 20 |     sh "clang-format -style llvm -i #{paths.join(" ")}"
 21 |   end
 22 | 
 23 |   desc "Run re2c"
 24 |   task :re2c do
 25 |     Dir.glob("ext/qiita_marker/qfm_*.re").each do |path|
 26 |       c_filename = path.sub(/\.re$/, ".c")
 27 |       sh "re2c --case-insensitive -b -i --no-debug-info --no-generation-date -8 --encoding-policy substitute -o #{c_filename} #{path}"
 28 |       sh "clang-format -style llvm -i #{c_filename} #{path}"
 29 |     end
 30 |   end
 31 | 
 32 |   desc "Clean generated files by re2c"
 33 |   task :re2c_clean do
 34 |     Dir.glob("ext/qiita_marker/qfm_*.re").each do |path|
 35 |       c_filename = path.sub(/\.re$/, ".c")
 36 |       sh "test -e #{c_filename} && rm -f #{c_filename} || true"
 37 |     end
 38 |   end
 39 | end
 40 | 
 41 | module ProjectRenamer
 42 |   PATH_MAP = {
 43 |     "commonmarker" => "qiita_marker",
 44 |   }.freeze
 45 | 
 46 |   SYMBOL_MAP = {
 47 |     "commonmarker" => "qiita_marker",
 48 |     "CommonMarker" => "QiitaMarker",
 49 |     "COMMONMARKER" => "QIITA_MARKER",
 50 |   }.freeze
 51 | 
 52 |   SYMBOL_RENAME_EXCLUSION_PATH_PATTERNS = [
 53 |     /\.(?:bundle|so)$/,
 54 |     /README/,
 55 |     %r{^ext/commonmarker/cmark-upstream/},
 56 |     %r{^tasks/},
 57 |     %r{^tmp/},
 58 |   ].freeze
 59 | 
 60 |   class << self
 61 |     def rename
 62 |       rename_paths
 63 |       rename_symbols
 64 |     end
 65 | 
 66 |     private
 67 | 
 68 |     def rename_paths
 69 |       Dir.glob("**/*").each do |path|
 70 |         next if SYMBOL_RENAME_EXCLUSION_PATH_PATTERNS.any? { |pattern| path.match?(pattern) }
 71 | 
 72 |         PATH_MAP.each do |old, new|
 73 |           next unless path.include?(old)
 74 | 
 75 |           if File.directory?(path)
 76 |             FileUtils.mkdir_p(path.gsub(old, new))
 77 |           else
 78 |             File.rename(path, path.gsub(old, new))
 79 |           end
 80 |         end
 81 |       end
 82 |     end
 83 | 
 84 |     def rename_symbols
 85 |       Dir.glob("**/*").each do |path|
 86 |         next if SYMBOL_RENAME_EXCLUSION_PATH_PATTERNS.any? { |pattern| path.match?(pattern) }
 87 |         next unless File.file?(path)
 88 | 
 89 |         source = File.read(path)
 90 | 
 91 |         SYMBOL_MAP.each do |old, new|
 92 |           source.gsub!(old, new)
 93 |         end
 94 | 
 95 |         File.write(path, source)
 96 |       end
 97 |     end
 98 |   end
 99 | end
100 | 


--------------------------------------------------------------------------------
/test/benchmark.rb:
--------------------------------------------------------------------------------
 1 | # frozen_string_literal: true
 2 | 
 3 | require "benchmark/ips"
 4 | require "qiita_marker"
 5 | require "redcarpet"
 6 | require "kramdown"
 7 | require "benchmark"
 8 | 
 9 | benchinput = File.read("test/benchinput.md").freeze
10 | 
11 | printf("input size = %d bytes\n\n", { bytes: benchinput.bytesize })
12 | 
13 | Benchmark.ips do |x|
14 |   x.report("redcarpet") do
15 |     Redcarpet::Markdown.new(Redcarpet::Render::HTML, autolink: false, tables: false).render(benchinput)
16 |   end
17 | 
18 |   x.report("qiita_marker with to_html") do
19 |     QiitaMarker.render_html(benchinput)
20 |   end
21 | 
22 |   x.report("qiita_marker with to_xml") do
23 |     QiitaMarker.render_html(benchinput)
24 |   end
25 | 
26 |   x.report("qiita_marker with ruby HtmlRenderer") do
27 |     QiitaMarker::HtmlRenderer.new.render(QiitaMarker.render_doc(benchinput))
28 |   end
29 | 
30 |   x.report("qiita_marker with render_doc.to_html") do
31 |     QiitaMarker.render_doc(benchinput, :DEFAULT, [:autolink]).to_html(:DEFAULT, [:autolink])
32 |   end
33 | 
34 |   x.report("kramdown") do
35 |     Kramdown::Document.new(benchinput).to_html(benchinput)
36 |   end
37 | 
38 |   x.compare!
39 | end
40 | 


--------------------------------------------------------------------------------
/test/fixtures/curly.md:
--------------------------------------------------------------------------------
1 | This curly quote “makes qiita_marker throw an exception”.
2 | 


--------------------------------------------------------------------------------
/test/fixtures/dingus.md:
--------------------------------------------------------------------------------
 1 | ## Try CommonMark
 2 | 
 3 | You can try CommonMark here.  This dingus is powered by
 4 | [commonmark.js](https://github.com/jgm/commonmark.js), the
 5 | JavaScript reference implementation.
 6 | 
 7 | 1. item one
 8 | 2. item two
 9 |    - sublist
10 |    - sublist
11 | 


--------------------------------------------------------------------------------
/test/fixtures/strong.md:
--------------------------------------------------------------------------------
1 | I am **strong**
2 | 


--------------------------------------------------------------------------------
/test/fixtures/table.md:
--------------------------------------------------------------------------------
 1 | One extension:
 2 | 
 3 | | a   | b   |
 4 | | --- | --- |
 5 | | c   | d   |
 6 | | **x** | |
 7 | 
 8 | Another extension:
 9 | 
10 | ~~hi~~
11 | 


--------------------------------------------------------------------------------
/test/test_attributes.rb:
--------------------------------------------------------------------------------
 1 | # frozen_string_literal: true
 2 | 
 3 | require "test_helper"
 4 | 
 5 | class TestAttributes < Minitest::Test
 6 |   def setup
 7 |     contents = fixtures_file("dingus.md")
 8 |     @doc = QiitaMarker.render_doc(contents.strip)
 9 |   end
10 | 
11 |   def test_sourcepos
12 |     sourcepos = []
13 | 
14 |     @doc.walk do |node|
15 |       sourcepos << node.sourcepos
16 |     end
17 | 
18 |     sourcepos.delete_if { |h| h.values.all?(&:zero?) }
19 | 
20 |     result = [{ start_line: 1, start_column: 1, end_line: 10, end_column: 12 }, { start_line: 1, start_column: 1, end_line: 1, end_column: 17 }, { start_line: 1, start_column: 4, end_line: 1, end_column: 17 }, { start_line: 3, start_column: 1, end_line: 5, end_column: 36 }, { start_line: 3, start_column: 1, end_line: 3, end_column: 55 }, { start_line: 4, start_column: 1, end_line: 4, end_column: 53 }, { start_line: 4, start_column: 2, end_line: 4, end_column: 14 }, { start_line: 4, start_column: 54, end_line: 4, end_column: 58 }, { start_line: 5, start_column: 1, end_line: 5, end_column: 36 }, { start_line: 7, start_column: 1, end_line: 10, end_column: 12 }, { start_line: 7, start_column: 1, end_line: 7, end_column: 11 }, { start_line: 7, start_column: 4, end_line: 7, end_column: 11 }, { start_line: 7, start_column: 4, end_line: 7, end_column: 11 }, { start_line: 8, start_column: 1, end_line: 10, end_column: 12 }, { start_line: 8, start_column: 4, end_line: 8, end_column: 11 }, { start_line: 8, start_column: 4, end_line: 8, end_column: 11 }, { start_line: 9, start_column: 4, end_line: 10, end_column: 12 }, { start_line: 9, start_column: 4, end_line: 9, end_column: 12 }, { start_line: 9, start_column: 6, end_line: 9, end_column: 12 }, { start_line: 9, start_column: 6, end_line: 9, end_column: 12 }, { start_line: 10, start_column: 4, end_line: 10, end_column: 12 }, { start_line: 10, start_column: 6, end_line: 10, end_column: 12 }, { start_line: 10, start_column: 6, end_line: 10, end_column: 12 }]
21 | 
22 |     assert_equal(result, sourcepos)
23 |   end
24 | end
25 | 


--------------------------------------------------------------------------------
/test/test_basics.rb:
--------------------------------------------------------------------------------
 1 | # frozen_string_literal: true
 2 | 
 3 | require "test_helper"
 4 | 
 5 | class TestBasics < Minitest::Test
 6 |   def setup
 7 |     @doc = QiitaMarker.render_doc("Hi *there*")
 8 |   end
 9 | 
10 |   def test_to_html
11 |     assert_equal("

Hi there

\n", @doc.to_html) 12 | end 13 | 14 | def test_markdown_to_html 15 | html = QiitaMarker.render_html("Hi *there*") 16 | 17 | assert_equal("

Hi there

\n", html) 18 | end 19 | 20 | # basic test that just checks if every option is accepted & no errors are thrown 21 | def test_accept_every_option 22 | text = "Hello **world** -- how are _you_ today? I'm ~~fine~~, ~yourself~?" 23 | parse_opt = [:SOURCEPOS, :UNSAFE, :VALIDATE_UTF8, :SMART, :LIBERAL_HTML_TAG, :FOOTNOTES, :STRIKETHROUGH_DOUBLE_TILDE] 24 | render_opt = parse_opt + [:HARDBREAKS, :NOBREAKS, :GITHUB_PRE_LANG, :TABLE_PREFER_STYLE_ATTRIBUTES, :FULL_INFO_STRING] 25 | 26 | extensions = [:table, :tasklist, :strikethrough, :autolink, :tagfilter] 27 | 28 | assert_equal("

Hello world – how are you today? I’m fine, ~yourself~?

\n", QiitaMarker.render_doc(text, parse_opt, extensions).to_html) 29 | 30 | # NOTE: how tho the doc returned has sourcepos info, by default the renderer 31 | # won't emit it. for that we need to pass in the render opt 32 | assert_equal("

Hello world – how are you today? I’m fine, ~yourself~?

\n", QiitaMarker.render_doc(text, parse_opt, extensions).to_html(render_opt, extensions)) 33 | 34 | assert_equal("

Hello world – how are you today? I’m fine, ~yourself~?

\n", QiitaMarker.render_html(text, parse_opt, extensions)) 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /test/test_commands.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | class TestCommands < Minitest::Test 6 | def test_basic 7 | out = make_bin("strong.md") 8 | 9 | assert_equal("

I am strong

", out) 10 | end 11 | 12 | def test_does_not_have_extensions 13 | out = make_bin("table.md") 14 | 15 | assert_includes(out, "| a") 16 | refute_includes(out, "

hi") 17 | refute_includes(out, "") 18 | end 19 | 20 | def test_understands_extensions 21 | out = make_bin("table.md", "--extension=table") 22 | 23 | refute_includes(out, "| a") 24 | refute_includes(out, "

hi") 25 | ["

a c
", "", "", ""].each { |html| assert_includes(out, html) } 26 | end 27 | 28 | def test_understands_multiple_extensions 29 | out = make_bin("table.md", "--extension=table,strikethrough") 30 | 31 | refute_includes(out, "| a") 32 | assert_includes(out, "

hi") 33 | ["

", "a", "", "c", "
", "", "", ""].each { |html| assert_includes(out, html) } 34 | end 35 | 36 | def test_understands_html_format_with_renderer_and_extensions 37 | out = make_bin("table.md", "--to=html --extension=table,strikethrough --html-renderer") 38 | 39 | refute_includes(out, "| a") 40 | assert_includes(out, "

hi") 41 | ["

", "a", "", "c", "
", "", "", ""].each { |html| assert_includes(out, html) } 42 | end 43 | 44 | def test_understands_xml_format 45 | out = make_bin("strong.md", "--to=xml") 46 | 47 | assert_includes(out, '') 48 | assert_includes(out, 'strong') 49 | end 50 | 51 | def test_understands_commonmark_format 52 | out = make_bin("strong.md", "--to=commonmark") 53 | 54 | assert_equal("I am **strong**", out) 55 | end 56 | 57 | def test_understands_plaintext_format 58 | out = make_bin("strong.md", "--to=plaintext") 59 | 60 | assert_equal("I am strong", out) 61 | end 62 | 63 | def test_aborts_invalid_format 64 | _out, err = capture_subprocess_io do 65 | make_bin("strong.md", "--to=unknown") 66 | end 67 | 68 | assert_match("format 'unknown' not found", err) 69 | end 70 | 71 | def test_aborts_format_and_html_renderer_combinations 72 | (QiitaMarker::Config::OPTS[:format] - [:html]).each do |format| 73 | _out, err = capture_subprocess_io do 74 | make_bin("strong.md", "--to=#{format} --html-renderer") 75 | end 76 | 77 | assert_match("format '#{format}' does not support using the HtmlRenderer renderer", err) 78 | end 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /test/test_commonmark.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | class TestCommonmark < Minitest::Test 6 | HTML_COMMENT = /\s?/ 7 | 8 | def setup 9 | @markdown = <<~MD 10 | Hi *there*! 11 | 12 | 1. I am a numeric list. 13 | 2. I continue the list. 14 | * Suddenly, an unordered list! 15 | * What fun! 16 | 17 | Okay, _enough_. 18 | 19 | | a | b | 20 | | --- | --- | 21 | | c | d | 22 | MD 23 | end 24 | 25 | def render_doc(doc) 26 | QiitaMarker.render_doc(doc, :DEFAULT, [:table]) 27 | end 28 | 29 | def test_to_commonmark 30 | compare = render_doc(@markdown).to_commonmark 31 | 32 | assert_equal( 33 | render_doc(@markdown).to_html.squeeze(" ").gsub(HTML_COMMENT, ""), 34 | render_doc(compare).to_html.squeeze(" ").gsub(HTML_COMMENT, ""), 35 | ) 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /test/test_doc.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | class TestDocNode < Minitest::Test 6 | def setup 7 | @doc = QiitaMarker.render_doc("Hi *there*. This has __many nodes__!") 8 | @first_child = @doc.first_child 9 | @last_child = @doc.last_child 10 | @link = QiitaMarker.render_doc("[GitHub](https://www.github.com)").first_child.first_child 11 | @image = QiitaMarker.render_doc('![alt text](https://github.com/favicon.ico "Favicon")') 12 | @image = @image.first_child.first_child 13 | @header = QiitaMarker.render_doc("### Header Three").first_child 14 | @ul_list = QiitaMarker.render_doc("* Bullet\n*Bullet").first_child 15 | @ol_list = QiitaMarker.render_doc("1. One\n2. Two").first_child 16 | @fence = QiitaMarker.render_doc("``` ruby\nputs 'wow'\n```").first_child 17 | end 18 | 19 | def test_get_type 20 | assert_equal(:document, @doc.type) 21 | end 22 | 23 | def test_get_type_string 24 | assert_equal("document", @doc.type_string) 25 | end 26 | 27 | def test_get_first_child 28 | assert_equal(:paragraph, @first_child.type) 29 | end 30 | 31 | def test_get_next 32 | assert_equal(:emph, @first_child.first_child.next.type) 33 | end 34 | 35 | def test_insert_before 36 | paragraph = Node.new(:paragraph) 37 | 38 | assert(@first_child.insert_before(paragraph)) 39 | assert_match("

\n

Hi there.", @doc.to_html) 40 | end 41 | 42 | def test_insert_after 43 | paragraph = Node.new(:paragraph) 44 | 45 | assert(@first_child.insert_after(paragraph)) 46 | assert_match("many nodes!

\n

\n", @doc.to_html) 47 | end 48 | 49 | def test_prepend_child 50 | code = Node.new(:code) 51 | 52 | assert(@first_child.prepend_child(code)) 53 | assert_match("

Hi there.", @doc.to_html) 54 | end 55 | 56 | def test_append_child 57 | strong = Node.new(:strong) 58 | 59 | assert(@first_child.append_child(strong)) 60 | assert_match("!

\n", @doc.to_html) 61 | end 62 | 63 | def test_get_last_child 64 | assert_equal(:paragraph, @last_child.type) 65 | end 66 | 67 | def test_get_parent 68 | assert_equal(:paragraph, @first_child.first_child.next.parent.type) 69 | end 70 | 71 | def test_get_previous 72 | assert_equal(:text, @first_child.first_child.next.previous.type) 73 | end 74 | 75 | def test_get_url 76 | assert_equal("https://www.github.com", @link.url) 77 | end 78 | 79 | def test_set_url 80 | assert_equal("https://www.mozilla.org", @link.url = "https://www.mozilla.org") 81 | end 82 | 83 | def test_get_title 84 | assert_equal("Favicon", @image.title) 85 | end 86 | 87 | def test_set_title 88 | assert_equal("Octocat", @image.title = "Octocat") 89 | end 90 | 91 | def test_get_header_level 92 | assert_equal(3, @header.header_level) 93 | end 94 | 95 | def test_set_header_level 96 | assert_equal(6, @header.header_level = 6) 97 | end 98 | 99 | def test_get_list_type 100 | assert_equal(:bullet_list, @ul_list.list_type) 101 | assert_equal(:ordered_list, @ol_list.list_type) 102 | end 103 | 104 | def test_set_list_type 105 | assert_equal(:ordered_list, @ul_list.list_type = :ordered_list) 106 | assert_equal(:bullet_list, @ol_list.list_type = :bullet_list) 107 | end 108 | 109 | def test_get_list_start 110 | assert_equal(1, @ol_list.list_start) 111 | end 112 | 113 | def test_set_list_start 114 | assert_equal(8, @ol_list.list_start = 8) 115 | end 116 | 117 | def test_get_list_tight 118 | assert(@ul_list.list_tight) 119 | assert(@ol_list.list_tight) 120 | end 121 | 122 | def test_set_list_tight 123 | refute(@ul_list.list_tight = false) 124 | refute(@ol_list.list_tight = false) 125 | end 126 | 127 | def test_get_fence_info 128 | assert_equal("ruby", @fence.fence_info) 129 | end 130 | 131 | def test_set_fence_info 132 | assert_equal("javascript", @fence.fence_info = "javascript") 133 | end 134 | end 135 | -------------------------------------------------------------------------------- /test/test_encoding.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | class TestEncoding < Minitest::Test 6 | # see http://git.io/vq4FR 7 | def test_encoding 8 | contents = fixtures_file("curly.md") 9 | doc = QiitaMarker.render_doc(contents, :SMART) 10 | render = doc.to_html 11 | 12 | assert_equal("

This curly quote “makes qiita_marker throw an exception”.

", render.rstrip) 13 | 14 | render = doc.to_xml 15 | 16 | assert_includes(render, 'This curly quote “makes qiita_marker throw an exception”.') 17 | end 18 | 19 | def test_string_content_is_utf8 20 | doc = QiitaMarker.render_doc("Hi *there*") 21 | text = doc.first_child.last_child.first_child 22 | 23 | assert_equal("there", text.string_content) 24 | assert_equal("UTF-8", text.string_content.encoding.name) 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /test/test_extensions.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | class TestExtensions < Minitest::Test 6 | def setup 7 | @markdown = fixtures_file("table.md") 8 | end 9 | 10 | def test_uses_specified_extensions 11 | QiitaMarker.render_html(@markdown, :DEFAULT, []).tap do |out| 12 | assert_includes(out, "| a") 13 | assert_includes(out, "| x") 14 | assert_includes(out, "~~hi~~") 15 | end 16 | 17 | QiitaMarker.render_html(@markdown, :DEFAULT, [:table]).tap do |out| 18 | refute_includes(out, "| a") 19 | ["
", "a", "", "c", "
", "", "", "", "x"].each { |html| assert_includes(out, html) } 20 | 21 | assert_includes(out, "~~hi~~") 22 | end 23 | 24 | QiitaMarker.render_html(@markdown, :DEFAULT, [:strikethrough]).tap do |out| 25 | assert_includes(out, "| a") 26 | refute_includes(out, "~~hi~~") 27 | assert_includes(out, "hi") 28 | end 29 | 30 | doc = QiitaMarker.render_doc("~a~ ~~b~~ ~~~c~~~", :STRIKETHROUGH_DOUBLE_TILDE, [:strikethrough]) 31 | 32 | assert_equal("

~a~ b ~~~c~~~

\n", doc.to_html) 33 | 34 | html = QiitaMarker.render_html("~a~ ~~b~~ ~~~c~~~", :STRIKETHROUGH_DOUBLE_TILDE, [:strikethrough]) 35 | 36 | assert_equal("

~a~ b ~~~c~~~

\n", html) 37 | 38 | QiitaMarker.render_html(@markdown, :DEFAULT, [:table, :strikethrough]).tap do |out| 39 | refute_includes(out, "| a") 40 | refute_includes(out, "| x") 41 | refute_includes(out, "~~hi~~") 42 | end 43 | end 44 | 45 | def test_extensions_with_renderers 46 | doc = QiitaMarker.render_doc(@markdown, :DEFAULT, [:table]) 47 | 48 | doc.to_html.tap do |out| 49 | refute_includes(out, "| a") 50 | ["
", "a", "", "c", "
", "", "", "", "x"].each { |html| assert_includes(out, html) } 51 | 52 | assert_includes(out, "~~hi~~") 53 | end 54 | 55 | HtmlRenderer.new.render(doc).tap do |out| 56 | refute_includes(out, "| a") 57 | ["
", "a", "", "c", "
", "", "", "", "x"].each { |html| assert_includes(out, html) } 58 | 59 | assert_includes(out, "~~hi~~") 60 | end 61 | 62 | doc = QiitaMarker.render_doc("~a~ ~~b~~ ~~~c~~~", :STRIKETHROUGH_DOUBLE_TILDE, [:strikethrough]) 63 | 64 | assert_equal("

~a~ b ~~~c~~~

\n", HtmlRenderer.new.render(doc)) 65 | end 66 | 67 | def test_bad_extension_specifications 68 | assert_raises(TypeError) { QiitaMarker.render_html(@markdown, :DEFAULT, "nope") } 69 | assert_raises(TypeError) { QiitaMarker.render_html(@markdown, :DEFAULT, ["table"]) } 70 | assert_raises(ArgumentError) { QiitaMarker.render_html(@markdown, :DEFAULT, [:table, :bad]) } 71 | end 72 | 73 | def test_comments_are_kept_as_expected 74 | assert_equal( 75 | " <xmp>\n", 76 | QiitaMarker.render_html(" \n", :UNSAFE, [:tagfilter]), 77 | ) 78 | end 79 | 80 | def test_table_prefer_style_attributes 81 | assert_equal(<<~HTML, QiitaMarker.render_html(<<~MD, :TABLE_PREFER_STYLE_ATTRIBUTES, [:table])) 82 | <table> 83 | <thead> 84 | <tr> 85 | <th style="text-align: left">aaa</th> 86 | <th>bbb</th> 87 | <th style="text-align: center">ccc</th> 88 | <th>ddd</th> 89 | <th style="text-align: right">eee</th> 90 | </tr> 91 | </thead> 92 | <tbody> 93 | <tr> 94 | <td style="text-align: left">fff</td> 95 | <td>ggg</td> 96 | <td style="text-align: center">hhh</td> 97 | <td>iii</td> 98 | <td style="text-align: right">jjj</td> 99 | </tr> 100 | </tbody> 101 | </table> 102 | HTML 103 | aaa | bbb | ccc | ddd | eee 104 | :-- | --- | :-: | --- | --: 105 | fff | ggg | hhh | iii | jjj 106 | MD 107 | end 108 | 109 | def test_plaintext 110 | assert_equal(<<~HTML, QiitaMarker.render_doc(<<~MD, :DEFAULT, [:table, :strikethrough]).to_plaintext) 111 | Hello ~there~. 112 | 113 | | a | 114 | | --- | 115 | | b | 116 | HTML 117 | Hello ~~there~~. 118 | 119 | | a | 120 | | - | 121 | | b | 122 | MD 123 | end 124 | end 125 | -------------------------------------------------------------------------------- /test/test_footnotes.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | class TestFootnotes < Minitest::Test 6 | def setup 7 | @doc = QiitaMarker.render_doc("Hello[^hi].\n\n[^hi]: Hey!\n", :FOOTNOTES) 8 | end 9 | 10 | def test_to_html 11 | expected = <<~HTML 12 | <p>Hello<sup class="footnote-ref"><a href="#fn-hi" id="fnref-hi" data-footnote-ref>1</a></sup>.</p> 13 | <section class="footnotes" data-footnotes> 14 | <ol> 15 | <li id="fn-hi"> 16 | <p>Hey! <a href="#fnref-hi" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="1" aria-label="Back to reference 1">↩</a></p> 17 | </li> 18 | </ol> 19 | </section> 20 | HTML 21 | 22 | assert_equal(expected, @doc.to_html) 23 | end 24 | 25 | def test_html_renderer 26 | expected = <<~HTML 27 | <p>Hello<sup class="footnote-ref"><a href="#fn1" id="fnref1">1</a></sup>.</p> 28 | <section class="footnotes"> 29 | <ol> 30 | <li id="fn1"> 31 | <p>Hey! <a href="#fnref1" class="footnote-backref">↩</a></p> 32 | </li> 33 | </ol> 34 | </section> 35 | HTML 36 | 37 | assert_equal(expected, QiitaMarker::HtmlRenderer.new.render(@doc)) 38 | end 39 | 40 | def test_render_html 41 | md = <<~MARKDOWN 42 | # footnotes 43 | Let's render some footnotes[^1] 44 | 45 | [^1]: This is a footnote 46 | MARKDOWN 47 | expected = <<~HTML 48 | <h1>footnotes</h1> 49 | <p>Let's render some footnotes<sup class="footnote-ref"><a href="#fn-1" id="fnref-1" data-footnote-ref>1</a></sup></p> 50 | <section class="footnotes" data-footnotes> 51 | <ol> 52 | <li id="fn-1"> 53 | <p>This is a footnote <a href="#fnref-1" class="footnote-backref" data-footnote-backref data-footnote-backref-idx="1" aria-label="Back to reference 1">↩</a></p> 54 | </li> 55 | </ol> 56 | </section> 57 | HTML 58 | assert_equal(expected, QiitaMarker.render_html(md, :FOOTNOTES)) 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /test/test_gc.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # rubocop:disable Lint/UselessAssignment 4 | require "test_helper" 5 | 6 | class TestNode < Minitest::Test 7 | # These tests are somewhat fragile. It would be better to allocate lots 8 | # of memory after a GC run to make sure that potentially freed memory 9 | # isn't valid by accident. 10 | 11 | def test_drop_parent_reference 12 | doc = QiitaMarker.render_doc("Hi *there*") 13 | text = doc.first_child.last_child.first_child 14 | doc = nil 15 | GC.start 16 | # Test that doc has not been freed. 17 | assert_equal("there", text.string_content) 18 | end 19 | 20 | def test_drop_child_reference 21 | doc = QiitaMarker.render_doc("Hi *there*") 22 | text = doc.first_child.last_child.first_child 23 | text = nil 24 | GC.start 25 | # Test that the cached child object is still valid. 26 | text = doc.first_child.last_child.first_child 27 | 28 | assert_equal("there", text.string_content) 29 | end 30 | 31 | def test_remove_parent 32 | doc = QiitaMarker.render_doc("Hi *there*") 33 | para = doc.first_child 34 | para.delete 35 | doc = nil 36 | para = nil 37 | # TODO: Test that the `para` node was actually freed after unlinking. 38 | end 39 | 40 | def test_add_parent 41 | doc = Node.new(:document) 42 | hrule = Node.new(:hrule) 43 | doc.append_child(hrule) 44 | # If the hrule node was erroneously freed, this would result in a double 45 | # free. 46 | end 47 | end 48 | # rubocop:enable Lint/UselessAssignment 49 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "qiita_marker" 4 | require "minitest/autorun" 5 | require "minitest/pride" 6 | require "minitest/focus" 7 | 8 | include QiitaMarker 9 | 10 | FIXTURES_DIR = File.join(File.dirname(__FILE__), "fixtures") 11 | 12 | def fixtures_file(file) 13 | File.read(File.join(FIXTURES_DIR, file), encoding: "utf-8") 14 | end 15 | 16 | def make_bin(file, args = "") 17 | %x(ruby bin/qiita_marker #{File.join(FIXTURES_DIR, file)} #{args}).chomp 18 | end 19 | 20 | def open_spec_file(filename) 21 | line_number = 0 22 | start_line = 0 23 | end_line = 0 24 | example_number = 0 25 | markdown_lines = [] 26 | html_lines = [] 27 | state = 0 # 0 regular text, 1 markdown example, 2 html output 28 | headertext = "" 29 | tests = [] 30 | extensions = [] 31 | 32 | header_re = Regexp.new("#+ ") 33 | filepath = File.join("ext", "qiita_marker", "cmark-upstream", "test", filename) 34 | 35 | File.readlines(filepath, encoding: "utf-8").each do |line| 36 | line_number += 1 37 | 38 | l = line.strip 39 | if l =~ /^`{32} example(.*)$/ 40 | state = 1 41 | extensions = Regexp.last_match(1).split 42 | elsif l == "`" * 32 43 | state = 0 44 | example_number += 1 45 | end_line = line_number 46 | tests << { 47 | markdown: markdown_lines.join.tr("→", "\t"), 48 | html: html_lines.join.tr("→", "\t").rstrip, 49 | example: example_number, 50 | start_line: start_line, 51 | end_line: end_line, 52 | section: headertext, 53 | extensions: extensions.map(&:to_sym), 54 | } 55 | start_line = 0 56 | markdown_lines = [] 57 | html_lines = [] 58 | elsif l == "." 59 | state = 2 60 | elsif state == 1 61 | start_line = line_number - 1 if start_line.zero? 62 | markdown_lines << line.to_s 63 | elsif state == 2 64 | html_lines << line.to_s 65 | elsif state.zero? && header_re.match(line) 66 | headertext = line.sub(header_re, "").strip 67 | end 68 | end 69 | 70 | tests 71 | end 72 | -------------------------------------------------------------------------------- /test/test_linebreaks.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | class TestLinebreaks < Minitest::Test 6 | def test_hardbreak_no_spaces 7 | doc = QiitaMarker.render_doc("foo\nbaz") 8 | 9 | assert_equal("<p>foo<br />\nbaz</p>\n", doc.to_html(:HARDBREAKS)) 10 | end 11 | 12 | def test_hardbreak_with_spaces 13 | doc = QiitaMarker.render_doc("foo \nbaz") 14 | 15 | assert_equal("<p>foo<br />\nbaz</p>\n", doc.to_html(:HARDBREAKS)) 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/test_maliciousness.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | module QiitaMarker 6 | class TestMaliciousness < Minitest::Test 7 | def setup 8 | @doc = QiitaMarker.render_doc("Hi *there*") 9 | end 10 | 11 | def test_init_with_bad_type 12 | assert_raises(TypeError) do 13 | Node.new(123) 14 | end 15 | 16 | assert_raises(NodeError) do 17 | Node.new(:totes_fake) 18 | end 19 | 20 | assert_raises(TypeError) do 21 | Node.new([]) 22 | end 23 | 24 | assert_raises(TypeError) do 25 | Node.new([23]) 26 | end 27 | 28 | assert_raises(TypeError) do 29 | Node.new(nil) 30 | end 31 | end 32 | 33 | def test_rendering_with_bad_type 34 | assert_raises(TypeError) do 35 | QiitaMarker.render_html("foo \n baz", 123) 36 | end 37 | 38 | assert_raises(TypeError) do 39 | QiitaMarker.render_html("foo \n baz", :totes_fake) 40 | end 41 | 42 | assert_raises(TypeError) do 43 | QiitaMarker.render_html("foo \n baz", []) 44 | end 45 | 46 | assert_raises(TypeError) do 47 | QiitaMarker.render_html("foo \n baz", [23]) 48 | end 49 | 50 | assert_raises(TypeError) do 51 | QiitaMarker.render_html("foo \n baz", nil) 52 | end 53 | 54 | assert_raises(TypeError) do 55 | QiitaMarker.render_html("foo \n baz", [:SMART, "totes_fake"]) 56 | end 57 | 58 | assert_raises(TypeError) do 59 | QiitaMarker.render_html(123) 60 | end 61 | 62 | assert_raises(TypeError) do 63 | QiitaMarker.render_html([123]) 64 | end 65 | 66 | assert_raises(TypeError) do 67 | QiitaMarker.render_html(nil) 68 | end 69 | 70 | assert_raises(TypeError) do 71 | QiitaMarker.render_doc("foo \n baz", 123) 72 | end 73 | 74 | err = assert_raises(TypeError) do 75 | QiitaMarker.render_doc("foo \n baz", :safe) 76 | end 77 | 78 | assert_equal("option ':safe' does not exist for QiitaMarker::Config::OPTS[:parse]", err.message) 79 | 80 | assert_raises(TypeError) do 81 | QiitaMarker.render_doc("foo \n baz", :totes_fake) 82 | end 83 | 84 | assert_raises(TypeError) do 85 | QiitaMarker.render_doc("foo \n baz", []) 86 | end 87 | 88 | assert_raises(TypeError) do 89 | QiitaMarker.render_doc("foo \n baz", [23]) 90 | end 91 | 92 | assert_raises(TypeError) do 93 | QiitaMarker.render_doc("foo \n baz", nil) 94 | end 95 | 96 | assert_raises(TypeError) do 97 | QiitaMarker.render_doc("foo \n baz", [:SMART, "totes_fake"]) 98 | end 99 | 100 | assert_raises(TypeError) do 101 | QiitaMarker.render_doc(123) 102 | end 103 | 104 | assert_raises(TypeError) do 105 | QiitaMarker.render_doc([123]) 106 | end 107 | 108 | assert_raises(TypeError) do 109 | QiitaMarker.render_doc(nil) 110 | end 111 | end 112 | 113 | def test_bad_set_string_content 114 | assert_raises(TypeError) do 115 | @doc.string_content = 123 116 | end 117 | end 118 | 119 | def test_bad_walking 120 | assert_nil(@doc.parent) 121 | assert_nil(@doc.previous) 122 | end 123 | 124 | def test_bad_insertion 125 | code = Node.new(:code) 126 | assert_raises(NodeError) do 127 | @doc.insert_before(code) 128 | end 129 | 130 | paragraph = Node.new(:paragraph) 131 | assert_raises(NodeError) do 132 | @doc.insert_after(paragraph) 133 | end 134 | 135 | document = Node.new(:document) 136 | assert_raises(NodeError) do 137 | @doc.prepend_child(document) 138 | end 139 | 140 | assert_raises(NodeError) do 141 | @doc.append_child(document) 142 | end 143 | end 144 | 145 | def test_bad_url_get 146 | assert_raises(NodeError) do 147 | @doc.url 148 | end 149 | end 150 | 151 | def test_bad_url_set 152 | assert_raises(NodeError) do 153 | @doc.url = "123" 154 | end 155 | 156 | link = QiitaMarker.render_doc("[GitHub](https://www.github.com)").first_child.first_child 157 | assert_raises(TypeError) do 158 | link.url = 123 159 | end 160 | end 161 | 162 | def test_bad_title_get 163 | assert_raises(NodeError) do 164 | @doc.title 165 | end 166 | end 167 | 168 | def test_bad_title_set 169 | assert_raises(NodeError) do 170 | @doc.title = "123" 171 | end 172 | 173 | image = QiitaMarker.render_doc('![alt text](https://github.com/favicon.ico "Favicon")') 174 | image = image.first_child.first_child 175 | assert_raises(TypeError) do 176 | image.title = 123 177 | end 178 | end 179 | 180 | def test_bad_header_level_get 181 | assert_raises(NodeError) do 182 | @doc.header_level 183 | end 184 | end 185 | 186 | def test_bad_header_level_set 187 | assert_raises(NodeError) do 188 | @doc.header_level = 1 189 | end 190 | 191 | header = QiitaMarker.render_doc("### Header Three").first_child 192 | assert_raises(TypeError) do 193 | header.header_level = "123" 194 | end 195 | end 196 | 197 | def test_bad_list_type_get 198 | assert_raises(NodeError) do 199 | @doc.list_type 200 | end 201 | end 202 | 203 | def test_bad_list_type_set 204 | assert_raises(NodeError) do 205 | @doc.list_type = :bullet_list 206 | end 207 | 208 | ul_list = QiitaMarker.render_doc("* Bullet\n*Bullet").first_child 209 | assert_raises(NodeError) do 210 | ul_list.list_type = :fake 211 | end 212 | assert_raises(TypeError) do 213 | ul_list.list_type = 1234 214 | end 215 | end 216 | 217 | def test_bad_list_start_get 218 | assert_raises(NodeError) do 219 | @doc.list_start 220 | end 221 | end 222 | 223 | def test_bad_list_start_set 224 | assert_raises(NodeError) do 225 | @doc.list_start = 12 226 | end 227 | 228 | ol_list = QiitaMarker.render_doc("1. One\n2. Two").first_child 229 | assert_raises(TypeError) do 230 | ol_list.list_start = :fake 231 | end 232 | end 233 | 234 | def test_bad_list_tight_get 235 | assert_raises(NodeError) do 236 | @doc.list_tight 237 | end 238 | end 239 | 240 | def test_bad_list_tight_set 241 | assert_raises(NodeError) do 242 | @doc.list_tight = false 243 | end 244 | end 245 | 246 | def test_bad_fence_info_get 247 | assert_raises(NodeError) do 248 | @doc.fence_info 249 | end 250 | end 251 | 252 | def test_bad_fence_info_set 253 | assert_raises(NodeError) do 254 | @doc.fence_info = "ruby" 255 | end 256 | 257 | fence = QiitaMarker.render_doc("``` ruby\nputs 'wow'\n```").first_child 258 | assert_raises(TypeError) do 259 | fence.fence_info = 123 260 | end 261 | end 262 | end 263 | end 264 | -------------------------------------------------------------------------------- /test/test_node.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | class TestNode < Minitest::Test 6 | def setup 7 | @doc = QiitaMarker.render_doc("Hi *there*, I am mostly text!") 8 | end 9 | 10 | def test_walk 11 | nodes = [] 12 | @doc.walk do |node| 13 | nodes << node.type 14 | end 15 | 16 | assert_equal([:document, :paragraph, :text, :emph, :text, :text], nodes) 17 | end 18 | 19 | def test_each 20 | nodes = [] 21 | @doc.first_child.each do |node| 22 | nodes << node.type 23 | end 24 | 25 | assert_equal([:text, :emph, :text], nodes) 26 | end 27 | 28 | def test_deprecated_each_child 29 | nodes = [] 30 | _, err = capture_io do 31 | @doc.first_child.each_child do |node| 32 | nodes << node.type 33 | end 34 | end 35 | 36 | assert_equal([:text, :emph, :text], nodes) 37 | assert_match(/`each_child` is deprecated/, err) 38 | end 39 | 40 | def test_select 41 | nodes = @doc.first_child.select { |node| node.type == :text } 42 | 43 | assert_instance_of(QiitaMarker::Node, nodes.first) 44 | assert_equal([:text, :text], nodes.map(&:type)) 45 | end 46 | 47 | def test_map 48 | nodes = @doc.first_child.map(&:type) 49 | 50 | assert_equal([:text, :emph, :text], nodes) 51 | end 52 | 53 | def test_insert_illegal 54 | assert_raises(NodeError) do 55 | @doc.insert_before(@doc) 56 | end 57 | end 58 | 59 | def test_to_html 60 | assert_equal("<p>Hi <em>there</em>, I am mostly text!</p>\n", @doc.to_html) 61 | end 62 | 63 | def test_html_renderer 64 | renderer = HtmlRenderer.new 65 | result = renderer.render(@doc) 66 | 67 | assert_equal("<p>Hi <em>there</em>, I am mostly text!</p>\n", result) 68 | end 69 | 70 | def test_walk_and_set_string_content 71 | @doc.walk do |node| 72 | node.string_content = "world" if node.type == :text && node.string_content == "there" 73 | end 74 | result = HtmlRenderer.new.render(@doc) 75 | 76 | assert_equal("<p>Hi <em>world</em>, I am mostly text!</p>\n", result) 77 | end 78 | 79 | def test_walk_and_delete_node 80 | @doc.walk do |node| 81 | if node.type == :emph 82 | node.insert_before(node.first_child) 83 | node.delete 84 | end 85 | end 86 | 87 | assert_equal("<p>Hi there, I am mostly text!</p>\n", @doc.to_html) 88 | end 89 | 90 | def test_inspect 91 | assert_match(/#<QiitaMarker::Node\(document\):/, @doc.inspect) 92 | end 93 | 94 | def test_pretty_print 95 | assert_match(/#<QiitaMarker::Node\(document\):/, PP.pp(@doc, +"")) 96 | end 97 | end 98 | -------------------------------------------------------------------------------- /test/test_options.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | class TestExtensions < Minitest::Test 6 | def test_full_info_string 7 | md = <<~MD 8 | ```ruby 9 | module Foo 10 | ``` 11 | MD 12 | 13 | QiitaMarker.render_html(md, :FULL_INFO_STRING).tap do |out| 14 | assert_includes(out, '<pre><code class="language-ruby">') 15 | end 16 | 17 | md = <<~MD 18 | ```ruby my info string 19 | module Foo 20 | ``` 21 | MD 22 | 23 | QiitaMarker.render_html(md, :FULL_INFO_STRING).tap do |out| 24 | assert_includes(out, '<pre><code class="language-ruby" data-meta="my info string">') 25 | end 26 | 27 | md = <<~MD 28 | ```ruby my \x00 string 29 | module Foo 30 | ``` 31 | MD 32 | 33 | QiitaMarker.render_html(md, :FULL_INFO_STRING).tap do |out| 34 | assert_includes(out, %(<pre><code class="language-ruby" data-meta="my � string">)) 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /test/test_pathological_inputs.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | require "minitest/benchmark" if ENV["BENCH"] 5 | 6 | def markdown(str) 7 | QiitaMarker.render_doc(str).to_html 8 | end 9 | 10 | # list of pairs consisting of input and a regex that must match the output. 11 | pathological = { 12 | "nested strong emph" => 13 | [ 14 | "#{"*a **a " * 65_000}b#{" a** a*" * 65_000}", 15 | Regexp.compile("(<em>a <strong>a ){65_000}b( a</strong> a</em>){65_000}"), 16 | ], 17 | "many emph closers with no openers" => 18 | [ 19 | ("a_ " * 65_000), 20 | Regexp.compile("(a[_] ){64999}a_"), 21 | ], 22 | "many emph openers with no closers" => 23 | [ 24 | ("_a " * 65_000), 25 | Regexp.compile("(_a ){64999}_a"), 26 | ], 27 | "many link closers with no openers" => 28 | [ 29 | ("a]" * 65_000), 30 | Regexp.compile('(a\]){65_000}'), 31 | ], 32 | "many link openers with no closers" => 33 | [ 34 | ("[a" * 65_000), 35 | Regexp.compile('(\[a){65_000}'), 36 | ], 37 | "mismatched openers and closers" => 38 | [ 39 | ("*a_ " * 50_000), 40 | Regexp.compile("([*]a[_] ){49999}[*]a_"), 41 | ], 42 | "link openers and emph closers" => 43 | [ 44 | ("[ a_" * 50_000), 45 | Regexp.compile('(\[ a_){50000}'), 46 | ], 47 | "hard link/emph case" => 48 | [ 49 | "**x [a*b**c*](d)", 50 | Regexp.compile('\\*\\*x <a href=\'d\'>a<em>b</em><em>c</em></a>'), 51 | ], 52 | "nested brackets" => 53 | [ 54 | "#{"[" * 50_000}a#{"]" * 50_000}", 55 | Regexp.compile('\[{50000}a\]{50000}'), 56 | ], 57 | "nested block quotes" => 58 | [ 59 | "#{"> " * 50_000}a", 60 | Regexp.compile('(<blockquote>\n){50000}'), 61 | ], 62 | "U+0000 in input" => 63 | [ 64 | 'abc\u0000de\u0000', 65 | Regexp.compile('abc\ufffd?de\ufffd?'), 66 | ], 67 | } 68 | 69 | pathological.each_pair do |name, description| 70 | define_method("test_#{name}") do 71 | input, = description 72 | 73 | assert markdown(input) 74 | end 75 | end 76 | 77 | if ENV["BENCH"] 78 | class PathologicalInputsPerformanceTest < Minitest::Benchmark 79 | def test_bench_pathological_one 80 | assert_performance_linear(0.99) do |n| 81 | star = "*" * (n * 10) 82 | markdown("#{star}#{star}hi#{star}#{star}") 83 | end 84 | end 85 | 86 | def test_bench_pathological_two 87 | assert_performance_linear(0.99) do |n| 88 | c = "`t`t`t`t`t`t" * (n * 10) 89 | markdown(c) 90 | end 91 | end 92 | 93 | def test_bench_pathological_three 94 | assert_performance_linear(0.99) do |n| 95 | markdown(" [a]: #{"A" * n}\n\n#{"[a][]" * n}\n") 96 | end 97 | end 98 | 99 | def test_bench_pathological_four 100 | assert_performance_linear(0.5) do |n| 101 | markdown("#{"[" * n}a#{"]" * n}") 102 | end 103 | end 104 | 105 | def test_bench_pathological_five 106 | assert_performance_linear(0.99) do |n| 107 | markdown("#{"**a *a " * n}#{"a* a**" * n}") 108 | end 109 | end 110 | 111 | def test_bench_unbound_recursion 112 | assert_performance_linear(0.99) do |n| 113 | markdown("#{"[" * n}foo#{"](bar)" * n}") 114 | end 115 | end 116 | end 117 | end 118 | -------------------------------------------------------------------------------- /test/test_plaintext.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | class TestPlaintext < Minitest::Test 6 | def setup 7 | @markdown = <<~MD 8 | Hi *there*! 9 | 10 | 1. I am a numeric list. 11 | 2. I continue the list. 12 | * Suddenly, an unordered list! 13 | * What fun! 14 | 15 | Okay, _enough_. 16 | 17 | | a | b | 18 | | --- | --- | 19 | | c | d | 20 | MD 21 | end 22 | 23 | def render_doc(doc) 24 | QiitaMarker.render_doc(doc, :DEFAULT, [:table]) 25 | end 26 | 27 | def test_to_commonmark 28 | compare = render_doc(@markdown).to_plaintext 29 | 30 | assert_equal(<<~PLAINTEXT, compare) 31 | Hi there! 32 | 33 | 1. I am a numeric list. 34 | 2. I continue the list. 35 | 36 | - Suddenly, an unordered list! 37 | - What fun! 38 | 39 | Okay, enough. 40 | 41 | | a | b | 42 | | --- | --- | 43 | | c | d | 44 | PLAINTEXT 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /test/test_qfm_autolink_class_name.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require("test_helper") 4 | 5 | describe "TestQfmAutolinkClassName" do 6 | let(:options) { [:AUTOLINK_CLASS_NAME] } 7 | let(:extensions) { [:autolink] } 8 | let(:text) do 9 | <<~MD 10 | https://example.com 11 | <https://example.com> 12 | [Example](https://example.com) 13 | test@example.com 14 | MD 15 | end 16 | let(:doc) { QiitaMarker.render_doc(text, options, extensions) } 17 | let(:expected) do 18 | <<~HTML 19 | <p><a href="https://example.com" class="autolink">https://example.com</a> 20 | <a href="https://example.com">https://example.com</a> 21 | <a href="https://example.com">Example</a> 22 | <a href="mailto:test@example.com" class="autolink">test@example.com</a></p> 23 | HTML 24 | end 25 | let(:rendered_html) { doc.to_html(options, extensions) } 26 | 27 | it "appends class name to extension's autolinks" do 28 | assert_equal(expected, rendered_html) 29 | end 30 | 31 | describe "without AUTOLINK_CLASS_NAME option" do 32 | let(:options) { [:DEFAULT] } 33 | let(:expected) do 34 | <<~HTML 35 | <p><a href="https://example.com">https://example.com</a> 36 | <a href="https://example.com">https://example.com</a> 37 | <a href="https://example.com">Example</a> 38 | <a href="mailto:test@example.com">test@example.com</a></p> 39 | HTML 40 | end 41 | 42 | it "does not append class name to extension's autolink" do 43 | assert_equal(expected, rendered_html) 44 | end 45 | end 46 | 47 | describe "without autolink extension" do 48 | let(:extensions) { [] } 49 | let(:expected) do 50 | <<~HTML 51 | <p>https://example.com 52 | <a href="https://example.com">https://example.com</a> 53 | <a href="https://example.com">Example</a> 54 | test@example.com</p> 55 | HTML 56 | end 57 | 58 | it "does not append class name" do 59 | assert_equal(expected, rendered_html) 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /test/test_qfm_code_data_metadata.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require("test_helper") 4 | 5 | class TestQfmCodeDataMetadata < Minitest::Test 6 | def test_to_html 7 | text = <<~MD 8 | ```ruby:example main.rb 9 | puts :foo 10 | ``` 11 | MD 12 | doc = render_doc(text) 13 | expected = <<~HTML 14 | <pre><code data-metadata="ruby:example main.rb">puts :foo 15 | </code></pre> 16 | HTML 17 | 18 | assert_equal(expected, doc.to_html(:CODE_DATA_METADATA)) 19 | assert_equal(expected, QiitaMarker::HtmlRenderer.new(options: :CODE_DATA_METADATA).render(doc)) 20 | end 21 | 22 | def test_with_character_reference 23 | text = <<~MD 24 | ```ruby:example&#x20;main.rb 25 | puts :foo 26 | ``` 27 | MD 28 | doc = render_doc(text) 29 | expected = <<~HTML 30 | <pre><code data-metadata="ruby:example main.rb">puts :foo 31 | </code></pre> 32 | HTML 33 | 34 | assert_equal(expected, doc.to_html(:CODE_DATA_METADATA)) 35 | assert_equal(expected, QiitaMarker::HtmlRenderer.new(options: :CODE_DATA_METADATA).render(doc)) 36 | end 37 | 38 | def render_doc(markdown) 39 | QiitaMarker.render_doc(markdown, :DEFAULT, []) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /test/test_qfm_custom_block.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require("test_helper") 4 | 5 | class TestQfmCustomBlock < Minitest::Test 6 | def setup 7 | text = <<~MD 8 | :::foo bar 9 | message 10 | 11 | - list1 12 | - list2 13 | 14 | ```ruby 15 | puts 'hello' 16 | ``` 17 | 18 | <div>html block</div> 19 | ::: 20 | 21 | :::fizz buzz 22 | second custom block 23 | ::: 24 | 25 | > :::hoge fuga 26 | > custom block in blockquote 27 | > ::: 28 | MD 29 | @doc = QiitaMarker.render_doc(text, [:UNSAFE], [:custom_block]) 30 | end 31 | 32 | def test_to_html 33 | @expected = <<~HTML 34 | <div data-type="customblock" data-metadata="foo bar"> 35 | <p>message</p> 36 | <ul> 37 | <li>list1</li> 38 | <li>list2</li> 39 | </ul> 40 | <pre><code class="language-ruby">puts 'hello' 41 | </code></pre> 42 | <div>html block</div> 43 | </div> 44 | <div data-type="customblock" data-metadata="fizz buzz"> 45 | <p>second custom block</p> 46 | </div> 47 | <blockquote> 48 | <div data-type="customblock" data-metadata="hoge fuga"> 49 | <p>custom block in blockquote</p> 50 | </div> 51 | </blockquote> 52 | HTML 53 | 54 | assert_equal(@expected, @doc.to_html([:UNSAFE], [:custom_block])) 55 | end 56 | 57 | def test_to_html_with_sourcepos 58 | @expected = <<~HTML 59 | <div data-type="customblock" data-metadata="foo bar" data-sourcepos="1:1-12:3"> 60 | <p data-sourcepos="2:1-2:7">message</p> 61 | <ul data-sourcepos="4:1-6:0"> 62 | <li data-sourcepos="4:1-4:7">list1</li> 63 | <li data-sourcepos="5:1-6:0">list2</li> 64 | </ul> 65 | <pre data-sourcepos="7:1-9:3"><code class="language-ruby">puts 'hello' 66 | </code></pre> 67 | <div>html block</div> 68 | </div> 69 | <div data-type="customblock" data-metadata="fizz buzz" data-sourcepos="14:1-16:3"> 70 | <p data-sourcepos="15:1-15:19">second custom block</p> 71 | </div> 72 | <blockquote data-sourcepos="18:1-20:5"> 73 | <div data-type="customblock" data-metadata="hoge fuga" data-sourcepos="18:3-20:5"> 74 | <p data-sourcepos="19:3-19:28">custom block in blockquote</p> 75 | </div> 76 | </blockquote> 77 | HTML 78 | 79 | assert_equal(@expected, @doc.to_html([:UNSAFE, :SOURCEPOS], [:custom_block])) 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /test/test_qfm_mention_no_emphasis.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | class TestQfmMentionNoEmphasis < Minitest::Test 6 | describe "with mention_no_emphasis option" do 7 | [ 8 | ["@_username_", false], 9 | ["@__username__", false], 10 | ["@___username___", false], 11 | ["@user__name__", false], 12 | ["@some__user__name__", false], 13 | [" @_username_", false], 14 | ["あ@_username_", false], 15 | ["A@_username_", true], 16 | ["@*username*", true], 17 | ["_foo_", true], 18 | ["_", false], 19 | ["_foo @username_", false], 20 | ["__foo @username__", false], 21 | ["___foo @username___", false], 22 | ].each do |text, emphasize| 23 | describe "with text #{text.inspect}" do 24 | if emphasize 25 | it "emphasizes the text" do 26 | QiitaMarker.render_html(text, :MENTION_NO_EMPHASIS).tap do |out| 27 | assert_match(/(<em>|<strong>)/, out.chomp) 28 | end 29 | end 30 | else 31 | it "does not emphasize the text" do 32 | QiitaMarker.render_html(text, :MENTION_NO_EMPHASIS).tap do |out| 33 | assert_match "<p>#{text.strip}</p>", out.chomp 34 | end 35 | end 36 | end 37 | end 38 | end 39 | end 40 | 41 | describe "without mention_no_emphasis option" do 42 | describe 'with text "@_username_"' do 43 | text = "@_username_" 44 | it "emphasizes the text" do 45 | QiitaMarker.render_html(text, :DEFAULT, []).tap do |out| 46 | assert_match(/<em>/, out.chomp) 47 | end 48 | end 49 | end 50 | 51 | describe 'with text "_foo @username_"' do 52 | text = "_foo @username_" 53 | it "emphasizes the text" do 54 | QiitaMarker.render_html(text, :DEFAULT, []).tap do |out| 55 | assert_match(/<em>/, out.chomp) 56 | end 57 | end 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /test/test_renderer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | class TestRenderer < Minitest::Test 6 | def setup 7 | @doc = QiitaMarker.render_doc("Hi *there*") 8 | end 9 | 10 | def test_html_renderer 11 | renderer = HtmlRenderer.new 12 | result = renderer.render(@doc) 13 | 14 | assert_equal("<p>Hi <em>there</em></p>\n", result) 15 | end 16 | 17 | def test_multiple_tables 18 | content = <<~DOC 19 | | Input | Expected | Actual | 20 | | ----------- | ---------------- | --------- | 21 | | One | Two | Three | 22 | 23 | | Header | Row | Example | 24 | | :------: | ---: | :------ | 25 | | Foo | Bar | Baz | 26 | DOC 27 | doc = QiitaMarker.render_doc(content, :DEFAULT, [:autolink, :table, :tagfilter]) 28 | results = QiitaMarker::HtmlRenderer.new.render(doc) 29 | 30 | assert_equal(2, results.scan(/<tbody>/).size) 31 | end 32 | 33 | def test_escape_html_encoding 34 | my_renderer = Class.new(HtmlRenderer) do 35 | attr_reader :input_encoding, :output_encoding 36 | 37 | def text(node) 38 | @input_encoding = node.string_content.encoding 39 | escape_html(node.string_content).tap do |escaped| 40 | @output_encoding = escaped.encoding 41 | end 42 | end 43 | end 44 | 45 | renderer = my_renderer.new 46 | 47 | assert_equal(Encoding::UTF_8, renderer.render(@doc).encoding) 48 | assert_equal(renderer.input_encoding, renderer.output_encoding) 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /test/test_smartpunct.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | class SmartPunctTest < Minitest::Test 6 | smart_punct = open_spec_file("smart_punct.txt") 7 | 8 | smart_punct.each do |testcase| 9 | doc = QiitaMarker.render_doc(testcase[:markdown], :SMART) 10 | html = QiitaMarker.render_html(testcase[:markdown], :SMART) 11 | 12 | define_method("test_smart_punct_example_#{testcase[:example]}") do 13 | doc_rendered = doc.to_html.strip 14 | html_rendered = html.strip 15 | 16 | assert_equal testcase[:html], doc_rendered, testcase[:markdown] 17 | assert_equal testcase[:html], html_rendered, testcase[:markdown] 18 | end 19 | end 20 | 21 | def test_smart_hardbreak_no_spaces_render_doc 22 | markdown = "\"foo\"\nbaz" 23 | result = "<p>“foo”<br />\nbaz</p>\n" 24 | doc = QiitaMarker.render_doc(markdown, :SMART) 25 | 26 | assert_equal(result, doc.to_html([:HARDBREAKS])) 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /test/test_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | require "json" 5 | 6 | class TestSpec < Minitest::Test 7 | spec = open_spec_file("spec.txt") 8 | 9 | spec.each do |testcase| 10 | next if testcase[:extensions].include?(:disabled) 11 | 12 | doc = QiitaMarker.render_doc(testcase[:markdown], :DEFAULT, testcase[:extensions]) 13 | 14 | define_method("test_to_html_example_#{testcase[:example]}") do 15 | actual = doc.to_html(:UNSAFE, testcase[:extensions]).rstrip 16 | 17 | assert_equal testcase[:html], actual, testcase[:markdown] 18 | end 19 | 20 | define_method("test_html_renderer_example_#{testcase[:example]}") do 21 | actual = HtmlRenderer.new(options: :UNSAFE, extensions: testcase[:extensions]).render(doc).rstrip 22 | 23 | assert_equal testcase[:html], actual, testcase[:markdown] 24 | end 25 | 26 | define_method("test_sourcepos_example_#{testcase[:example]}") do 27 | lhs = doc.to_html([:UNSAFE, :SOURCEPOS], testcase[:extensions]).rstrip 28 | rhs = HtmlRenderer.new(options: [:UNSAFE, :SOURCEPOS], extensions: testcase[:extensions]).render(doc).rstrip 29 | 30 | assert_equal lhs, rhs, testcase[:markdown] 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /test/test_tasklists.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | class TestTasklists < Minitest::Test 6 | def setup 7 | text = <<-MD 8 | - [x] Add task list 9 | - [ ] Define task list 10 | MD 11 | @doc = QiitaMarker.render_doc(text, :DEFAULT, [:tasklist]) 12 | @expected = <<~HTML 13 | <ul> 14 | <li><input type="checkbox" checked="" disabled="" /> Add task list</li> 15 | <li><input type="checkbox" disabled="" /> Define task list</li> 16 | </ul> 17 | HTML 18 | end 19 | 20 | def test_to_html 21 | assert_equal(@expected, @doc.to_html) 22 | end 23 | 24 | def test_html_renderer 25 | assert_equal(@expected, QiitaMarker::HtmlRenderer.new.render(@doc)) 26 | end 27 | 28 | def test_tasklist_state 29 | list = @doc.first_child 30 | 31 | assert_equal("checked", list.first_child.tasklist_state) 32 | assert_predicate(list.first_child, :tasklist_item_checked?) 33 | assert_equal("unchecked", list.first_child.next.tasklist_state) 34 | refute_predicate(list.first_child.next, :tasklist_item_checked?) 35 | end 36 | 37 | def test_set_tasklist_state 38 | list = @doc.first_child 39 | list.first_child.tasklist_item_checked = false 40 | 41 | refute_predicate(list.first_child, :tasklist_item_checked?) 42 | list.first_child.next.tasklist_item_checked = true 43 | 44 | assert_predicate(list.first_child.next, :tasklist_item_checked?) 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /test/test_xml.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "test_helper" 4 | 5 | class TestXml < Minitest::Test 6 | def setup 7 | @markdown = <<~MD 8 | Hi *there*! 9 | 10 | 1. I am a numeric list. 11 | 2. I continue the list. 12 | * Suddenly, an unordered list! 13 | * What fun! 14 | 15 | Okay, _enough_. 16 | 17 | | a | b | 18 | | --- | --- | 19 | | c | d | 20 | MD 21 | end 22 | 23 | def render_doc(doc) 24 | QiitaMarker.render_doc(doc, :DEFAULT, [:table]) 25 | end 26 | 27 | def test_to_xml 28 | compare = render_doc(@markdown).to_xml(:SOURCEPOS) 29 | 30 | assert_equal(<<~XML, compare) 31 | <?xml version="1.0" encoding="UTF-8"?> 32 | <!DOCTYPE document SYSTEM "CommonMark.dtd"> 33 | <document sourcepos="1:1-12:13" xmlns="http://commonmark.org/xml/1.0"> 34 | <paragraph sourcepos="1:1-1:11"> 35 | <text sourcepos="1:1-1:3" xml:space="preserve">Hi </text> 36 | <emph sourcepos="1:4-1:10"> 37 | <text sourcepos="1:5-1:9" xml:space="preserve">there</text> 38 | </emph> 39 | <text sourcepos="1:11-1:11" xml:space="preserve">!</text> 40 | </paragraph> 41 | <list sourcepos="3:1-4:23" type="ordered" start="1" delim="period" tight="true"> 42 | <item sourcepos="3:1-3:23"> 43 | <paragraph sourcepos="3:4-3:23"> 44 | <text sourcepos="3:4-3:23" xml:space="preserve">I am a numeric list.</text> 45 | </paragraph> 46 | </item> 47 | <item sourcepos="4:1-4:23"> 48 | <paragraph sourcepos="4:4-4:23"> 49 | <text sourcepos="4:4-4:23" xml:space="preserve">I continue the list.</text> 50 | </paragraph> 51 | </item> 52 | </list> 53 | <list sourcepos="5:1-7:0" type="bullet" tight="true"> 54 | <item sourcepos="5:1-5:30"> 55 | <paragraph sourcepos="5:3-5:30"> 56 | <text sourcepos="5:3-5:30" xml:space="preserve">Suddenly, an unordered list!</text> 57 | </paragraph> 58 | </item> 59 | <item sourcepos="6:1-7:0"> 60 | <paragraph sourcepos="6:3-6:11"> 61 | <text sourcepos="6:3-6:11" xml:space="preserve">What fun!</text> 62 | </paragraph> 63 | </item> 64 | </list> 65 | <paragraph sourcepos="8:1-8:15"> 66 | <text sourcepos="8:1-8:6" xml:space="preserve">Okay, </text> 67 | <emph sourcepos="8:7-8:14"> 68 | <text sourcepos="8:8-8:13" xml:space="preserve">enough</text> 69 | </emph> 70 | <text sourcepos="8:15-8:15" xml:space="preserve">.</text> 71 | </paragraph> 72 | <table sourcepos="10:1-12:13"> 73 | <table_header sourcepos="10:1-10:13"> 74 | <table_cell sourcepos="10:2-10:6"> 75 | <text sourcepos="10:3-10:3" xml:space="preserve">a</text> 76 | </table_cell> 77 | <table_cell sourcepos="10:8-10:12"> 78 | <text sourcepos="10:9-10:9" xml:space="preserve">b</text> 79 | </table_cell> 80 | </table_header> 81 | <table_row sourcepos="12:1-12:13"> 82 | <table_cell sourcepos="12:2-12:6"> 83 | <text sourcepos="12:3-12:3" xml:space="preserve">c</text> 84 | </table_cell> 85 | <table_cell sourcepos="12:8-12:12"> 86 | <text sourcepos="12:9-12:9" xml:space="preserve">d</text> 87 | </table_cell> 88 | </table_row> 89 | </table> 90 | </document> 91 | XML 92 | end 93 | 94 | def test_to_xml_with_quotes 95 | compare = render_doc('"quotes" should be escaped').to_xml(:DEFAULT) 96 | 97 | assert_equal(<<~XML, compare) 98 | <?xml version="1.0" encoding="UTF-8"?> 99 | <!DOCTYPE document SYSTEM "CommonMark.dtd"> 100 | <document xmlns="http://commonmark.org/xml/1.0"> 101 | <paragraph> 102 | <text xml:space="preserve">&quot;quotes&quot; should be escaped</text> 103 | </paragraph> 104 | </document> 105 | XML 106 | end 107 | end 108 | --------------------------------------------------------------------------------
", "a", "", "c", "