├── .codecov.yml
├── .github
└── workflows
│ ├── ci-bench.yml
│ ├── ci-build.yml
│ └── ci-release.yml
├── .gitignore
├── .golangci.yml
├── .goreleaser.yml
├── CONTRIBUTE.adoc
├── LICENSE
├── LIMITATIONS.adoc
├── Makefile
├── README.adoc
├── cmd
└── libasciidoc
│ ├── cmd_suite_test.go
│ ├── main.go
│ ├── root_cmd.go
│ ├── root_cmd_test.go
│ ├── test
│ ├── admonition.adoc
│ ├── doc_with_attributes.adoc
│ └── test.adoc
│ ├── version_cmd.go
│ └── version_cmd_test.go
├── code-gen
└── renderer-templates
│ └── main.go
├── docs
└── design.adoc
├── go.mod
├── go.sum
├── libasciidoc.go
├── libasciidoc_bench_test.go
├── libasciidoc_suite_test.go
├── libasciidoc_test.go
├── make
├── bench.mk
├── clean.mk
├── git.mk
├── go.mk
├── help.mk
├── lint.mk
├── profile.mk
├── test.mk
└── verbose.mk
├── pkg
├── configuration
│ ├── configuration.go
│ └── macro_template.go
├── parser
│ ├── attribute_substitution_test.go
│ ├── attributes_test.go
│ ├── bench_test.go
│ ├── blank_line_test.go
│ ├── check_list_test.go
│ ├── comment_test.go
│ ├── context.go
│ ├── cross_reference_test.go
│ ├── delimited_block_admonition_test.go
│ ├── delimited_block_example_test.go
│ ├── delimited_block_fenced_test.go
│ ├── delimited_block_listing_test.go
│ ├── delimited_block_literal_test.go
│ ├── delimited_block_markdown_quote_test.go
│ ├── delimited_block_open_test.go
│ ├── delimited_block_passthrough_test.go
│ ├── delimited_block_quote_test.go
│ ├── delimited_block_sidebar_test.go
│ ├── delimited_block_source_test.go
│ ├── delimited_block_verse_test.go
│ ├── document_fragment_processing_test.go
│ ├── document_header_test.go
│ ├── document_preprocessing.go
│ ├── document_preprocessing_builder_test.go
│ ├── document_preprocessing_conditionals_test.go
│ ├── document_preprocessing_include_files_test.go
│ ├── document_processing.go
│ ├── document_processing_aggregate.go
│ ├── document_processing_aggregate_test.go
│ ├── document_processing_apply_substitutions.go
│ ├── document_processing_apply_substitutions_test.go
│ ├── document_processing_arrange_lists.go
│ ├── document_processing_collect_footnotes.go
│ ├── document_processing_collect_footnotes_test.go
│ ├── document_processing_filter_elements.go
│ ├── document_processing_filter_elements_test.go
│ ├── document_processing_insert_preamble_test.go
│ ├── document_processing_parse_fragments.go
│ ├── document_processing_parse_fragments_test.go
│ ├── document_processing_raw_source_test.go
│ ├── document_processing_refine_fragments.go
│ ├── document_substitutions.go
│ ├── document_substitutions_test.go
│ ├── document_test.go
│ ├── element_attributes_test.go
│ ├── escape_backslash_test.go
│ ├── footnote_test.go
│ ├── frontmatter_test.go
│ ├── generate.go
│ ├── icon_test.go
│ ├── image_test.go
│ ├── index_terms_test.go
│ ├── inline_button_test.go
│ ├── inline_elements_test.go
│ ├── inline_menu_test.go
│ ├── labeled_list_test.go
│ ├── line_break_test.go
│ ├── link_test.go
│ ├── mixed_lists_test.go
│ ├── ordered_list_test.go
│ ├── paragragh_debug_test.go
│ ├── paragragh_stats_test.go
│ ├── paragraph_test.go
│ ├── parser.go
│ ├── parser.peg
│ ├── parser_ext.go
│ ├── parser_ext_test.go
│ ├── parser_suite_test.go
│ ├── passthrough_test.go
│ ├── q_a_list_test.go
│ ├── quoted_string_test.go
│ ├── quoted_text_test.go
│ ├── section_test.go
│ ├── special_character_test.go
│ ├── stats.go
│ ├── symbol_test.go
│ ├── table_of_contents_test.go
│ ├── table_test.go
│ ├── thematic_break_test.go
│ ├── unordered_list_test.go
│ └── user_macro_test.go
├── renderer
│ ├── renderer.go
│ ├── renderer_suite_test.go
│ └── sgml
│ │ ├── attribution.go
│ │ ├── blank_line.go
│ │ ├── context.go
│ │ ├── cross_reference.go
│ │ ├── delimited_block.go
│ │ ├── delimited_block_admonition.go
│ │ ├── delimited_block_discard_lines.go
│ │ ├── delimited_block_example.go
│ │ ├── delimited_block_fenced.go
│ │ ├── delimited_block_listing.go
│ │ ├── delimited_block_markdown_quote.go
│ │ ├── delimited_block_open.go
│ │ ├── delimited_block_passthrough.go
│ │ ├── delimited_block_quote.go
│ │ ├── delimited_block_sidebar.go
│ │ ├── delimited_block_source.go
│ │ ├── delimited_block_verse.go
│ │ ├── document_details.go
│ │ ├── element_id.go
│ │ ├── element_role.go
│ │ ├── element_style.go
│ │ ├── elements.go
│ │ ├── footnote_reference.go
│ │ ├── html5
│ │ ├── article.go
│ │ ├── blank_line.go
│ │ ├── blank_line_test.go
│ │ ├── callout_list.go
│ │ ├── check_list_test.go
│ │ ├── comment_test.go
│ │ ├── cross_reference.go
│ │ ├── cross_reference_test.go
│ │ ├── delimited_block_admonition_test.go
│ │ ├── delimited_block_admonition_tmpl.go
│ │ ├── delimited_block_example_test.go
│ │ ├── delimited_block_example_tmpl.go
│ │ ├── delimited_block_fenced_test.go
│ │ ├── delimited_block_fenced_tmpl.go
│ │ ├── delimited_block_listing_test.go
│ │ ├── delimited_block_listing_tmpl.go
│ │ ├── delimited_block_literal_test.go
│ │ ├── delimited_block_literal_tmpl.go
│ │ ├── delimited_block_markdown_quote_test.go
│ │ ├── delimited_block_markdown_quote_tmpl.go
│ │ ├── delimited_block_open_test.go
│ │ ├── delimited_block_open_tmpl.go
│ │ ├── delimited_block_passthrough_test.go
│ │ ├── delimited_block_passthrough_tmpl.go
│ │ ├── delimited_block_quote_test.go
│ │ ├── delimited_block_quote_tmpl.go
│ │ ├── delimited_block_sidebar_test.go
│ │ ├── delimited_block_sidebar_tmpl.go
│ │ ├── delimited_block_source_test.go
│ │ ├── delimited_block_source_tmpl.go
│ │ ├── delimited_block_verse_test.go
│ │ ├── delimited_block_verse_tmpl.go
│ │ ├── distinct_lists_test.go
│ │ ├── document_details.go
│ │ ├── document_details_test.go
│ │ ├── file_inclusion_test.go
│ │ ├── footnote_reference.go
│ │ ├── footnote_reference_test.go
│ │ ├── front_matter_test.go
│ │ ├── html5.go
│ │ ├── html5_suite_test.go
│ │ ├── html5_test.go
│ │ ├── icon.go
│ │ ├── icon_test.go
│ │ ├── image.go
│ │ ├── image_test.go
│ │ ├── index_terms_test.go
│ │ ├── inline_button.go
│ │ ├── inline_button_test.go
│ │ ├── inline_menu.go
│ │ ├── inline_menu_test.go
│ │ ├── labeled_list.go
│ │ ├── labeled_list_test.go
│ │ ├── link.go
│ │ ├── link_test.go
│ │ ├── manpage.go
│ │ ├── ordered_list.go
│ │ ├── ordered_list_test.go
│ │ ├── paragraph.go
│ │ ├── paragraph_test.go
│ │ ├── passthrough_test.go
│ │ ├── preamble.go
│ │ ├── quoted_string_test.go
│ │ ├── quoted_text.go
│ │ ├── quoted_text_test.go
│ │ ├── section.go
│ │ ├── section_test.go
│ │ ├── special_character_test.go
│ │ ├── string_test.go
│ │ ├── symbol_test.go
│ │ ├── table.go
│ │ ├── table_of_contents.go
│ │ ├── table_of_contents_test.go
│ │ ├── table_test.go
│ │ ├── templates.go
│ │ ├── templates_test.go
│ │ ├── thematic_break_test.go
│ │ ├── unordered_list.go
│ │ ├── unordered_list_test.go
│ │ └── user_macro_test.go
│ │ ├── html_escape.go
│ │ ├── icon.go
│ │ ├── image.go
│ │ ├── index_term.go
│ │ ├── inline_button.go
│ │ ├── inline_elements.go
│ │ ├── inline_menu.go
│ │ ├── link.go
│ │ ├── list.go
│ │ ├── literal_blocks.go
│ │ ├── paragraph.go
│ │ ├── passthrough.go
│ │ ├── preamble.go
│ │ ├── predefined_attribute.go
│ │ ├── predefined_attribute_test.go
│ │ ├── quoted_text.go
│ │ ├── render_plain_text.go
│ │ ├── section.go
│ │ ├── sgml.go
│ │ ├── sgml_renderer.go
│ │ ├── sgml_suite_test.go
│ │ ├── special_character.go
│ │ ├── string.go
│ │ ├── symbol.go
│ │ ├── table.go
│ │ ├── table_of_contents.go
│ │ ├── templates.go
│ │ ├── user_macro.go
│ │ └── xhtml5
│ │ ├── article.go
│ │ ├── blank_line.go
│ │ ├── blank_line_test.go
│ │ ├── comment_test.go
│ │ ├── cross_reference_test.go
│ │ ├── delimited_block_admonition_test.go
│ │ ├── delimited_block_example_test.go
│ │ ├── delimited_block_fenced_test.go
│ │ ├── delimited_block_listing_test.go
│ │ ├── delimited_block_literal_test.go
│ │ ├── delimited_block_markdown_quote_test.go
│ │ ├── delimited_block_passthrough_test.go
│ │ ├── delimited_block_quote_test.go
│ │ ├── delimited_block_quote_tmpl.go
│ │ ├── delimited_block_sidebar_test.go
│ │ ├── delimited_block_source_test.go
│ │ ├── delimited_block_verse_test.go
│ │ ├── delimited_block_verse_tmpl.go
│ │ ├── distinct_lists_test.go
│ │ ├── document_details.go
│ │ ├── document_details_test.go
│ │ ├── file_inclusion_test.go
│ │ ├── footnote_reference.go
│ │ ├── footnote_reference_test.go
│ │ ├── icon.go
│ │ ├── icon_test.go
│ │ ├── image.go
│ │ ├── image_test.go
│ │ ├── index_terms_test.go
│ │ ├── labeled_list.go
│ │ ├── labeled_list_test.go
│ │ ├── link_test.go
│ │ ├── ordered_list_test.go
│ │ ├── paragraph.go
│ │ ├── paragraph_test.go
│ │ ├── passthrough_test.go
│ │ ├── quoted_string_test.go
│ │ ├── quoted_text_test.go
│ │ ├── section_test.go
│ │ ├── string_test.go
│ │ ├── suite_test.go
│ │ ├── table.go
│ │ ├── table_of_contents_test.go
│ │ ├── table_test.go
│ │ ├── thematic_break_test.go
│ │ ├── unordered_list_test.go
│ │ ├── user_macro_test.go
│ │ ├── xhtml5.go
│ │ └── xhtml5_test.go
├── types
│ ├── attributes.go
│ ├── document_xrefs.go
│ ├── footnotes.go
│ ├── non_alphanumeric_replacement_test.go
│ ├── non_alphanumerics_replacement.go
│ ├── predefined_attributes.go
│ ├── section_numbering_test.go
│ ├── types.go
│ ├── types_suite_test.go
│ ├── types_test.go
│ ├── types_utils.go
│ └── types_utils_test.go
└── validator
│ ├── validator.go
│ ├── validator_suite_test.go
│ └── validator_test.go
├── test
├── bench
│ ├── mocking.adoc
│ └── vertx-examples.adoc
├── compat
│ ├── article.adoc
│ ├── article.html
│ ├── basic.adoc
│ ├── basic.html
│ ├── demo-full.tmpl.html
│ ├── demo-shorter.adoc
│ ├── demo-shorter.html
│ ├── demo.adoc
│ ├── demo.html
│ ├── encoding.adoc
│ ├── encoding.html
│ ├── exline-mono.adoc
│ ├── exline-mono.html
│ ├── include.adoc
│ ├── includes
│ │ ├── chapter-a.adoc
│ │ ├── child-include.adoc
│ │ └── grandchild-include.adoc
│ ├── lists.adoc
│ ├── lists.html
│ ├── master.adoc
│ ├── master.html
│ ├── parent-include.adoc
│ ├── parent-include.html
│ ├── sample.adoc
│ ├── sample.html
│ ├── sscript.adoc
│ └── sscript.html
├── compat_test.go
├── doc.go
├── images
│ └── favicon-glasses-16x16.png
├── includes
│ ├── attributes.adoc
│ ├── chapter-a.adoc
│ ├── child-include-preamble.adoc
│ ├── child-include.adoc
│ ├── grandchild-include.adoc
│ ├── hello_world.go.txt
│ ├── include.foo
│ ├── parent-include-absolute-offset.adoc
│ ├── parent-include-relative-offset.adoc
│ ├── parent-include.adoc
│ ├── sample.html
│ ├── table_common.txt
│ ├── table_parent.adoc
│ ├── table_specific.txt
│ ├── tag-include-unclosed.adoc
│ └── tag-include.adoc
└── test_suite_test.go
├── testsupport
├── console_matcher.go
├── console_matcher_test.go
├── document_fragment_matcher.go
├── document_fragment_matcher_test.go
├── document_fragments_matcher.go
├── document_fragments_matcher_test.go
├── document_matcher.go
├── document_matcher_test.go
├── html5_matcher.go
├── html5_matcher_test.go
├── inline_elements_matcher.go
├── inline_elements_matcher_test.go
├── log_init.go
├── metadata_matcher.go
├── metadata_matcher_test.go
├── parse_document.go
├── parse_document_fragments.go
├── parse_document_fragments_test.go
├── parse_document_test.go
├── preparse_document.go
├── render_html5.go
├── render_html5_test.go
├── table_of_contents_matcher.go
├── table_of_contents_matcher_test.go
└── testsupport_suite_test.go
└── tools.go
/.codecov.yml:
--------------------------------------------------------------------------------
1 | codecov:
2 | token: 61ac59d6-e16b-4057-9b30-c541bde05a3f
3 | notify:
4 | require_ci_to_pass: yes
5 |
6 | coverage:
7 | status:
8 | project:
9 | default:
10 | # basic
11 | target: 80
12 | threshold: 2
13 | patch: yes
14 | changes: no
15 | precision: 2
16 | round: down
17 |
18 | parsers:
19 | gcov:
20 | branch_detection:
21 | conditional: yes
22 | loop: yes
23 | method: no
24 | macro: no
25 |
26 | comment:
27 | layout: "header, diff"
28 | behavior: default
29 | require_changes: no
30 |
31 | ignore:
32 | # - "path/to/folder" # ignore folders and all its contents
33 | # - "test_*.rb" # wildcards accepted
34 | # - "**/*.py" # glob accepted
35 | # - "[a-z]+/test_.*" # regexp accepted
36 | - "pkg/parser/parser.go"
--------------------------------------------------------------------------------
/.github/workflows/ci-bench.yml:
--------------------------------------------------------------------------------
1 | name: ci-bench
2 | on:
3 | pull_request_target:
4 | branches:
5 | - master
6 |
7 | jobs:
8 | bench:
9 | runs-on: ${{ matrix.os }}
10 | strategy:
11 | matrix:
12 | go-version: [1.18.x]
13 | os: [ubuntu-latest]
14 | name: Benchstat with Go ${{ matrix.go-version }}
15 |
16 | steps:
17 | - name: Install Go
18 | uses: actions/setup-go@v3
19 | with:
20 | go-version: ${{ matrix.go-version }}
21 |
22 | - name: Checkout code
23 | uses: actions/checkout@v3
24 | with:
25 | fetch-depth: 0
26 |
27 | - name: Cache dependencies
28 | uses: actions/cache@v3
29 | with:
30 | path: ~/go/pkg/mod
31 | key: ${{ runner.os }}-go-${{ hashFiles ('**/go.sum') }}
32 | restore-keys: |
33 | ${{ runner.os }}-go-
34 |
35 | - name: Verify parser
36 | run: |
37 | make verify-parser
38 |
39 | - name: Bench and Diff
40 | id: bench-diff
41 | run: |
42 | go install golang.org/x/perf/cmd/benchstat@latest
43 | make bench-diff
44 | DIFF_MASTER=$(make print-bench-diff-master)
45 | # because set-output doesn't support multiline content out-of-the-box
46 | DIFF_MASTER="${DIFF_MASTER//'%'/'%25'}"
47 | DIFF_MASTER="${DIFF_MASTER//$'\n'/'%0A'}"
48 | DIFF_MASTER="${DIFF_MASTER//$'\r'/'%0D'}"
49 | echo "::set-output name=diff-master::$DIFF_MASTER"
50 |
51 | DIFF_LATEST_RELEASE=$(make print-bench-diff-latest-release)
52 | # because set-output doesn't support multiline content out-of-the-box
53 | DIFF_LATEST_RELEASE="${DIFF_LATEST_RELEASE//'%'/'%25'}"
54 | DIFF_LATEST_RELEASE="${DIFF_LATEST_RELEASE//$'\n'/'%0A'}"
55 | DIFF_LATEST_RELEASE="${DIFF_LATEST_RELEASE//$'\r'/'%0D'}"
56 | echo "::set-output name=diff-latest-release::$DIFF_LATEST_RELEASE"
57 |
58 | - name: Comment Benchmark Diffs
59 | uses: marocchino/sticky-pull-request-comment@v2
60 | with:
61 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
62 | header: bench
63 | message: |
64 | Comparing with `master` branch:
65 |
66 | ```
67 | ${{ steps.bench-diff.outputs.diff-master }}
68 | ```
69 |
70 | Comparing with latest release:
71 |
72 | ```
73 | ${{ steps.bench-diff.outputs.diff-latest-release }}
74 | ```
75 |
76 |
--------------------------------------------------------------------------------
/.github/workflows/ci-build.yml:
--------------------------------------------------------------------------------
1 | name: ci-build
2 | on:
3 | push:
4 | branches:
5 | - master
6 | tags-ignore:
7 | - '*.*'
8 | pull_request:
9 | branches:
10 | - master
11 |
12 | jobs:
13 | test:
14 | runs-on: ${{ matrix.os }}
15 | strategy:
16 | matrix:
17 | go-version: [1.18.x,1.19.x]
18 | os: [ubuntu-latest, macos-latest, windows-latest]
19 | name: Test ${{ matrix.os }} with Go ${{ matrix.go-version }}
20 |
21 | steps:
22 | - name: Install Go
23 | uses: actions/setup-go@v3
24 | with:
25 | go-version: ${{ matrix.go-version }}
26 |
27 | - name: Checkout code
28 | uses: actions/checkout@v3
29 |
30 | - name: Cache dependencies
31 | uses: actions/cache@v3
32 | with:
33 | path: ~/go/pkg/mod
34 | key: ${{ runner.os }}-go-${{ hashFiles ('**/go.sum') }}
35 | restore-keys: |
36 | ${{ runner.os }}-go-
37 |
38 | - name: Verify parser
39 | if: runner.os == 'Linux'
40 | run: |
41 | make verify-parser
42 |
43 | - name: Build
44 | run: |
45 | make build
46 |
47 | - name: Test
48 | run: |
49 | make test-with-coverage
50 |
51 | - name: Codecov
52 | if: runner.os == 'Linux'
53 | uses: codecov/codecov-action@v3
54 | with:
55 | # Path to coverage file to upload
56 | file: coverprofile.out
57 |
58 | golangci:
59 | name: Lint with Go ${{ matrix.go-version }}
60 | runs-on: ubuntu-latest
61 | strategy:
62 | matrix:
63 | go-version: [1.18.x]
64 | steps:
65 | - name: Checkout code
66 | uses: actions/checkout@v3
67 |
68 | - name: Lint
69 | uses: golangci/golangci-lint-action@v3
70 | with:
71 | version: latest
72 | args: -c .golangci.yml
73 |
74 |
--------------------------------------------------------------------------------
/.github/workflows/ci-release.yml:
--------------------------------------------------------------------------------
1 | name: ci-release
2 |
3 | on:
4 | push:
5 | tags:
6 | - '*'
7 |
8 | permissions:
9 | contents: write
10 |
11 | jobs:
12 | goreleaser:
13 | runs-on: ubuntu-latest
14 | steps:
15 | -
16 | name: Checkout
17 | uses: actions/checkout@v3
18 | with:
19 | fetch-depth: 0
20 | -
21 | name: Fetch all tags
22 | run: git fetch --force --tags
23 | -
24 | name: Set up Go
25 | uses: actions/setup-go@v3
26 | with:
27 | go-version: 1.18
28 | -
29 | name: Run GoReleaser
30 | uses: goreleaser/goreleaser-action@v2
31 | with:
32 | distribution: goreleaser
33 | version: latest
34 | args: release --rm-dist
35 | env:
36 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ### Intellij ###
2 | .idea/
3 | *.iws
4 | *.iml
5 | /out/
6 | .idea_modules/
7 | atlassian-ide-plugin.xml
8 |
9 | ### Eclipse ###
10 | .metadata
11 | tmp/
12 | *.tmp
13 | *.bak
14 | *.swp
15 | *~.nib
16 | local.properties
17 | .settings/
18 | .loadpath
19 | .recommenders
20 |
21 | # Eclipse Core
22 | .project
23 |
24 | # External tool builders
25 | .externalToolBuilders/
26 |
27 | # Locally stored "Eclipse launch configurations"
28 | *.launch
29 |
30 | # Code Recommenders
31 | .recommenders/
32 |
33 |
34 | ### VisualStudioCode ###
35 | .vscode
36 |
37 | ### Go ###
38 | # Compiled Object files, Static and Dynamic libs (Shared Objects)
39 | *.o
40 | *.a
41 | *.so
42 | *.coverprofile
43 |
44 | # Folders
45 | _obj
46 | _test
47 |
48 | # binaries
49 | bin/
50 |
51 | # dep package manager
52 | vendor
53 |
54 | # exclude `tmp` and `dist` directories
55 | tmp
56 | dist
57 |
58 | # exclude code coverage merged results
59 | coverage.txt
60 | .DS_Store
61 | *.html
62 | *.test
63 | !test/**/*.html
64 | !pkg/**/*.html
65 | *libasciidoc.html
66 | profile*.*
67 | !make/*.mk
68 | coverprofile.out
69 |
--------------------------------------------------------------------------------
/.golangci.yml:
--------------------------------------------------------------------------------
1 | run:
2 | skip-dirs:
3 | - test/includes
4 | skip-files:
5 | - pkg/parser/parser.go # generated
6 | timeout: 10m
7 |
8 | linters:
9 | enable-all: false
10 | enable:
11 | - megacheck
12 | - govet
13 | - gocyclo
14 | - unused
15 | - gofmt
16 | - revive
17 | - misspell
18 | - exportloopref
19 | - nolintlint
20 | disable-all: false
21 | disable:
22 | - maligned
23 | - prealloc
24 | - scopelint
25 | - golint
26 | - asasalint
27 | presets:
28 | - bugs
29 | - unused
30 | fast: false
31 |
32 | linters-settings:
33 | revive:
34 | # Enable all available rules.
35 | # Default: false
36 | # enable-all-rules: true
37 | rules:
38 | # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#dot-imports
39 | - name: dot-imports
40 | disabled: true
41 | exhaustive:
42 | # check switch statements in generated files also
43 | check-generated: false
44 | # indicates that switch statements are to be considered exhaustive if a
45 | # 'default' case is present, even if all enum members aren't listed in the
46 | # switch
47 | default-signifies-exhaustive: true
48 | nolintlint:
49 | # Disable to ensure that all nolint directives actually have an effect.
50 | # Default: false
51 | allow-unused: false
52 | # Disable to ensure that nolint directives don't have a leading space.
53 | # Default: true
54 | allow-leading-space: false
55 | # Exclude following linters from requiring an explanation.
56 | # Default: []
57 | allow-no-explanation: []
58 | # Enable to require an explanation of nonzero length after each nolint directive.
59 | # Default: false
60 | require-explanation: false
61 | # Enable to require nolint directives to mention the specific linter being suppressed.
62 | # Default: false
63 | require-specific: true
--------------------------------------------------------------------------------
/.goreleaser.yml:
--------------------------------------------------------------------------------
1 | # This is an example goreleaser.yaml file with some sane defaults.
2 | # Make sure to check the documentation at http://goreleaser.com
3 | before:
4 | hooks:
5 | # you may remove this if you don't use vgo
6 | # - go mod download
7 | # you may remove this if you don't need go generate
8 | # - go generate ./...
9 | builds:
10 | -
11 | # Path to main.go file or main package.
12 | # Default is `.`.
13 | main: ./cmd/libasciidoc
14 | # Custom ldflags templates.
15 | # Default is `-s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}`.
16 | ldflags:
17 | - "-s -w -X github.com/bytesparadise/libasciidoc.BuildTag={{.Version}} -X github.com/bytesparadise/libasciidoc.BuildCommit={{.Commit}} -X github.com/bytesparadise/libasciidoc.BuildTime={{.Date}}"
18 | env:
19 | - CGO_ENABLED=0
20 |
21 | archives:
22 | - id: default
23 | replacements:
24 | darwin: Darwin
25 | linux: Linux
26 | windows: Windows
27 | 386: i386
28 | amd64: x86_64
29 |
30 | checksum:
31 | name_template: 'checksums.txt'
32 |
33 | snapshot:
34 | name_template: "{{ .Tag }}-next"
35 |
36 | changelog:
37 | use: github
38 | sort: asc
39 | groups:
40 | - title: Features
41 | regexp: "^.*feat[(\\w)(/(\\w))?]*:+.*$"
42 | order: 0
43 | - title: Refactoring
44 | regexp: "^.*refactor[(\\w)(/(\\w))?]*:+.*$"
45 | order: 1
46 | - title: 'Bug fixes'
47 | regexp: "^.*fix[(\\w)(/(\\w))?]*:+.*$"
48 | order: 2
49 | - title: Others
50 | order: 999
51 | filters:
52 | exclude:
53 | - '^docs:'
54 | - '^test:'
55 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # It's necessary to set this because some environments don't link sh -> bash.
2 | SHELL := /bin/bash
3 |
4 | include ./make/*.mk
5 | .DEFAULT_GOAL := help
--------------------------------------------------------------------------------
/cmd/libasciidoc/cmd_suite_test.go:
--------------------------------------------------------------------------------
1 | package main_test
2 |
3 | import (
4 | . "github.com/onsi/ginkgo/v2"
5 | . "github.com/onsi/gomega"
6 |
7 | "testing"
8 |
9 | _ "github.com/bytesparadise/libasciidoc/testsupport"
10 | )
11 |
12 | func TestCmd(t *testing.T) {
13 | RegisterFailHandler(Fail)
14 | RunSpecs(t, "Cmd Suite")
15 | }
16 |
--------------------------------------------------------------------------------
/cmd/libasciidoc/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "strings"
7 |
8 | "github.com/pkg/errors"
9 | "github.com/spf13/cobra"
10 | )
11 |
12 | func main() {
13 | rootCmd := NewRootCmd()
14 | versionCmd := NewVersionCmd()
15 | rootCmd.AddCommand(versionCmd)
16 | rootCmd.SetHelpCommand(helpCommand)
17 | if err := rootCmd.Execute(); err != nil {
18 | fmt.Println(err)
19 | os.Exit(1)
20 | }
21 | }
22 |
23 | var helpCommand = &cobra.Command{
24 | Use: "help [command]",
25 | Short: "Help about the command",
26 | RunE: func(c *cobra.Command, args []string) error {
27 | cmd, args, e := c.Root().Find(args)
28 | if cmd == nil || e != nil || len(args) > 0 {
29 | return errors.Errorf("unknown help topic: %v", strings.Join(args, " "))
30 | }
31 | helpFunc := cmd.HelpFunc()
32 | helpFunc(cmd, args)
33 | return nil
34 | },
35 | }
36 |
--------------------------------------------------------------------------------
/cmd/libasciidoc/test/admonition.adoc:
--------------------------------------------------------------------------------
1 | NOTE: this is a note
2 |
3 | [NOTE]
4 | a para note
5 |
6 | [NOTE]
7 | ----
8 | multiple
9 |
10 | paras
11 |
12 | ----
13 |
--------------------------------------------------------------------------------
/cmd/libasciidoc/test/doc_with_attributes.adoc:
--------------------------------------------------------------------------------
1 | :foo1: FOO1
2 |
3 | :!foo1:
4 |
5 | {foo1} and {foo2}
--------------------------------------------------------------------------------
/cmd/libasciidoc/test/test.adoc:
--------------------------------------------------------------------------------
1 | [NOTE]
2 | ----
3 | multiple
4 |
5 | paragraphs
6 |
7 | ----
--------------------------------------------------------------------------------
/cmd/libasciidoc/version_cmd.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/bytesparadise/libasciidoc"
7 | "github.com/spf13/cobra"
8 | )
9 |
10 | // NewVersionCmd returns the root command
11 | func NewVersionCmd() *cobra.Command {
12 | return &cobra.Command{
13 | Use: "version",
14 | Short: "Print the version and build info",
15 | Run: func(cmd *cobra.Command, args []string) {
16 | if libasciidoc.BuildTag != "" {
17 | fmt.Fprintf(cmd.OutOrStdout(), "version: %s\n", libasciidoc.BuildTag)
18 | } else {
19 | fmt.Fprintf(cmd.OutOrStdout(), "commit: %s\n", libasciidoc.BuildCommit)
20 | }
21 | fmt.Fprintf(cmd.OutOrStdout(), "build time: %s\n", libasciidoc.BuildTime)
22 | },
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/cmd/libasciidoc/version_cmd_test.go:
--------------------------------------------------------------------------------
1 | package main_test
2 |
3 | import (
4 | "bytes"
5 |
6 | main "github.com/bytesparadise/libasciidoc/cmd/libasciidoc"
7 |
8 | . "github.com/onsi/ginkgo/v2"
9 | . "github.com/onsi/gomega"
10 | )
11 |
12 | var _ = Describe("version cmd", func() {
13 |
14 | It("ok", func() {
15 | // given
16 | versionCmd := main.NewVersionCmd()
17 | buf := new(bytes.Buffer)
18 | versionCmd.SetOutput(buf)
19 | versionCmd.SetArgs([]string{})
20 | // when
21 | err := versionCmd.Execute()
22 | // then
23 | Expect(err).ToNot(HaveOccurred())
24 | Expect(buf.String()).ToNot(BeEmpty())
25 | })
26 |
27 | })
28 |
--------------------------------------------------------------------------------
/docs/design.adoc:
--------------------------------------------------------------------------------
1 | = Libasciidoc Design
2 |
3 | This document brifley explains how the library works, from parsing a document to rendering in HTML.
4 |
5 | == Types
6 |
7 | A document contains blocks, which can have attributes, nested blocks and elements.
8 |
9 | Blocks are of the following types:
10 |
11 | - Section
12 | - Paragraph
13 | - Delimited block
14 | - Image
15 | - Table
16 | - List
17 | - File inclusion
18 | - Comment
19 |
20 | Tables, lists and delimited blocks can contain other blocks.
21 |
22 | Blocks can also contain elements, such as:
23 |
24 | - quoted text
25 | - links
26 | - (inline) images
27 | - passthrough text
28 | - foot notes
29 | - user-defined macros
30 |
31 | Attributes are set within squate brackets (`[]`), both on top of the document blocks and as a suffix of elements.
32 |
33 |
34 | == Parsing the document
35 |
36 | First, the internal parser (generated after the link:../pkg/parser/parser.peg[grammar] returns a "draft document" in which the sections are not embedded in a hierarchical manner, other blocks are also not attached to their parent section, blanklines are present, document attributes declarations, substitutions and reset macros are also present, and file inclusions have been processed.
37 |
38 | This so-called "draft document" is then processed to return a "final document" in which the sections are organized in a hierarchical manner with their child blocks (paragraphs, delimited blocks, etc.) attached to them. Also, all document attribute substitutions have been processed (sometimes resulting in new elements such as links) and removed. Blank line elements have been stripped off, too (except in delimited blocks).
39 |
40 | == HTMl5 Rendering
41 |
42 | The HTML5 renderer takes the final document and applies templates for each element. Having the sections in a hierarchical manner makes life easier in term of surrounding `
` tags.
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/bytesparadise/libasciidoc
2 |
3 | go 1.17
4 |
5 | require (
6 | github.com/alecthomas/chroma/v2 v2.3.0
7 | github.com/davecgh/go-spew v1.1.1
8 | github.com/felixge/fgtrace v0.1.0
9 | github.com/google/go-cmp v0.5.9
10 | github.com/mna/pigeon v1.1.0
11 | github.com/onsi/ginkgo/v2 v2.7.0
12 | github.com/onsi/gomega v1.24.2
13 | github.com/pkg/errors v0.9.1
14 | github.com/pkg/profile v1.6.0
15 | github.com/sirupsen/logrus v1.7.0
16 | github.com/spf13/cobra v1.1.1
17 | github.com/spf13/pflag v1.0.5
18 | github.com/stretchr/testify v1.8.0
19 | gopkg.in/yaml.v2 v2.4.0
20 | )
21 |
22 | require (
23 | github.com/DataDog/gostackparse v0.5.0 // indirect
24 | github.com/dlclark/regexp2 v1.4.0 // indirect
25 | github.com/go-logr/logr v1.2.3 // indirect
26 | github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
27 | github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
28 | github.com/inconshreveable/mousetrap v1.0.0 // indirect
29 | github.com/pmezard/go-difflib v1.0.0 // indirect
30 | golang.org/x/mod v0.8.0 // indirect
31 | golang.org/x/net v0.17.0 // indirect
32 | golang.org/x/sys v0.13.0 // indirect
33 | golang.org/x/text v0.13.0 // indirect
34 | golang.org/x/tools v0.6.0 // indirect
35 | gopkg.in/yaml.v3 v3.0.1 // indirect
36 | )
37 |
38 | // include support for disabling unexported fields
39 | // TODO: still needed?
40 | replace github.com/davecgh/go-spew => github.com/flw-cn/go-spew v1.1.2-0.20200624141737-10fccbfd0b23
41 |
--------------------------------------------------------------------------------
/libasciidoc_bench_test.go:
--------------------------------------------------------------------------------
1 | package libasciidoc_test
2 |
3 | import (
4 | "strings"
5 | "testing"
6 |
7 | "github.com/bytesparadise/libasciidoc"
8 | "github.com/bytesparadise/libasciidoc/pkg/configuration"
9 |
10 | // pkgprofile "github.com/pkg/profile"
11 | "github.com/felixge/fgtrace"
12 | log "github.com/sirupsen/logrus"
13 | "github.com/stretchr/testify/require"
14 | )
15 |
16 | func BenchmarkRealDocumentProcessing(b *testing.B) {
17 | // defer pkgprofile.Start(pkgprofile.MemProfile).Stop()
18 | defer fgtrace.Config{Dst: fgtrace.File("./tmp/fgtrace.json")}.Trace().Stop() //nolint:errcheck
19 | log.SetLevel(log.ErrorLevel)
20 | b.Run("demo.adoc", processDocument("./test/compat/demo.adoc"))
21 | b.Run("vertx-examples.adoc", processDocument("./test/bench/vertx-examples.adoc"))
22 | b.Run("mocking.adoc", processDocument("./test/bench/mocking.adoc"))
23 | }
24 |
25 | func processDocument(filename string) func(b *testing.B) {
26 | return func(b *testing.B) {
27 | for i := 0; i < b.N; i++ {
28 | out := &strings.Builder{}
29 | _, err := libasciidoc.ConvertFile(out,
30 | configuration.NewConfiguration(
31 | configuration.WithFilename(filename),
32 | configuration.WithCSS([]string{"path/to/style.css"}),
33 | configuration.WithHeaderFooter(true)))
34 | require.NoError(b, err)
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/libasciidoc_suite_test.go:
--------------------------------------------------------------------------------
1 | package libasciidoc_test
2 |
3 | import (
4 | "testing"
5 |
6 | . "github.com/onsi/ginkgo/v2"
7 | . "github.com/onsi/gomega"
8 | )
9 |
10 | func TestLibasciidoc(t *testing.T) {
11 | RegisterFailHandler(Fail)
12 | RunSpecs(t, "Libasciidoc Suite")
13 | }
14 |
--------------------------------------------------------------------------------
/make/bench.mk:
--------------------------------------------------------------------------------
1 | REPORTS_DIR=./tmp/bench/reports
2 | BENCH_COUNT ?= 10
3 |
4 | # Detecting GOPATH and removing trailing "/" if any
5 | GOPATH = $(realpath $(shell go env GOPATH))
6 |
7 | .PHONY: bench
8 | ## run the top-level benchmarks
9 | bench: clean generate-optimized
10 | @mkdir -p $(REPORTS_DIR)
11 | @go test -tags bench -bench=. -benchmem -count=$(BENCH_COUNT) -run=XXX \
12 | github.com/bytesparadise/libasciidoc \
13 | | tee $(REPORTS_DIR)/$(GITHUB_SHA)-$(GIT_COMMIT_ID_SHORT).bench
14 | @echo "generated $(REPORTS_DIR)/$(GITHUB_SHA)-$(GIT_COMMIT_ID_SHORT).bench"
15 |
16 | .PHONY: bench-diff
17 | ## run the top-level benchmarks and compares with results of 'master' and 'v0.7.0'
18 | bench-diff: clean generate-optimized check-git-status
19 | @git config advice.detachedHead false
20 | @mkdir -p $(REPORTS_DIR)
21 | @go test -tags bench -bench=. -benchmem -count=$(BENCH_COUNT) -run=XXX \
22 | github.com/bytesparadise/libasciidoc \
23 | | tee $(REPORTS_DIR)/$(GITHUB_SHA)-$(GIT_COMMIT_ID_SHORT).bench
24 | @echo "generated $(REPORTS_DIR)/$(GITHUB_SHA)-$(GIT_COMMIT_ID_SHORT).bench"
25 | @git checkout master
26 | @go test -tags bench -bench=. -benchmem -count=$(BENCH_COUNT) -run=XXX \
27 | github.com/bytesparadise/libasciidoc \
28 | | tee $(REPORTS_DIR)/master.bench
29 | @echo "generated $(REPORTS_DIR)/master.bench"
30 | @git checkout v0.7.0
31 | @go test -tags bench -bench=. -benchmem -count=$(BENCH_COUNT) -run=XXX \
32 | github.com/bytesparadise/libasciidoc \
33 | | tee $(REPORTS_DIR)/v0.7.0.bench
34 | @echo "generated $(REPORTS_DIR)/v0.7.0.bench"
35 | @git checkout $(GITHUB_SHA)
36 | @echo "HEAD is now at $(shell git log -1 --format='%h') (expecting $(GITHUB_SHA))"
37 | @$(GOPATH)/bin/benchstat $(REPORTS_DIR)/master.bench $(REPORTS_DIR)/$(GITHUB_SHA)-$(GIT_COMMIT_ID_SHORT).bench >> $(REPORTS_DIR)/diffs-master.txt
38 | @$(GOPATH)/bin/benchstat $(REPORTS_DIR)/v0.7.0.bench $(REPORTS_DIR)/$(GITHUB_SHA)-$(GIT_COMMIT_ID_SHORT).bench >> $(REPORTS_DIR)/diffs-latest-release.txt
39 |
40 | .PHONY: print-bench-diff-master
41 | print-bench-diff-master:
42 | @cat $(REPORTS_DIR)/diffs-master.txt
43 |
44 | .PHONY: print-bench-diff-latest-release
45 | print-bench-diff-latest-release:
46 | @cat $(REPORTS_DIR)/diffs-latest-release.txt
47 |
48 | check-git-status:
49 | ifneq ("$(shell git status --porcelain)","")
50 | @echo "Repository contains uncommitted changes:"
51 | @git status --porcelain
52 | @exit 1
53 | else
54 | @echo "Repository has no uncommitted changes"
55 | endif
56 |
57 |
--------------------------------------------------------------------------------
/make/clean.mk:
--------------------------------------------------------------------------------
1 | clean:
2 | @-rm pkg/parser/parser.go
--------------------------------------------------------------------------------
/make/git.mk:
--------------------------------------------------------------------------------
1 | GIT_COMMIT_ID_SHORT := $(shell git rev-parse --short HEAD)
2 | ifneq ($(shell git status --porcelain),)
3 | GIT_COMMIT_ID_SHORT := $(GIT_COMMIT_ID_SHORT)-dirty
4 | endif
5 |
6 | # using the env var defined by GitHub Actions
7 | # (see https://docs.github.com/en/actions/learn-github-actions/environment-variables#default-environment-variables)
8 | GITHUB_SHA ?= $(shell git rev-parse --abbrev-ref HEAD)
9 |
10 | BUILD_TIME = `date -u '+%Y-%m-%dT%H:%M:%SZ'`
--------------------------------------------------------------------------------
/make/help.mk:
--------------------------------------------------------------------------------
1 | .PHONY: help
2 | # Based on https://gist.github.com/rcmachado/af3db315e31383502660
3 | ## Display this help text
4 | help:/
5 | $(info Available targets)
6 | $(info -----------------)
7 | @awk '/^[a-zA-Z\-%\_0-9]+:/ { \
8 | helpMessage = match(lastLine, /^## (.*)/); \
9 | helpCommand = substr($$1, 0, index($$1, ":")-1); \
10 | if (helpMessage) { \
11 | helpMessage = substr(lastLine, RSTART + 3, RLENGTH); \
12 | gsub(/##/, "\n ", helpMessage); \
13 | printf "%-35s - %s\n", helpCommand, helpMessage; \
14 | lastLine = "" \
15 | } \
16 | } \
17 | { hasComment = match(lastLine, /^## (.*)/); \
18 | if(hasComment) { \
19 | lastLine=lastLine$$0; \
20 | } \
21 | else { \
22 | lastLine = $$0 \
23 | } \
24 | }' $(MAKEFILE_LIST)
--------------------------------------------------------------------------------
/make/lint.mk:
--------------------------------------------------------------------------------
1 | .PHONY: install-golangci-lint
2 | ## Install development tools.
3 | install-golangci-lint:
4 | @curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(shell go env GOPATH)/bin
5 |
6 | .PHONY: lint
7 | ## run golangci-lint against project
8 | lint: install-golangci-lint
9 | @$(shell go env GOPATH)/bin/golangci-lint run -v -c .golangci.yml ./...
10 |
--------------------------------------------------------------------------------
/make/profile.mk:
--------------------------------------------------------------------------------
1 | .PHONY: profile
2 | ## run the profilers on the parser
3 | profile: clean generate-optimized
4 | @mkdir -p ./tmp/bench/reports
5 | @go test \
6 | -tags=bench \
7 | -cpuprofile=tmp/bench/reports/$(GITHUB_SHA)-$(GIT_COMMIT_ID_SHORT).cpu.prof \
8 | -memprofile tmp/bench/reports/$(GITHUB_SHA)-$(GIT_COMMIT_ID_SHORT).mem.prof \
9 | -bench=. \
10 | -benchtime=10x \
11 | github.com/bytesparadise/libasciidoc \
12 | -run=XXX
13 | @echo "generate CPU reports..."
14 | @go tool pprof -text -output=tmp/bench/reports/$(GITHUB_SHA)-$(GIT_COMMIT_ID_SHORT).cpu.txt \
15 | tmp/bench/reports/$(GITHUB_SHA)-$(GIT_COMMIT_ID_SHORT).cpu.prof
16 | ifndef CI
17 | @go tool pprof -svg -output=tmp/bench/reports/$(GITHUB_SHA)-$(GIT_COMMIT_ID_SHORT).cpu.svg \
18 | tmp/bench/reports/$(GITHUB_SHA)-$(GIT_COMMIT_ID_SHORT).cpu.prof
19 | endif
20 | @echo "generate memory reports"
21 | @go tool pprof -text -output=tmp/bench/reports/$(GITHUB_SHA)-$(GIT_COMMIT_ID_SHORT).mem.txt \
22 | tmp/bench/reports/$(GITHUB_SHA)-$(GIT_COMMIT_ID_SHORT).mem.prof
23 | ifndef CI
24 | @go tool pprof -svg -output=tmp/bench/reports/$(GITHUB_SHA)-$(GIT_COMMIT_ID_SHORT).mem.svg \
25 | tmp/bench/reports/$(GITHUB_SHA)-$(GIT_COMMIT_ID_SHORT).mem.prof
26 | endif
27 |
28 |
--------------------------------------------------------------------------------
/make/test.mk:
--------------------------------------------------------------------------------
1 | .PHONY: install-ginkgo
2 | ## Install development tools.
3 | install-ginkgo:
4 | @go install -v github.com/onsi/ginkgo/v2/ginkgo
5 | @ginkgo version
6 |
7 | .PHONY: test
8 | ## run all tests excluding fixtures and vendored packages
9 | test: clean generate-optimized install-ginkgo
10 | @ginkgo -r --randomize-all --randomize-suites --trace --race --compilers=0
11 |
12 | COVERPKGS := $(shell go list ./... | grep -v vendor | paste -sd "," -)
13 |
14 | .PHONY: test-with-coverage
15 | ## run all tests excluding fixtures and vendored packages
16 | test-with-coverage: generate-optimized install-ginkgo
17 | @echo $(COVERPKGS)
18 | @ginkgo -r --randomize-all --randomize-suites --trace --race --compilers=0 --cover -coverpkg $(COVERPKGS)
19 |
20 | .PHONY: test-fixtures
21 | ## run all fixtures tests
22 | test-fixtures: generate-optimized
23 | @ginkgo -r --randomize-all --randomize-suites --trace --race --compilers=0 -tags=fixtures --focus=fixtures
24 |
--------------------------------------------------------------------------------
/make/verbose.mk:
--------------------------------------------------------------------------------
1 | # When you run make VERBOSE=1 (the default), executed commands will be printed
2 | # before executed. If you run make VERBOSE=2 verbose flags are turned on and
3 | # quiet flags are turned off for various commands. Use V_FLAG in places where
4 | # you can toggle on/off verbosity using -v. Use Q_FLAG in places where you can
5 | # toggle on/off quiet mode using -q. Use S_FLAG where you want to toggle on/off
6 | # silence mode using -s...
7 | VERBOSE ?= 1
8 | Q = @
9 | Q_FLAG = -q
10 | QUIET_FLAG = --quiet
11 | V_FLAG =
12 | S_FLAG = -s
13 | X_FLAG =
14 | ifeq ($(VERBOSE),1)
15 | Q =
16 | endif
17 | ifeq ($(VERBOSE),2)
18 | Q =
19 | Q_FLAG =
20 | QUIET_FLAG =
21 | S_FLAG =
22 | V_FLAG = -v
23 | X_FLAG = -x
24 | endif
--------------------------------------------------------------------------------
/pkg/configuration/macro_template.go:
--------------------------------------------------------------------------------
1 | package configuration
2 |
3 | import "io"
4 |
5 | // MacroTemplate an interface of template for user macro.
6 | type MacroTemplate interface {
7 | Execute(wr io.Writer, data interface{}) error
8 | }
9 |
--------------------------------------------------------------------------------
/pkg/parser/document_preprocessing_builder_test.go:
--------------------------------------------------------------------------------
1 | package parser
2 |
3 | import (
4 | "github.com/bytesparadise/libasciidoc/pkg/configuration"
5 | "github.com/bytesparadise/libasciidoc/pkg/types"
6 |
7 | . "github.com/onsi/ginkgo/v2"
8 | . "github.com/onsi/gomega"
9 | )
10 |
11 | var _ = Describe("preprocessing condition stack", func() {
12 |
13 | It("should eval to false when pushing single disabled entry", func() {
14 | // given
15 | c := newConditions()
16 | ctx := NewParseContext(configuration.NewConfiguration())
17 | // when
18 | eval := c.push(ctx, &types.IfdefCondition{
19 | Name: "cookie",
20 | })
21 | // then
22 | Expect(eval).To(BeFalse())
23 | })
24 |
25 | It("should eval to true when pushing single enabled entry", func() {
26 | // given
27 | c := newConditions()
28 | ctx := NewParseContext(configuration.NewConfiguration(configuration.WithAttribute("cookie", "yummy")))
29 | // when
30 | eval := c.push(ctx, &types.IfdefCondition{
31 | Name: "cookie",
32 | })
33 | // then
34 | Expect(eval).To(BeTrue())
35 | })
36 |
37 | It("should update when pushing multiple entries", func() {
38 | // given
39 | c := newConditions()
40 | ctx := NewParseContext(configuration.NewConfiguration(
41 | configuration.WithAttribute("cookie", "yummy"),
42 | configuration.WithAttribute("chocolate", "dark"),
43 | configuration.WithAttribute("pasta", ""),
44 | ))
45 | // when
46 | eval := c.push(ctx, &types.IfdefCondition{
47 | Name: "cookie",
48 | })
49 | // then
50 | Expect(eval).To(BeTrue())
51 |
52 | // when
53 | eval = c.push(ctx, &types.IfdefCondition{
54 | Name: "cookie",
55 | })
56 | // then
57 | Expect(eval).To(BeTrue())
58 |
59 | // when
60 | eval = c.push(ctx, &types.IfdefCondition{
61 | Name: "unknown",
62 | })
63 | // then switch to false when condition is evaled to `false`
64 | Expect(eval).To(BeFalse())
65 |
66 | // when
67 | eval = c.push(ctx, &types.IfdefCondition{
68 | Name: "cookie",
69 | })
70 | // then remains to `false` because of `unknown` condition
71 | Expect(eval).To(BeFalse())
72 |
73 | // when
74 | eval = c.pop()
75 | // then remains to `false` because of `unknown` condition
76 | Expect(eval).To(BeFalse())
77 |
78 | // when
79 | eval = c.pop()
80 | // then back to `true`
81 | Expect(eval).To(BeTrue())
82 |
83 | })
84 | })
85 |
--------------------------------------------------------------------------------
/pkg/parser/document_processing.go:
--------------------------------------------------------------------------------
1 | package parser
2 |
3 | import (
4 | "io"
5 |
6 | "github.com/bytesparadise/libasciidoc/pkg/configuration"
7 | "github.com/bytesparadise/libasciidoc/pkg/types"
8 |
9 | "github.com/davecgh/go-spew/spew"
10 | log "github.com/sirupsen/logrus"
11 | )
12 |
13 | const bufferSize = 10
14 |
15 | // ParseDocument parses the content of the reader identitied by the filename and applies all the substitutions and arrangements
16 | func ParseDocument(r io.Reader, config *configuration.Configuration, opts ...Option) (*types.Document, error) {
17 | done := make(chan interface{})
18 | defer close(done)
19 |
20 | footnotes := types.NewFootnotes()
21 | doc, err := Aggregate(NewParseContext(config, opts...),
22 | // SplitHeader(done,
23 | FilterOut(done,
24 | ArrangeLists(done,
25 | CollectFootnotes(footnotes, done,
26 | ApplySubstitutions(NewParseContext(config, opts...), done, // needs to be before 'ArrangeLists'
27 | RefineFragments(NewParseContext(config, opts...), r, done,
28 | ParseDocumentFragments(NewParseContext(config, opts...), r, done),
29 | ),
30 | ),
31 | ),
32 | ),
33 | ),
34 | // ),
35 | )
36 | if err != nil {
37 | return nil, err
38 | }
39 | if len(footnotes.Notes) > 0 {
40 | doc.Footnotes = footnotes.Notes
41 | }
42 | if log.IsLevelEnabled(log.DebugLevel) {
43 | log.Debugf("parsed document:\n%s", spew.Sdump(doc))
44 | }
45 | return doc, nil
46 | }
47 |
--------------------------------------------------------------------------------
/pkg/parser/document_processing_collect_footnotes.go:
--------------------------------------------------------------------------------
1 | package parser
2 |
3 | import (
4 | "github.com/bytesparadise/libasciidoc/pkg/types"
5 | log "github.com/sirupsen/logrus"
6 | )
7 |
8 | func CollectFootnotes(n *types.Footnotes, done <-chan interface{}, fragmentStream <-chan types.DocumentFragment) chan types.DocumentFragment {
9 | processedFragmentStream := make(chan types.DocumentFragment, bufferSize)
10 | go func() {
11 | defer close(processedFragmentStream)
12 | for f := range fragmentStream {
13 | select {
14 | case <-done:
15 | log.WithField("pipeline_stage", "collect_footnotes").Debug("received 'done' signal")
16 | return
17 | case processedFragmentStream <- collectFootnotes(n, f):
18 | }
19 | }
20 | log.WithField("pipeline_stage", "collect_footnotes").Debug("done")
21 | }()
22 | return processedFragmentStream
23 | }
24 |
25 | func collectFootnotes(n *types.Footnotes, f types.DocumentFragment) types.DocumentFragment {
26 | if f.Error != nil {
27 | log.Debugf("skipping footnotes")
28 | return f
29 | }
30 | for _, e := range f.Elements {
31 | if log.IsLevelEnabled(log.DebugLevel) {
32 | log.Debugf("collecting footnotes in element of type '%T'", e)
33 | }
34 | if e, ok := e.(types.WithFootnotes); ok {
35 | e.SubstituteFootnotes(n)
36 | }
37 | }
38 | return f
39 | }
40 |
--------------------------------------------------------------------------------
/pkg/parser/document_processing_raw_source_test.go:
--------------------------------------------------------------------------------
1 | package parser_test
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/bytesparadise/libasciidoc/pkg/parser"
7 | "github.com/bytesparadise/libasciidoc/pkg/types"
8 |
9 | . "github.com/onsi/ginkgo/v2"
10 | . "github.com/onsi/gomega"
11 | )
12 |
13 | var _ = DescribeTable("'FileLocation' pattern",
14 | func(filename string, expected interface{}) {
15 | reader := strings.NewReader(filename)
16 | actual, err := parser.ParseReader(filename, reader, parser.Entrypoint("FileLocation"))
17 | Expect(err).ToNot(HaveOccurred())
18 | // GinkgoT().Log("actual result: %s", spew.Sdump(actual))
19 | // GinkgoT().Log("expected result: %s", spew.Sdump(expected))
20 | Expect(actual).To(Equal(expected))
21 | },
22 | Entry("'chapter-a.adoc'", "chapter-a.adoc", &types.Location{
23 | Path: "chapter-a.adoc",
24 | }),
25 | Entry("'chapter_a.adoc'", "chapter_a.adoc", &types.Location{
26 | Path: "chapter_a.adoc",
27 | }),
28 | Entry("'../../test/includes/chapter_a.adoc'", "../../test/includes/chapter_a.adoc", &types.Location{
29 | Path: "../../test/includes/chapter_a.adoc",
30 | }),
31 | Entry("'{includedir}/chapter-{foo}.adoc'", "{includedir}/chapter-{foo}.adoc", &types.Location{
32 | Path: "{includedir}/chapter-{foo}.adoc", // attribute substitutions are treared as part of the string element
33 | }),
34 | Entry("'{scheme}://{path}'", "{scheme}://{path}", &types.Location{
35 | Path: "{scheme}://{path}",
36 | }),
37 | )
38 |
39 | var _ = DescribeTable("check asciidoc file",
40 | func(path string, expectation bool) {
41 | Expect(parser.IsAsciidoc(path)).To(Equal(expectation))
42 | },
43 | Entry("foo.adoc", "foo.adoc", true),
44 | Entry("foo.asc", "foo.asc", true),
45 | Entry("foo.ad", "foo.ad", true),
46 | Entry("foo.asciidoc", "foo.asciidoc", true),
47 | Entry("foo.txt", "foo.txt", true),
48 | Entry("foo.csv", "foo.csv", false),
49 | Entry("foo.go", "foo.go", false),
50 | )
51 |
--------------------------------------------------------------------------------
/pkg/parser/frontmatter_test.go:
--------------------------------------------------------------------------------
1 | package parser_test
2 |
3 | import (
4 | "github.com/bytesparadise/libasciidoc/pkg/types"
5 | . "github.com/bytesparadise/libasciidoc/testsupport"
6 |
7 | . "github.com/onsi/ginkgo/v2"
8 | . "github.com/onsi/gomega"
9 | )
10 |
11 | var _ = Describe("front-matters", func() {
12 |
13 | Context("in final documents", func() {
14 |
15 | Context("yaml front-matter", func() {
16 |
17 | It("with simple attributes", func() {
18 | source := `---
19 | title: a title
20 | author: Xavier
21 | ---
22 |
23 | first paragraph`
24 | expected := &types.Document{
25 | Elements: []interface{}{
26 | &types.FrontMatter{
27 | Attributes: types.Attributes{
28 | "title": "a title", // TODO: convert `title` attribute from front-matter into `doctitle` here ?
29 | "author": "Xavier",
30 | },
31 | },
32 | &types.Paragraph{
33 | Elements: []interface{}{
34 | &types.StringElement{Content: "first paragraph"},
35 | },
36 | },
37 | },
38 | }
39 | Expect(ParseDocument(source)).To(MatchDocument(expected))
40 | })
41 |
42 | It("empty front-matter", func() {
43 | source := `---
44 | ---
45 |
46 | first paragraph`
47 | expected := &types.Document{
48 | Elements: []interface{}{
49 | &types.FrontMatter{},
50 | &types.Paragraph{
51 | Elements: []interface{}{
52 | &types.StringElement{Content: "first paragraph"},
53 | },
54 | },
55 | },
56 | }
57 | Expect(ParseDocument(source)).To(MatchDocument(expected))
58 | })
59 | })
60 | })
61 | })
62 |
--------------------------------------------------------------------------------
/pkg/parser/generate.go:
--------------------------------------------------------------------------------
1 | package parser
2 |
3 | //go:generate pigeon -optimize-parser -optimize-grammar -alternate-entrypoints DocumentRawLine,DocumentFragment,NormalGroup,AttributeStructuredValue,DelimitedBlockElements,AttributeDeclarationValue,FileLocation,IncludedFileLine,MarkdownQuoteAttribution,BlockAttributes,InlineAttributes,TableColumnsAttribute,LineRanges,TagRanges,DocumentAuthorFullName -o parser.go parser.peg
4 |
--------------------------------------------------------------------------------
/pkg/parser/inline_button_test.go:
--------------------------------------------------------------------------------
1 | package parser_test
2 |
3 | import (
4 | "github.com/bytesparadise/libasciidoc/pkg/types"
5 | . "github.com/bytesparadise/libasciidoc/testsupport"
6 |
7 | . "github.com/onsi/ginkgo/v2"
8 | . "github.com/onsi/gomega"
9 | )
10 |
11 | var _ = Describe("inline buttons", func() {
12 |
13 | Context("in final documents", func() {
14 |
15 | It("when experimental is enabled", func() {
16 | source := `:experimental:
17 |
18 | Click on btn:[OK].`
19 | expected := &types.Document{
20 | Elements: []interface{}{
21 | &types.AttributeDeclaration{
22 | Name: "experimental",
23 | },
24 | &types.Paragraph{
25 | Elements: []interface{}{
26 | &types.StringElement{
27 | Content: "Click on ",
28 | },
29 | &types.InlineButton{
30 | Attributes: types.Attributes{
31 | types.AttrButtonLabel: "OK",
32 | },
33 | },
34 | &types.StringElement{
35 | Content: ".",
36 | },
37 | },
38 | },
39 | },
40 | }
41 | Expect(ParseDocument(source)).To(MatchDocument(expected))
42 | })
43 |
44 | It("when experimental is not enabled", func() {
45 | source := `Click on btn:[OK].`
46 | expected := &types.Document{
47 | Elements: []interface{}{
48 | &types.Paragraph{
49 | Elements: []interface{}{
50 | &types.StringElement{
51 | Content: "Click on btn:[OK].",
52 | },
53 | },
54 | },
55 | },
56 | }
57 | Expect(ParseDocument(source)).To(MatchDocument(expected))
58 | })
59 | })
60 | })
61 |
--------------------------------------------------------------------------------
/pkg/parser/line_break_test.go:
--------------------------------------------------------------------------------
1 | package parser_test
2 |
3 | import (
4 | "github.com/bytesparadise/libasciidoc/pkg/types"
5 | . "github.com/bytesparadise/libasciidoc/testsupport"
6 |
7 | . "github.com/onsi/ginkgo/v2"
8 | . "github.com/onsi/gomega"
9 | )
10 |
11 | var _ = Describe("line breaks", func() {
12 |
13 | Context("in final documents", func() {
14 |
15 | It("simple case", func() {
16 | source := `since 2021 +`
17 | expected := &types.Document{
18 | Elements: []interface{}{
19 | &types.Paragraph{
20 | Elements: []interface{}{
21 | &types.StringElement{
22 | Content: "since 2021",
23 | },
24 | &types.LineBreak{},
25 | },
26 | },
27 | },
28 | }
29 | Expect(ParseDocument(source)).To(MatchDocument(expected))
30 | })
31 |
32 | It("after punctuation", func() {
33 | source := `:author: Xavier
34 | Copyright (C) 2021 {author}. +`
35 | expected := &types.Document{
36 | Elements: []interface{}{
37 | &types.AttributeDeclaration{
38 | Name: types.AttrAuthor,
39 | Value: "Xavier",
40 | },
41 | &types.Paragraph{
42 | Elements: []interface{}{
43 | &types.StringElement{
44 | Content: "Copyright ",
45 | },
46 | &types.Symbol{
47 | Name: "(C)",
48 | },
49 | &types.StringElement{
50 | Content: " 2021 Xavier.",
51 | },
52 | &types.LineBreak{},
53 | },
54 | },
55 | },
56 | }
57 | Expect(ParseDocument(source)).To(MatchDocument(expected))
58 | })
59 | })
60 | })
61 |
--------------------------------------------------------------------------------
/pkg/parser/paragragh_debug_test.go:
--------------------------------------------------------------------------------
1 | // +build stats
2 |
3 | package parser_test
4 |
5 | import (
6 | "encoding/json"
7 | "fmt"
8 |
9 | "github.com/bytesparadise/libasciidoc/pkg/parser"
10 | "github.com/bytesparadise/libasciidoc/pkg/types"
11 | . "github.com/bytesparadise/libasciidoc/testsupport"
12 |
13 | . "github.com/onsi/ginkgo/v2"
14 | . "github.com/onsi/gomega"
15 | )
16 |
17 | var _ = Describe("paragraphs", func() {
18 |
19 | It("multiline paragraph with rich content", func() {
20 | source := `:fds-version: 6.7.4
21 |
22 | PyroSim is a graphical user interface for the https://github.com/firemodels/fds/releases/tag/FDS{fds-version}/[Fire Dynamics Simulator (FDS) version {fds-version}].
23 | FDS is closely integrated into PyroSim.
24 | FDS models can predict smoke, temperature, carbon monoxide, and other substances during fires.
25 | The results of these simulations are used to ensure the safety of buildings before construction, evaluate safety options of existing buildings, reconstruct fires for post-accident investigation, and assist in firefighter training.
26 | `
27 | expected := &types.Document{
28 | Elements: []interface{}{
29 | &types.AttributeDeclaration{
30 | Name: "fds-version",
31 | Value: string("6.7.4"),
32 | },
33 | &types.Paragraph{
34 | Elements: []interface{}{
35 | &types.StringElement{
36 | Content: "PyroSim is a graphical user interface for the ",
37 | },
38 | // https://github.com/firemodels/fds/releases/tag/FDS{fds-version}/[Fire Dynamics Simulator (FDS) version {fds-version}]
39 | &types.InlineLink{
40 | Attributes: types.Attributes{
41 | types.AttrInlineLinkText: "Fire Dynamics Simulator (FDS) version 6.7.4",
42 | },
43 | Location: &types.Location{
44 | Scheme: "https://",
45 | Path: "github.com/firemodels/fds/releases/tag/FDS6.7.4/",
46 | },
47 | },
48 | &types.StringElement{
49 | Content: ".\nFDS is closely integrated into PyroSim.\nFDS models can predict smoke, temperature, carbon monoxide, and other substances during fires.\nThe results of these simulations are used to ensure the safety of buildings before construction, evaluate safety options of existing buildings, reconstruct fires for post-accident investigation, and assist in firefighter training.",
50 | },
51 | },
52 | },
53 | },
54 | }
55 | stats := parser.Stats{}
56 | Expect(ParseDocument(source, parser.Debug(true), parser.Statistics(&stats, "no match"))).To(MatchDocument(expected))
57 | fmt.Printf("ExprCnt: %d\n", stats.ExprCnt)
58 | result, _ := json.MarshalIndent(stats.ChoiceAltCnt, " ", " ")
59 | fmt.Printf("ChoiceAltCnt: \n%s\n", result)
60 | })
61 | })
62 |
--------------------------------------------------------------------------------
/pkg/parser/paragragh_stats_test.go:
--------------------------------------------------------------------------------
1 | // +build stats
2 |
3 | package parser_test
4 |
5 | import (
6 | "encoding/json"
7 | "fmt"
8 |
9 | "github.com/bytesparadise/libasciidoc/pkg/parser"
10 | "github.com/bytesparadise/libasciidoc/pkg/types"
11 | . "github.com/bytesparadise/libasciidoc/testsupport"
12 |
13 | . "github.com/onsi/ginkgo/v2"
14 | . "github.com/onsi/gomega"
15 | )
16 |
17 | var _ = Describe("paragraphs", func() {
18 |
19 | It("multiline paragraph with rich content", func() {
20 | source := `:fds-version: 6.7.4
21 |
22 | PyroSim is a graphical user interface for the https://github.com/firemodels/fds/releases/tag/FDS{fds-version}/[Fire Dynamics Simulator (FDS) version {fds-version}].
23 | FDS is closely integrated into PyroSim.
24 | FDS models can predict smoke, temperature, carbon monoxide, and other substances during fires.
25 | The results of these simulations are used to ensure the safety of buildings before construction, evaluate safety options of existing buildings, reconstruct fires for post-accident investigation, and assist in firefighter training.
26 | `
27 | expected := &types.Document{
28 | Elements: []interface{}{
29 | &types.AttributeDeclaration{
30 | Name: "fds-version",
31 | Value: string("6.7.4"),
32 | },
33 | &types.Paragraph{
34 | Elements: []interface{}{
35 | &types.StringElement{
36 | Content: "PyroSim is a graphical user interface for the ",
37 | },
38 | // https://github.com/firemodels/fds/releases/tag/FDS{fds-version}/[Fire Dynamics Simulator (FDS) version {fds-version}]
39 | &types.InlineLink{
40 | Attributes: types.Attributes{
41 | types.AttrInlineLinkText: "Fire Dynamics Simulator (FDS) version 6.7.4",
42 | },
43 | Location: &types.Location{
44 | Scheme: "https://",
45 | Path: "github.com/firemodels/fds/releases/tag/FDS6.7.4/",
46 | },
47 | },
48 | &types.StringElement{
49 | Content: ".\nFDS is closely integrated into PyroSim.\nFDS models can predict smoke, temperature, carbon monoxide, and other substances during fires.\nThe results of these simulations are used to ensure the safety of buildings before construction, evaluate safety options of existing buildings, reconstruct fires for post-accident investigation, and assist in firefighter training.",
50 | },
51 | },
52 | },
53 | },
54 | }
55 | stats := parser.Stats{}
56 | Expect(ParseDocument(source, parser.Debug(true), parser.Statistics(&stats, "no match"))).To(MatchDocument(expected))
57 | fmt.Printf("ExprCnt: %d\n", stats.ExprCnt)
58 | result, _ := json.MarshalIndent(stats.ChoiceAltCnt, " ", " ")
59 | fmt.Printf("ChoiceAltCnt: \n%s\n", result)
60 | })
61 | })
62 |
--------------------------------------------------------------------------------
/pkg/parser/parser_suite_test.go:
--------------------------------------------------------------------------------
1 | package parser_test
2 |
3 | import (
4 | . "github.com/onsi/ginkgo/v2"
5 | . "github.com/onsi/gomega"
6 |
7 | "testing"
8 |
9 | _ "github.com/bytesparadise/libasciidoc/testsupport"
10 | )
11 |
12 | func TestParser(t *testing.T) {
13 | RegisterFailHandler(Fail)
14 | RunSpecs(t, "Parser Suite")
15 | }
16 |
--------------------------------------------------------------------------------
/pkg/parser/stats.go:
--------------------------------------------------------------------------------
1 | package parser
2 |
3 | import (
4 | "fmt"
5 | "sort"
6 | "strings"
7 | )
8 |
9 | func PrettyPrintStats(stats *Stats) string {
10 | rules := make([]string, 0, len(stats.ChoiceAltCnt))
11 | for r := range stats.ChoiceAltCnt {
12 | rules = append(rules, r)
13 | }
14 | sort.Strings(rules)
15 | buf := &strings.Builder{}
16 | for _, r := range rules {
17 | buf.WriteString(fmt.Sprintf("%s ", r))
18 | for choice, count := range stats.ChoiceAltCnt[r] {
19 | buf.WriteString(fmt.Sprintf("| %s->%dx ", choice, count))
20 | }
21 | buf.WriteString("\n")
22 | }
23 | buf.WriteString(fmt.Sprintf("---------------\nTotal: %d\n\n", stats.ExprCnt))
24 | return buf.String()
25 | }
26 |
--------------------------------------------------------------------------------
/pkg/renderer/renderer.go:
--------------------------------------------------------------------------------
1 | package renderer
2 |
3 | import (
4 | "fmt"
5 | "io"
6 |
7 | "github.com/bytesparadise/libasciidoc/pkg/configuration"
8 | "github.com/bytesparadise/libasciidoc/pkg/renderer/sgml/html5"
9 | "github.com/bytesparadise/libasciidoc/pkg/renderer/sgml/xhtml5"
10 | "github.com/bytesparadise/libasciidoc/pkg/types"
11 | )
12 |
13 | func Render(doc *types.Document, config *configuration.Configuration, output io.Writer) (types.Metadata, error) {
14 | switch config.BackEnd {
15 | case "html", "html5":
16 | return html5.Render(doc, config, output)
17 | case "xhtml", "xhtml5":
18 | return xhtml5.Render(doc, config, output)
19 | default:
20 | return types.Metadata{}, fmt.Errorf("backend '%s' not supported", config.BackEnd)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/pkg/renderer/renderer_suite_test.go:
--------------------------------------------------------------------------------
1 | package renderer_test
2 |
3 | import (
4 | "testing"
5 |
6 | . "github.com/onsi/ginkgo/v2"
7 | . "github.com/onsi/gomega"
8 |
9 | _ "github.com/bytesparadise/libasciidoc/testsupport"
10 | )
11 |
12 | func TestRenderer(t *testing.T) {
13 | RegisterFailHandler(Fail)
14 | RunSpecs(t, "Renderer Suite")
15 | }
16 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/attribution.go:
--------------------------------------------------------------------------------
1 | package sgml
2 |
3 | import "github.com/bytesparadise/libasciidoc/pkg/types"
4 |
5 | // Attribution a document block attribution
6 | type Attribution struct { // TODO: unexport this type?
7 | First string
8 | Second string
9 | }
10 |
11 | func newAttribution(b types.WithAttributes) Attribution {
12 | result := Attribution{}
13 | if author, found := b.GetAttributes().GetAsString(types.AttrQuoteAuthor); found {
14 | result.First = author
15 | if title, found := b.GetAttributes().GetAsString(types.AttrQuoteTitle); found {
16 | result.Second = title
17 | }
18 | } else if title, found := b.GetAttributes().GetAsString(types.AttrQuoteTitle); found {
19 | result.First = title
20 | }
21 | return result
22 | }
23 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/blank_line.go:
--------------------------------------------------------------------------------
1 | package sgml
2 |
3 | import (
4 | "strings"
5 | )
6 |
7 | func (r *sgmlRenderer) renderLineBreak() (string, error) {
8 | buf := &strings.Builder{}
9 | tmpl, err := r.lineBreak()
10 | if err != nil {
11 | return "", err
12 | }
13 | if err := tmpl.Execute(buf, nil); err != nil {
14 | return "", err
15 | }
16 | return buf.String(), nil
17 | }
18 |
19 | func (r *sgmlRenderer) renderThematicBreak() (string, error) {
20 | buf := &strings.Builder{}
21 | tmpl, err := r.thematicBreak()
22 | if err != nil {
23 | return "", err
24 | }
25 | if err := tmpl.Execute(buf, nil); err != nil {
26 | return "", err
27 | }
28 | return buf.String(), nil
29 | }
30 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/delimited_block.go:
--------------------------------------------------------------------------------
1 | package sgml
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/bytesparadise/libasciidoc/pkg/types"
7 | )
8 |
9 | func (r *sgmlRenderer) renderDelimitedBlock(ctx *context, b *types.DelimitedBlock) (string, error) {
10 | switch b.Kind {
11 | case types.Example:
12 | if b.Attributes.Has(types.AttrStyle) {
13 | return r.renderAdmonitionBlock(ctx, b)
14 | }
15 | return r.renderExampleBlock(ctx, b)
16 | case types.Fenced:
17 | return r.renderFencedBlock(ctx, b)
18 | case types.Literal:
19 | return r.renderLiteralBlock(ctx, b)
20 | case types.Listing:
21 | return r.renderListingBlock(ctx, b)
22 | case types.MarkdownQuote:
23 | return r.renderMarkdownQuoteBlock(ctx, b)
24 | case types.Passthrough:
25 | return r.renderPassthroughBlock(ctx, b)
26 | case types.Quote:
27 | return r.renderQuoteBlock(ctx, b)
28 | case types.Verse:
29 | return r.renderVerseBlock(ctx, b)
30 | case types.Sidebar:
31 | return r.renderSidebarBlock(ctx, b)
32 | case types.Open:
33 | return r.renderOpenBlock(ctx, b)
34 | default:
35 | return "", fmt.Errorf("unsupported kind of delimited block: '%s'", b.Kind)
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/delimited_block_discard_lines.go:
--------------------------------------------------------------------------------
1 | package sgml
2 |
3 | import (
4 | "github.com/bytesparadise/libasciidoc/pkg/types"
5 | )
6 |
7 | func discardBlankLines(lines []interface{}) []interface{} {
8 | // discard blank elements at the end
9 | // log.Debugf("discarding blank lines on %d elements...", len(lines))
10 | filteredLines := make([]interface{}, len(lines))
11 | copy(filteredLines, lines)
12 | // leading empty lines
13 | for {
14 | if len(filteredLines) == 0 {
15 | break
16 | }
17 | if _, ok := filteredLines[0].(types.BlankLine); ok {
18 | // remove last element of the slice since it's a blank line
19 | filteredLines = filteredLines[:len(filteredLines)-1]
20 | } else {
21 | break
22 | }
23 | }
24 | // trailing empty lines
25 | for {
26 | if len(filteredLines) == 0 {
27 | break
28 | }
29 | if _, ok := filteredLines[len(filteredLines)-1].(types.BlankLine); ok {
30 | // remove last element of the slice since it's a blank line
31 | filteredLines = filteredLines[:len(filteredLines)-1]
32 | } else {
33 | break
34 | }
35 | }
36 | return filteredLines
37 | }
38 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/delimited_block_fenced.go:
--------------------------------------------------------------------------------
1 | package sgml
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/bytesparadise/libasciidoc/pkg/types"
7 | "github.com/pkg/errors"
8 | )
9 |
10 | func (r *sgmlRenderer) renderFencedBlock(ctx *context, b *types.DelimitedBlock) (string, error) {
11 | previousWithinDelimitedBlock := ctx.withinDelimitedBlock
12 | defer func() {
13 | ctx.withinDelimitedBlock = previousWithinDelimitedBlock
14 | }()
15 | ctx.withinDelimitedBlock = true
16 | content, err := r.renderElements(ctx, b.Elements)
17 | if err != nil {
18 | return "", errors.Wrap(err, "unable to render fenced block content")
19 | }
20 | roles, err := r.renderElementRoles(ctx, b.Attributes)
21 | if err != nil {
22 | return "", errors.Wrap(err, "unable to render fenced block roles")
23 | }
24 | title, err := r.renderElementTitle(ctx, b.Attributes)
25 | if err != nil {
26 | return "", errors.Wrap(err, "unable to render fenced block roles")
27 | }
28 | return r.execute(r.fencedBlock, struct {
29 | Context *context
30 | ID string
31 | Title string
32 | Roles string
33 | Content string
34 | }{
35 | Context: ctx,
36 | ID: r.renderElementID(b.Attributes),
37 | Title: title,
38 | Roles: roles,
39 | Content: strings.Trim(content, "\n"),
40 | })
41 | }
42 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/delimited_block_listing.go:
--------------------------------------------------------------------------------
1 | package sgml
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/bytesparadise/libasciidoc/pkg/types"
7 | "github.com/pkg/errors"
8 | )
9 |
10 | func (r *sgmlRenderer) renderListingBlock(ctx *context, b *types.DelimitedBlock) (string, error) {
11 | if k, found := b.Attributes[types.AttrStyle]; found && k == types.Source {
12 | return r.renderSourceBlock(ctx, b)
13 | }
14 | previousWithinDelimitedBlock := ctx.withinDelimitedBlock
15 | defer func() {
16 | ctx.withinDelimitedBlock = previousWithinDelimitedBlock
17 | }()
18 | ctx.withinDelimitedBlock = true
19 | content, err := r.renderElements(ctx, b.Elements)
20 | if err != nil {
21 | return "", errors.Wrap(err, "unable to render listing block content")
22 | }
23 | roles, err := r.renderElementRoles(ctx, b.Attributes)
24 | if err != nil {
25 | return "", errors.Wrap(err, "unable to render listing block roles")
26 | }
27 | title, err := r.renderElementTitle(ctx, b.Attributes)
28 | if err != nil {
29 | return "", errors.Wrap(err, "unable to render listing block title")
30 | }
31 | return r.execute(r.listingBlock, struct {
32 | Context *context
33 | ID string
34 | Title string
35 | Roles string
36 | Content string
37 | }{
38 | Context: ctx,
39 | ID: r.renderElementID(b.Attributes),
40 | Title: title,
41 | Roles: roles,
42 | Content: strings.Trim(content, "\n"),
43 | })
44 | }
45 |
46 | func (r *sgmlRenderer) renderListingParagraph(ctx *context, p *types.Paragraph) (string, error) {
47 | content, err := r.renderElements(ctx, p.Elements)
48 | if err != nil {
49 | return "", errors.Wrap(err, "unable to render listing block content")
50 | }
51 | roles, err := r.renderElementRoles(ctx, p.Attributes)
52 | if err != nil {
53 | return "", errors.Wrap(err, "unable to render listing block roles")
54 | }
55 | title, err := r.renderElementTitle(ctx, p.Attributes)
56 | if err != nil {
57 | return "", errors.Wrap(err, "unable to render listing paragraph roles")
58 | }
59 | return r.execute(r.listingBlock, struct {
60 | Context *context
61 | ID string
62 | Title string
63 | Roles string
64 | Content string
65 | }{
66 | Context: ctx,
67 | ID: r.renderElementID(p.Attributes),
68 | Title: title,
69 | Roles: roles,
70 | Content: content,
71 | })
72 | }
73 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/delimited_block_markdown_quote.go:
--------------------------------------------------------------------------------
1 | package sgml
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/bytesparadise/libasciidoc/pkg/types"
7 | "github.com/pkg/errors"
8 | )
9 |
10 | func (r *sgmlRenderer) renderMarkdownQuoteBlock(ctx *context, b *types.DelimitedBlock) (string, error) {
11 | content, err := r.renderElements(ctx, b.Elements)
12 | if err != nil {
13 | return "", errors.Wrap(err, "unable to render markdown quote block content")
14 | }
15 | roles, err := r.renderElementRoles(ctx, b.Attributes)
16 | if err != nil {
17 | return "", errors.Wrap(err, "unable to render markdown quote block roles")
18 | }
19 | attribution := newAttribution(b)
20 | title, err := r.renderElementTitle(ctx, b.Attributes)
21 | if err != nil {
22 | return "", errors.Wrap(err, "unable to render markdown quote block title")
23 | }
24 | return r.execute(r.markdownQuoteBlock, struct {
25 | Context *context
26 | ID string
27 | Title string
28 | Roles string
29 | Attribution Attribution
30 | Content string
31 | }{
32 | Context: ctx,
33 | ID: r.renderElementID(b.Attributes),
34 | Title: title,
35 | Roles: roles,
36 | Attribution: attribution,
37 | Content: strings.Trim(content, "\n"),
38 | })
39 | }
40 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/delimited_block_open.go:
--------------------------------------------------------------------------------
1 | package sgml
2 |
3 | import (
4 | "github.com/bytesparadise/libasciidoc/pkg/types"
5 | "github.com/pkg/errors"
6 | )
7 |
8 | func (r *sgmlRenderer) renderOpenBlock(ctx *context, b *types.DelimitedBlock) (string, error) {
9 | blocks := discardBlankLines(b.Elements)
10 | content, err := r.renderElements(ctx, blocks)
11 | if err != nil {
12 | return "", errors.Wrap(err, "unable to render open block content")
13 | }
14 | roles, err := r.renderElementRoles(ctx, b.Attributes)
15 | if err != nil {
16 | return "", errors.Wrap(err, "unable to render open block roles")
17 | }
18 | title, err := r.renderElementTitle(ctx, b.Attributes)
19 | if err != nil {
20 | return "", errors.Wrap(err, "unable to render open block title")
21 | }
22 | return r.execute(r.openBlock, struct {
23 | Context *context
24 | ID string
25 | Title string
26 | Roles string
27 | Content string
28 | }{
29 | Context: ctx,
30 | ID: r.renderElementID(b.Attributes),
31 | Title: title,
32 | Roles: roles,
33 | Content: content,
34 | })
35 | }
36 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/delimited_block_passthrough.go:
--------------------------------------------------------------------------------
1 | package sgml
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/bytesparadise/libasciidoc/pkg/types"
7 | "github.com/pkg/errors"
8 | )
9 |
10 | func (r *sgmlRenderer) renderPassthroughBlock(ctx *context, b *types.DelimitedBlock) (string, error) {
11 | previousWithinDelimitedBlock := ctx.withinDelimitedBlock
12 | defer func() {
13 | ctx.withinDelimitedBlock = previousWithinDelimitedBlock
14 | }()
15 | ctx.withinDelimitedBlock = true
16 | content, err := r.renderElements(ctx, b.Elements)
17 | if err != nil {
18 | return "", errors.Wrap(err, "unable to render passthrough block content")
19 | }
20 | roles, err := r.renderElementRoles(ctx, b.Attributes)
21 | if err != nil {
22 | return "", errors.Wrap(err, "unable to render passthrough block roles")
23 | }
24 | return r.execute(r.passthroughBlock, struct {
25 | Context *context
26 | ID string
27 | Roles string
28 | Content string
29 | }{
30 | Context: ctx,
31 | ID: r.renderElementID(b.Attributes),
32 | Roles: roles,
33 | Content: strings.Trim(content, "\n"),
34 | })
35 | }
36 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/delimited_block_quote.go:
--------------------------------------------------------------------------------
1 | package sgml
2 |
3 | import (
4 | "github.com/bytesparadise/libasciidoc/pkg/types"
5 | "github.com/pkg/errors"
6 | log "github.com/sirupsen/logrus"
7 | )
8 |
9 | func (r *sgmlRenderer) renderQuoteBlock(ctx *context, b *types.DelimitedBlock) (string, error) {
10 | content, err := r.renderElements(ctx, b.Elements)
11 | if err != nil {
12 | return "", errors.Wrap(err, "unable to render quote block content")
13 | }
14 | roles, err := r.renderElementRoles(ctx, b.Attributes)
15 | if err != nil {
16 | return "", errors.Wrap(err, "unable to render quote block roles")
17 | }
18 | attribution := newAttribution(b)
19 | title, err := r.renderElementTitle(ctx, b.Attributes)
20 | if err != nil {
21 | return "", errors.Wrap(err, "unable to render quote block title")
22 | }
23 | return r.execute(r.quoteBlock, struct {
24 | Context *context
25 | ID string
26 | Title string
27 | Roles string
28 | Attribution Attribution
29 | Content string
30 | }{
31 | Context: ctx,
32 | ID: r.renderElementID(b.Attributes),
33 | Title: title,
34 | Roles: roles,
35 | Attribution: attribution,
36 | Content: content,
37 | })
38 | }
39 |
40 | func (r *sgmlRenderer) renderQuoteParagraph(ctx *context, p *types.Paragraph) (string, error) {
41 | log.Debug("rendering quote paragraph...")
42 | content, err := r.renderParagraphElements(ctx, p)
43 | if err != nil {
44 | return "", errors.Wrap(err, "unable to render quote paragraph lines")
45 | }
46 | attribution := newAttribution(p)
47 | title, err := r.renderElementTitle(ctx, p.Attributes)
48 | if err != nil {
49 | return "", errors.Wrap(err, "unable to render callout list roles")
50 | }
51 | return r.execute(r.quoteParagraph, struct {
52 | Context *context
53 | ID string
54 | Title string
55 | Attribution Attribution
56 | Content string
57 | }{
58 | Context: ctx,
59 | ID: r.renderElementID(p.Attributes),
60 | Title: title,
61 | Attribution: attribution,
62 | Content: string(content),
63 | })
64 | }
65 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/delimited_block_sidebar.go:
--------------------------------------------------------------------------------
1 | package sgml
2 |
3 | import (
4 | "github.com/bytesparadise/libasciidoc/pkg/types"
5 | "github.com/pkg/errors"
6 | )
7 |
8 | func (r *sgmlRenderer) renderSidebarBlock(ctx *context, b *types.DelimitedBlock) (string, error) {
9 | blocks := discardBlankLines(b.Elements)
10 | content, err := r.renderElements(ctx, blocks)
11 | if err != nil {
12 | return "", errors.Wrap(err, "unable to render sidebar block content")
13 | }
14 | roles, err := r.renderElementRoles(ctx, b.Attributes)
15 | if err != nil {
16 | return "", errors.Wrap(err, "unable to render sidebar block roles")
17 | }
18 | title, err := r.renderElementTitle(ctx, b.Attributes)
19 | if err != nil {
20 | return "", errors.Wrap(err, "unable to render sidebar block title")
21 | }
22 | return r.execute(r.sidebarBlock, struct {
23 | Context *context
24 | ID string
25 | Title string
26 | Roles string
27 | Content string
28 | }{
29 | Context: ctx,
30 | ID: r.renderElementID(b.Attributes),
31 | Title: title,
32 | Roles: roles,
33 | Content: content,
34 | })
35 | }
36 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/delimited_block_verse.go:
--------------------------------------------------------------------------------
1 | package sgml
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/bytesparadise/libasciidoc/pkg/types"
7 | "github.com/pkg/errors"
8 | log "github.com/sirupsen/logrus"
9 | )
10 |
11 | func (r *sgmlRenderer) renderVerseBlock(ctx *context, b *types.DelimitedBlock) (string, error) {
12 | roles, err := r.renderElementRoles(ctx, b.Attributes)
13 | if err != nil {
14 | return "", errors.Wrap(err, "unable to render verser block roles")
15 | }
16 | previousWithinDelimitedBlock := ctx.withinDelimitedBlock
17 | defer func() {
18 | ctx.withinDelimitedBlock = previousWithinDelimitedBlock
19 | }()
20 | ctx.withinDelimitedBlock = true
21 | content, err := r.renderElements(ctx, b.Elements)
22 | if err != nil {
23 | return "", errors.Wrap(err, "unable to render verse block content")
24 | }
25 | attribution := newAttribution(b)
26 | title, err := r.renderElementTitle(ctx, b.Attributes)
27 | if err != nil {
28 | return "", errors.Wrap(err, "unable to render verse block title")
29 | }
30 | return r.execute(r.verseBlock, struct {
31 | Context *context
32 | ID string
33 | Title string
34 | Roles string
35 | Attribution Attribution
36 | Content string
37 | }{
38 | Context: ctx,
39 | ID: r.renderElementID(b.Attributes),
40 | Title: title,
41 | Roles: roles,
42 | Attribution: attribution,
43 | Content: strings.Trim(string(content), "\n"),
44 | })
45 | }
46 |
47 | func (r *sgmlRenderer) renderVerseParagraph(ctx *context, p *types.Paragraph) (string, error) {
48 | log.Debug("rendering verse paragraph...")
49 | content, err := RenderParagraphElements(p)
50 | if err != nil {
51 | return "", errors.Wrap(err, "unable to render verse paragraph lines")
52 | }
53 | attribution := newAttribution(p)
54 | title, err := r.renderElementTitle(ctx, p.Attributes)
55 | if err != nil {
56 | return "", errors.Wrap(err, "unable to render callout list roles")
57 | }
58 | return r.execute(r.verseParagraph, struct {
59 | Context *context
60 | ID string
61 | Title string
62 | Attribution Attribution
63 | Content string
64 | }{
65 | Context: ctx,
66 | ID: r.renderElementID(p.Attributes),
67 | Title: title,
68 | Attribution: attribution,
69 | Content: string(content),
70 | })
71 | }
72 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/element_id.go:
--------------------------------------------------------------------------------
1 | package sgml
2 |
3 | import (
4 | texttemplate "text/template"
5 |
6 | "github.com/bytesparadise/libasciidoc/pkg/types"
7 | )
8 |
9 | func (r *sgmlRenderer) renderElementID(attrs types.Attributes) string {
10 | if id, ok := attrs[types.AttrID].(string); ok {
11 | return string(texttemplate.HTMLEscapeString(id))
12 | }
13 | return ""
14 | }
15 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/element_role.go:
--------------------------------------------------------------------------------
1 | package sgml
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 |
7 | "github.com/bytesparadise/libasciidoc/pkg/types"
8 | )
9 |
10 | func (r *sgmlRenderer) renderElementRoles(ctx *context, attrs types.Attributes) (string, error) {
11 | if roles, ok := attrs[types.AttrRoles].(types.Roles); ok {
12 | result := make([]string, len(roles))
13 | for i, e := range roles {
14 | s, err := r.renderElementRole(ctx, e)
15 | if err != nil {
16 | return "", err
17 | }
18 | result[i] = s
19 | }
20 | return strings.Join(result, " "), nil
21 | }
22 | return "", nil
23 | }
24 |
25 | // Image roles add float and alignment attributes -- we turn these into roles.
26 | func (r *sgmlRenderer) renderImageRoles(ctx *context, attrs types.Attributes) (string, error) {
27 | var result []string
28 | if val, found := attrs.GetAsString(types.AttrFloat); found {
29 | result = append(result, val)
30 | }
31 | if val, found := attrs.GetAsString(types.AttrImageAlign); found {
32 | result = append(result, "text-"+val)
33 | }
34 | if roles, ok := attrs[types.AttrRoles].(types.Roles); ok {
35 | for _, e := range roles {
36 | s, err := r.renderElementRole(ctx, e)
37 | if err != nil {
38 | return "", err
39 | }
40 | result = append(result, s)
41 | }
42 | }
43 | // log.Debugf("rendered image roles: '%s'", result)
44 | return strings.Join(result, " "), nil
45 | }
46 |
47 | func (r *sgmlRenderer) renderElementRole(ctx *context, role interface{}) (string, error) {
48 | result := strings.Builder{}
49 | switch role := role.(type) {
50 | case string:
51 | result.WriteString(role)
52 | case []interface{}:
53 | // when the role is made of strings and special characters
54 | for _, e := range role {
55 | s, err := r.renderElement(ctx, e)
56 | if err != nil {
57 | return "", err
58 | }
59 | result.WriteString(s)
60 | }
61 | default:
62 | return "", fmt.Errorf("unexpected type of element while rendering element role: '%T'", role)
63 | }
64 | // log.Debugf("rendered role: '%s'", result.String())
65 | return result.String(), nil
66 | }
67 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/element_style.go:
--------------------------------------------------------------------------------
1 | package sgml
2 |
3 | import (
4 | texttemplate "text/template"
5 |
6 | "github.com/bytesparadise/libasciidoc/pkg/types"
7 | )
8 |
9 | func (r *sgmlRenderer) renderElementStyle(attrs types.Attributes) string {
10 | if id, ok := attrs[types.AttrStyle].(string); ok {
11 | return string(texttemplate.HTMLEscapeString(id))
12 | }
13 | return ""
14 | }
15 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/article.go:
--------------------------------------------------------------------------------
1 | package html5
2 |
3 | const (
4 | articleTmpl = `
5 |
6 |
7 |
8 |
9 |
10 | {{ if .Generator }}
11 | {{ end }}{{ if .Description }}
12 | {{ end }}{{ if .Authors }}
13 | {{ end }}{{ range $css := .CSS }}
14 | {{ end }}
{{ .Title }}
15 |
16 |
17 | {{ if .IncludeHTMLBodyHeader }}{{ .Header }}{{ end }}
18 | {{ .Content }}
19 | {{ if .IncludeHTMLBodyFooter }}
25 | {{ end }}
26 |
27 | `
28 |
29 | articleHeaderTmpl = `
32 | `
33 | )
34 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/blank_line.go:
--------------------------------------------------------------------------------
1 | package html5
2 |
3 | const (
4 | lineBreakTmpl = "
"
5 | )
6 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/blank_line_test.go:
--------------------------------------------------------------------------------
1 | package html5_test
2 |
3 | import (
4 | . "github.com/bytesparadise/libasciidoc/testsupport"
5 |
6 | . "github.com/onsi/ginkgo/v2"
7 | . "github.com/onsi/gomega"
8 | )
9 |
10 | var _ = Describe("blank lines", func() {
11 |
12 | It("blank line between 2 paragraphs", func() {
13 | source := `first paragraph
14 |
15 | second paragraph`
16 | expected := `
17 |
first paragraph
18 |
19 |
20 |
second paragraph
21 |
22 | `
23 | Expect(RenderHTML(source)).To(MatchHTML(expected))
24 | })
25 |
26 | It("blank line with spaces and tabs between 2 paragraphs", func() {
27 | source := `first paragraph
28 |
29 | second paragraph`
30 | expected := `
31 |
first paragraph
32 |
33 |
34 |
second paragraph
35 |
36 | `
37 | Expect(RenderHTML(source)).To(MatchHTML(expected))
38 | })
39 |
40 | It("blank lines (tabs) at end of document", func() {
41 | source := `first paragraph
42 |
43 |
44 | `
45 | expected := `
46 |
first paragraph
47 |
48 | `
49 | Expect(RenderHTML(source)).To(MatchHTML(expected))
50 | })
51 |
52 | It("blank lines (spaces) at end of document", func() {
53 | source := `first paragraph
54 |
55 |
56 | `
57 | expected := `
58 |
first paragraph
59 |
60 | `
61 | Expect(RenderHTML(source)).To(MatchHTML(expected))
62 | })
63 | })
64 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/callout_list.go:
--------------------------------------------------------------------------------
1 | package html5
2 |
3 | // initializes the sgml
4 | const (
5 | calloutListTmpl = `
\n" +
8 | "{{ if .Title }}
{{ .Title }}
\n{{ end }}" +
9 | "
\n" +
10 | "{{ .Content }}" +
11 | " \n
\n"
12 |
13 | // NB: The items are numbered sequentially.
14 | calloutListElementTmpl = "
\n{{ .Content }} \n"
15 |
16 | // This should probably have been a
, but for compatibility we use
17 | calloutRefTmpl = "({{ .Ref }}) "
18 | )
19 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/comment_test.go:
--------------------------------------------------------------------------------
1 | package html5_test
2 |
3 | import (
4 | . "github.com/bytesparadise/libasciidoc/testsupport"
5 |
6 | . "github.com/onsi/ginkgo/v2"
7 | . "github.com/onsi/gomega"
8 | )
9 |
10 | var _ = Describe("comments", func() {
11 |
12 | Context("single line comments", func() {
13 |
14 | It("single line comment alone", func() {
15 | source := `// A single-line comment.`
16 | expected := ""
17 | Expect(RenderHTML(source)).To(MatchHTML(expected))
18 | })
19 |
20 | It("single line comment at end of line", func() {
21 | source := `foo // A single-line comment.`
22 | expected := `
23 |
foo // A single-line comment.
24 |
25 | `
26 | Expect(RenderHTML(source)).To(MatchHTML(expected))
27 | })
28 |
29 | It("single line comment within a paragraph", func() {
30 | source := `a first line
31 | // A single-line comment.
32 | another line`
33 | expected := `
34 |
a first line
35 | another line
36 |
37 | `
38 | Expect(RenderHTML(source)).To(MatchHTML(expected))
39 | })
40 | })
41 |
42 | Context("comment blocks", func() {
43 |
44 | It("comment block alone", func() {
45 | source := `////
46 | a *comment* block
47 | with multiple lines
48 | ////`
49 | expected := ""
50 | Expect(RenderHTML(source)).To(MatchHTML(expected))
51 | })
52 |
53 | It("comment block with paragraphs around", func() {
54 | source := `a first paragraph
55 |
56 | ////
57 | a *comment* block
58 | with multiple lines
59 | ////
60 |
61 | a second paragraph`
62 | expected := `
63 |
a first paragraph
64 |
65 |
66 |
a second paragraph
67 |
68 | `
69 | Expect(RenderHTML(source)).To(MatchHTML(expected))
70 | })
71 | })
72 |
73 | })
74 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/cross_reference.go:
--------------------------------------------------------------------------------
1 | package html5
2 |
3 | const (
4 | internalCrossReferenceTmpl = `{{ .Label }} `
5 | externalCrossReferenceTmpl = `{{ .Label }} `
6 | )
7 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/delimited_block_admonition_tmpl.go:
--------------------------------------------------------------------------------
1 | package html5
2 |
3 | const (
4 | admonitionBlockTmpl = `\n" +
6 | "
\n" +
7 | "\n" +
8 | "\n{{ .Icon }}\n \n" +
9 | "\n" +
10 | "{{ if .Title }}{{ .Title }}
\n{{ end }}" +
11 | "{{ .Content }}" +
12 | " \n \n
\n
\n"
13 | )
14 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/delimited_block_example_tmpl.go:
--------------------------------------------------------------------------------
1 | package html5
2 |
3 | const (
4 | exampleBlockTmpl = `\n" +
6 | "{{ if .Title }}
{{ .Caption }}{{ .Title }}
\n{{ end }}" +
7 | "
\n" +
8 | "{{ .Content }}" +
9 | "
\n" +
10 | "
\n"
11 | )
12 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/delimited_block_fenced_test.go:
--------------------------------------------------------------------------------
1 | package html5_test
2 |
3 | import (
4 | . "github.com/bytesparadise/libasciidoc/testsupport"
5 |
6 | . "github.com/onsi/ginkgo/v2"
7 | . "github.com/onsi/gomega"
8 | )
9 |
10 | var _ = Describe("delimited blocks", func() {
11 |
12 | Context("fenced blocks", func() {
13 |
14 | It("fenced block with surrounding empty lines", func() {
15 | source := "```\n\nsome source code \n\nhere \n\n\n\n```"
16 | expected := `
17 |
18 |
some source code
19 |
20 | here
21 |
22 |
23 | `
24 | Expect(RenderHTML(source)).To(MatchHTML(expected))
25 | })
26 |
27 | It("fenced block with empty lines", func() {
28 | source := "```\n\n\n\n```"
29 | expected := `
34 | `
35 | Expect(RenderHTML(source)).To(MatchHTML(expected))
36 | })
37 |
38 | It("fenced block with id and title and empty end lines", func() {
39 | source := "[#id-for-fences]\n.fenced block title\n```\nsome source code\n\nhere\n\n\n\n```"
40 | expected := `
41 |
fenced block title
42 |
43 |
some source code
44 |
45 | here
46 |
47 |
48 | `
49 | Expect(RenderHTML(source)).To(MatchHTML(expected))
50 | })
51 |
52 | It("fenced block with external link inside amd empty end line", func() {
53 | source := "```" + "\n" +
54 | "a https://website.com" + "\n" +
55 | "and more text on the" + "\n" +
56 | "next lines" + "\n\n" +
57 | "```"
58 | expected := `
59 |
60 |
a https://website.com
61 | and more text on the
62 | next lines
63 |
64 |
65 | `
66 | Expect(RenderHTML(source)).To(MatchHTML(expected))
67 | })
68 | })
69 | })
70 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/delimited_block_fenced_tmpl.go:
--------------------------------------------------------------------------------
1 | package html5
2 |
3 | const (
4 | fencedBlockTmpl = `\n" +
6 | "{{ if .Title }}
{{ .Title }}
\n{{ end }}" +
7 | "
\n" +
8 | "
{{ .Content }}
\n" +
9 | "
\n" +
10 | "
\n"
11 | )
12 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/delimited_block_listing_tmpl.go:
--------------------------------------------------------------------------------
1 | package html5
2 |
3 | const (
4 | listingBlockTmpl = `\n" +
6 | "{{ if .Title }}
{{ .Title }}
\n{{ end }}" +
7 | "
\n" +
8 | "
{{ .Content }} \n" +
9 | "
\n" +
10 | "
\n"
11 | )
12 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/delimited_block_literal_tmpl.go:
--------------------------------------------------------------------------------
1 | package html5
2 |
3 | const (
4 | literalBlockTmpl = `\n" +
6 | "{{ if .Title }}
{{ .Title }}
\n{{ end }}" +
7 | "
\n" +
8 | "
{{ .Content }} \n" +
9 | "
\n" +
10 | "
\n"
11 | )
12 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/delimited_block_markdown_quote_test.go:
--------------------------------------------------------------------------------
1 | package html5_test
2 |
3 | import (
4 | . "github.com/bytesparadise/libasciidoc/testsupport"
5 |
6 | . "github.com/onsi/ginkgo/v2"
7 | . "github.com/onsi/gomega"
8 | )
9 |
10 | var _ = Describe("markdown-style quote blocks", func() {
11 |
12 | Context("as delimited blocks", func() {
13 |
14 | It("with single marker without author", func() {
15 | source := `> some text
16 | on *multiple lines*`
17 |
18 | expected := `
19 |
20 |
21 |
some text
22 | on multiple lines
23 |
24 |
25 |
26 | `
27 | Expect(RenderHTML(source)).To(MatchHTML(expected))
28 | })
29 |
30 | It("with marker on each line without author", func() {
31 | source := `> some text
32 | > on *multiple lines*`
33 |
34 | expected := `
35 |
36 |
37 |
some text
38 | on multiple lines
39 |
40 |
41 |
42 | `
43 | Expect(RenderHTML(source)).To(MatchHTML(expected))
44 | })
45 |
46 | It("with marker on each line with author", func() {
47 | source := `> some text
48 | > on *multiple lines*
49 | > -- John Doe`
50 | expected := `
51 |
52 |
53 |
some text
54 | on multiple lines
55 |
56 |
57 |
58 | — John Doe
59 |
60 |
61 | `
62 | Expect(RenderHTML(source)).To(MatchHTML(expected))
63 | })
64 |
65 | It("with marker on each line with author and title", func() {
66 | source := `.title
67 | > some text
68 | > on *multiple lines*
69 | > -- John Doe`
70 | expected := `
71 |
title
72 |
73 |
74 |
some text
75 | on multiple lines
76 |
77 |
78 |
79 | — John Doe
80 |
81 |
82 | `
83 | Expect(RenderHTML(source)).To(MatchHTML(expected))
84 | })
85 |
86 | It("with author only", func() {
87 | source := `> -- John Doe`
88 | expected := `
89 |
90 |
91 |
92 | — John Doe
93 |
94 |
95 | `
96 | Expect(RenderHTML(source)).To(MatchHTML(expected))
97 | })
98 | })
99 | })
100 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/delimited_block_markdown_quote_tmpl.go:
--------------------------------------------------------------------------------
1 | package html5
2 |
3 | const (
4 | markdownQuoteBlockTmpl = `\n" +
6 | "{{ if .Title }}
{{ .Title }}
\n{{ end }}" +
7 | "
\n" +
8 | "{{ if .Content }}\n" +
9 | "
{{ .Content }}
\n" +
10 | "
\n{{ end }}" +
11 | " \n" +
12 | "{{ if .Attribution.First }}
\n" +
13 | "— {{ .Attribution.First }}" +
14 | "{{ if .Attribution.Second }} \n{{ .Attribution.Second }} {{ end }}\n" +
15 | "
\n{{ end }}" +
16 | "
\n"
17 | )
18 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/delimited_block_open_test.go:
--------------------------------------------------------------------------------
1 | package html5_test
2 |
3 | import (
4 | . "github.com/bytesparadise/libasciidoc/testsupport"
5 |
6 | . "github.com/onsi/ginkgo/v2"
7 | . "github.com/onsi/gomega"
8 | )
9 |
10 | var _ = Describe("open blocks", func() {
11 |
12 | Context("without masquerade", func() {
13 |
14 | It("with basic content and attributes", func() {
15 | source := `[#block-id]
16 | .Block Title
17 | --
18 | basic content
19 | --`
20 | expected := `
21 |
Block Title
22 |
27 |
28 | `
29 | Expect(RenderHTML(source)).To(MatchHTML(expected))
30 | })
31 |
32 | It("with table", func() {
33 | source := `[#block-id]
34 | .Block Title
35 | --
36 | [cols="2*^"]
37 | |===
38 | a|
39 | [#id]
40 | .A title
41 | image::image.png[]
42 | a|
43 | [#another-id]
44 | .Another title
45 | image::another-image.png[]
46 | |===
47 | --`
48 | expected := `
49 |
Block Title
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
Figure 1. A title
63 |
64 |
65 |
66 |
67 |
68 |
Figure 2. Another title
69 |
70 |
71 |
72 |
73 |
74 |
75 | `
76 | Expect(RenderHTML(source)).To(MatchHTML(expected))
77 | })
78 | })
79 | })
80 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/delimited_block_open_tmpl.go:
--------------------------------------------------------------------------------
1 | package html5
2 |
3 | const (
4 | openBlockTmpl = `\n" +
6 | "{{ if .Title }}
{{ .Title }}
\n{{ end }}" +
7 | "
\n" +
8 | "{{ .Content }}" +
9 | "
\n" +
10 | "
\n"
11 | )
12 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/delimited_block_passthrough_test.go:
--------------------------------------------------------------------------------
1 | package html5_test
2 |
3 | import (
4 | . "github.com/bytesparadise/libasciidoc/testsupport"
5 |
6 | . "github.com/onsi/ginkgo/v2"
7 | . "github.com/onsi/gomega"
8 | )
9 |
10 | var _ = Describe("passthrough blocks", func() {
11 |
12 | Context("as delimited blocks", func() {
13 |
14 | It("with title", func() {
15 | source := `.a title
16 | ++++
17 | _foo_
18 |
19 | *bar*
20 | ++++`
21 | expected := `_foo_
22 |
23 | *bar*
24 | `
25 | Expect(RenderHTML(source)).To(MatchHTML(expected))
26 | })
27 |
28 | It("with empty lines around", func() {
29 | source := `++++
30 |
31 | _foo_
32 |
33 | *bar*
34 |
35 | ++++`
36 | expected := `_foo_
37 |
38 | *bar*
39 | `
40 | Expect(RenderHTML(source)).To(MatchHTML(expected))
41 | })
42 |
43 | It("with special characters", func() {
44 | source := `++++
45 |
46 |
47 |
48 | ++++`
49 | expected := `
50 |
51 |
52 | `
53 | Expect(RenderHTML(source)).To(MatchHTML(expected))
54 | })
55 | })
56 |
57 | Context("as paragraph blocks", func() {
58 |
59 | It("2-line paragraph followed by another paragraph", func() {
60 | source := `[pass]
61 |
63 |
64 | another paragraph`
65 | expected := `
67 |
68 |
another paragraph
69 |
70 | `
71 | Expect(RenderHTML(source)).To(MatchHTML(expected))
72 | })
73 | })
74 | })
75 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/delimited_block_passthrough_tmpl.go:
--------------------------------------------------------------------------------
1 | package html5
2 |
3 | const (
4 |
5 | // the name here is weird because "pass" as a prefix triggers a false security warning
6 | passthroughBlock = "{{ .Content }}\n" //nolint:gosec // avoids a Gosec false positive because the const name starts with 'pass'
7 | )
8 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/delimited_block_quote_tmpl.go:
--------------------------------------------------------------------------------
1 | package html5
2 |
3 | const (
4 | quoteBlockTmpl = `\n" +
6 | "{{ if .Title }}
{{ .Title }}
\n{{ end }}" +
7 | "
\n" +
8 | "{{ .Content }}" +
9 | " \n" +
10 | "{{ if .Attribution.First }}
\n" +
11 | "— {{ .Attribution.First }}" +
12 | "{{ if .Attribution.Second }} \n{{ .Attribution.Second }} {{ end }}\n" +
13 | "
\n{{ end }}" +
14 | "
\n"
15 | )
16 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/delimited_block_sidebar_test.go:
--------------------------------------------------------------------------------
1 | package html5_test
2 |
3 | import (
4 | . "github.com/bytesparadise/libasciidoc/testsupport"
5 |
6 | . "github.com/onsi/ginkgo/v2"
7 | . "github.com/onsi/gomega"
8 | )
9 |
10 | var _ = Describe("sidebar blocks", func() {
11 |
12 | Context("as delimited blocks", func() {
13 |
14 | It("sidebar block with paragraph", func() {
15 | source := `****
16 | some *verse* content
17 |
18 | ****`
19 | expected := `
26 | `
27 | Expect(RenderHTML(source)).To(MatchHTML(expected))
28 | })
29 |
30 | It("sidebar block with id, title, paragraph and sourcecode block", func() {
31 | source := `[#id-for-sidebar]
32 | .title for sidebar
33 | ****
34 | some *verse* content
35 |
36 | ----
37 | foo
38 | bar
39 | ----
40 | ****`
41 | expected := `
55 | `
56 | Expect(RenderHTML(source)).To(MatchHTML(expected))
57 | })
58 | })
59 | })
60 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/delimited_block_sidebar_tmpl.go:
--------------------------------------------------------------------------------
1 | package html5
2 |
3 | const (
4 | sidebarBlockTmpl = "\n" +
6 | "
\n" +
7 | "{{ if .Title }}
{{ .Title }}
\n{{ end }}" +
8 | "{{ .Content }}" +
9 | "
\n" +
10 | "
\n"
11 | )
12 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/delimited_block_source_tmpl.go:
--------------------------------------------------------------------------------
1 | package html5
2 |
3 | const (
4 | sourceBlockTmpl = `\n" +
6 | "{{ if .Title }}
{{ .Title }}
\n{{ end }}" +
7 | "
\n" +
8 | "
` +
13 | `` +
15 | "{{ .Content }}
\n" +
16 | "
\n" +
17 | "
\n"
18 | )
19 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/delimited_block_verse_tmpl.go:
--------------------------------------------------------------------------------
1 | package html5
2 |
3 | const (
4 | verseBlockTmpl = `\n" +
6 | "{{ if .Title }}
{{ .Title }}
\n{{ end }}" +
7 | "
{{ .Content }} \n" +
8 | "{{ if .Attribution.First }}
\n— {{ .Attribution.First }}" +
9 | "{{ if .Attribution.Second }} \n{{ .Attribution.Second }} {{ end }}\n" +
10 | "
\n{{ end }}" +
11 | "
\n"
12 | )
13 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/document_details.go:
--------------------------------------------------------------------------------
1 | package html5
2 |
3 | const (
4 | documentDetailsTmpl = ` {{ if .Authors }}
5 | {{ .Authors }}{{ end }}{{ if .RevNumber }}
6 | {{ if .RevLabel }}{{ .RevLabel }} {{ end }}{{ .RevNumber }}{{ if .RevDate }},{{ end }} {{ end }}{{ if .RevDate }}
7 | {{ .RevDate }} {{ end }}{{ if .RevRemark }}
8 | {{ end }}
9 |
10 | `
11 |
12 | documentAuthorDetailsTmpl = `{{ if .Name }}{{ .Name }} {{ end }}{{ if .Email }}
13 | {{ .Email }} {{ end }}`
14 | )
15 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/footnote_reference.go:
--------------------------------------------------------------------------------
1 | package html5
2 |
3 | const (
4 | footnoteTmpl = ``
5 | footnoteRefTmpl = ``
6 | invalidFootnoteTmpl = ``
7 | footnotesTmpl = "\n"
8 |
9 | // arguably this should instead be an ordered list.
10 | footnoteElementTmpl = "\n"
13 | )
14 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/front_matter_test.go:
--------------------------------------------------------------------------------
1 | package html5_test
2 |
3 | import (
4 | . "github.com/bytesparadise/libasciidoc/testsupport"
5 |
6 | . "github.com/onsi/ginkgo/v2"
7 | . "github.com/onsi/gomega"
8 | )
9 |
10 | var _ = Describe("documents with front-matter", func() {
11 |
12 | It("should render with front-matter", func() {
13 | source := `---
14 | description: User Manual
15 | ---
16 |
17 | {description}
18 | `
19 | expected := `
22 | `
23 | Expect(RenderHTML(source)).To(MatchHTML(expected))
24 | })
25 |
26 | })
27 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/html5.go:
--------------------------------------------------------------------------------
1 | package html5
2 |
3 | import (
4 | "io"
5 |
6 | "github.com/bytesparadise/libasciidoc/pkg/configuration"
7 | "github.com/bytesparadise/libasciidoc/pkg/renderer/sgml"
8 | "github.com/bytesparadise/libasciidoc/pkg/types"
9 | )
10 |
11 | // Render renders the document to the output, using the SGML renderer configured with the HTML5 templates
12 | func Render(doc *types.Document, config *configuration.Configuration, output io.Writer) (types.Metadata, error) {
13 | return sgml.Render(doc, config, output, templates)
14 | }
15 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/html5_suite_test.go:
--------------------------------------------------------------------------------
1 | package html5_test
2 |
3 | import (
4 | . "github.com/onsi/ginkgo/v2"
5 | . "github.com/onsi/gomega"
6 |
7 | "testing"
8 |
9 | _ "github.com/bytesparadise/libasciidoc/testsupport"
10 | )
11 |
12 | func TestHtml5(t *testing.T) {
13 | RegisterFailHandler(Fail)
14 | RunSpecs(t, "Html5 Suite")
15 | }
16 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/icon.go:
--------------------------------------------------------------------------------
1 | package html5
2 |
3 | const (
4 | // Inline icons are presented as the icon as an image, or font icon, or the text "[class]" where class
5 | // is the Alt text, and defaults to the icon type. Unlike asciidoctor, we do place class settings on
6 | // the image to match rotate, flip, and size, allowing images to be manipulated with css style just
7 | // like the icons can. We also allow for an ID to be placed on the icon, as another enhancement.
8 | inlineIconTmpl = `` +
9 | `{{ if .Link }}{{ end }}` +
10 | `{{ .Icon }}` +
11 | `{{ if .Link }} {{ end }}` +
12 | ` `
13 |
14 | iconImageTmpl = ` `
24 |
25 | iconFontTmpl = ` `
31 |
32 | iconTextTmpl = `{{ if .Admonition }}{{ .Alt }}
{{ else }}[{{ .Alt }}]{{ end }}`
33 | )
34 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/image.go:
--------------------------------------------------------------------------------
1 | package html5
2 |
3 | const (
4 | blockImageTmpl = `
5 |
6 | {{ if ne .Href "" }}
{{ end }} {{ if ne .Href "" }} {{ end }}
7 |
{{ if .Title }}
8 |
{{ .Caption }}{{ .Title }}
9 | {{ else }}
10 | {{ end }}
11 | `
12 | inlineImageTmpl = `{{ if ne .Href "" }}{{ end }} {{ if ne .Href "" }} {{ end }} `
13 | )
14 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/inline_button.go:
--------------------------------------------------------------------------------
1 | package html5
2 |
3 | // initializes the sgml
4 | const (
5 | inlineButtonTmpl = `{{ . }} `
6 | )
7 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/inline_button_test.go:
--------------------------------------------------------------------------------
1 | package html5_test
2 |
3 | import (
4 | . "github.com/bytesparadise/libasciidoc/testsupport"
5 |
6 | . "github.com/onsi/ginkgo/v2"
7 | . "github.com/onsi/gomega"
8 | )
9 |
10 | var _ = Describe("buttons", func() {
11 |
12 | Context("in final documents", func() {
13 |
14 | It("when experimental is enabled", func() {
15 | source := `:experimental:
16 |
17 | Click on btn:[OK].`
18 | expected := `
21 | `
22 | Expect(RenderHTML(source)).To(MatchHTML(expected))
23 | })
24 |
25 | It("when experimental is not enabled", func() {
26 | source := `Click on btn:[OK].`
27 | expected := `
28 |
Click on btn:[OK].
29 |
30 | `
31 | Expect(RenderHTML(source)).To(MatchHTML(expected))
32 | })
33 | })
34 | })
35 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/inline_menu.go:
--------------------------------------------------------------------------------
1 | package html5
2 |
3 | // initializes the sgml
4 | const (
5 | inlineMenuTmpl =
6 | // eg: ``
7 | `{{ if len .Path | eq 1 }}` +
8 | // eg: ``
9 | `{{ else }}` +
10 | `` +
19 | `{{ end }}` +
20 | `{{ end }}`
21 | )
22 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/inline_menu_test.go:
--------------------------------------------------------------------------------
1 | package html5_test
2 |
3 | import (
4 | . "github.com/bytesparadise/libasciidoc/testsupport"
5 |
6 | . "github.com/onsi/ginkgo/v2"
7 | . "github.com/onsi/gomega"
8 | )
9 |
10 | var _ = Describe("inline menus", func() {
11 |
12 | Context("in final documents", func() {
13 |
14 | It("with main path", func() {
15 | source := `:experimental:
16 |
17 | Select menu:File[].`
18 | expected := `
21 | `
22 | Expect(RenderHTML(source)).To(MatchHTML(expected))
23 | })
24 |
25 | It("with single sub path", func() {
26 | source := `:experimental:
27 |
28 | Select menu:File[Save].`
29 | expected := `
32 | `
33 | Expect(RenderHTML(source)).To(MatchHTML(expected))
34 | })
35 |
36 | It("with multiple sub paths", func() {
37 | source := `:experimental:
38 |
39 | Select menu:File[Zoom > Reset].`
40 | expected := `
43 | `
44 | Expect(RenderHTML(source)).To(MatchHTML(expected))
45 | })
46 |
47 | It("when experimental is not enabled", func() {
48 | source := `Select menu:File[Zoom > Reset].`
49 | expected := `
50 |
Select menu:File[Zoom > Reset].
51 |
52 | `
53 | Expect(RenderHTML(source)).To(MatchHTML(expected))
54 | })
55 | })
56 | })
57 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/labeled_list.go:
--------------------------------------------------------------------------------
1 | package html5
2 |
3 | const (
4 | labeledListTmpl = `\n" +
7 | "{{ if .Title }}
{{ .Title }}
\n{{ end }}" +
8 | "
\n{{ .Content }} \n
\n"
9 |
10 | labeledListElementTmpl = "{{ .Term }} \n" +
11 | "{{ if .Content }}\n{{ .Content }} \n{{ end }}"
12 |
13 | labeledListHorizontalTmpl = `\n" +
16 | "{{ if .Title }}
{{ .Title }}
\n{{ end }}" +
17 | "
\n
\n"
18 |
19 | // Continuation items (multiple terms sharing a single definition) make this a bit more complex.
20 | labeledListHorizontalElementTmpl = "{{ if not .Continuation }}\n" +
21 | "\n{{ else }} \n{{ end }}" +
22 | "{{ .Term }}\n" +
23 | "{{ if .Content }} \n\n{{ .Content }} \n \n{{ end }}"
24 |
25 | qAndAListTmpl = "\n" +
28 | "{{ if .Title }}
{{ .Title }}
\n{{ end }}" +
29 | "
\n{{ .Content }} \n
\n"
30 |
31 | qAndAListElementTmpl = "\n{{ .Term }}
\n{{ .Content }} \n"
32 | )
33 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/link.go:
--------------------------------------------------------------------------------
1 | package html5
2 |
3 | const (
4 | linkTmpl = `{{ .Text }} `
5 | )
6 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/manpage.go:
--------------------------------------------------------------------------------
1 | package html5
2 |
3 | const (
4 | manpageHeaderTmpl = `{{ if.IncludeH1 }}
10 | {{ end }}`
11 | )
12 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/ordered_list.go:
--------------------------------------------------------------------------------
1 | package html5
2 |
3 | const (
4 | orderedListTmpl = `\n" +
8 | "{{ if .Title }}
{{ .Title }}
\n{{ end }}" +
9 | `
\n{{ .Content }} \n
\n"
15 |
16 | orderedListElementTmpl = "\n{{ .Content }} \n"
17 | )
18 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/paragraph.go:
--------------------------------------------------------------------------------
1 | package html5
2 |
3 | const (
4 | paragraphTmpl = "\n" +
6 | "{{ if .Title }}
{{ .Title }}
\n{{ end }}" +
7 | "
{{ .Content }}
\n" +
8 | "
\n"
9 |
10 | admonitionParagraphTmpl = `{{ if .Content }}` +
11 | "\n" +
13 | "
\n\n\n" +
14 | "{{ if .Icon }}{{ .Icon }}{{ end }}\n" +
15 | " \n" +
16 | "\n" +
17 | "{{ if .Title }}{{ .Title }}
\n{{ end }}" +
18 | "{{ .Content }}" +
19 | "\n \n \n
\n
\n{{ end }}"
20 |
21 | embeddedParagraphTmpl = "{{ .CheckStyle }}{{ .Content }}
\n"
22 |
23 | verseParagraphTmpl = "\n" +
24 | "{{ if .Title }}
{{ .Title }}
\n{{ end }}" +
25 | "
{{ .Content }} \n" +
26 | "{{ if .Attribution.First }}
\n— {{ .Attribution.First }}" +
27 | "{{ if .Attribution.Second }} \n{{ .Attribution.Second }} \n{{ else }}\n{{ end }}" +
28 | "
\n{{ end }}
\n"
29 |
30 | quoteParagraphTmpl = "\n" +
31 | "{{ if .Title }}
{{ .Title }}
\n{{ end }}" +
32 | "
\n" +
33 | "{{ .Content }}\n" +
34 | " \n" +
35 | "{{ if .Attribution.First }}
\n— {{ .Attribution.First }}" +
36 | "{{ if .Attribution.Second }} \n{{ .Attribution.Second }} \n{{ else }}\n{{ end }}" +
37 | "
\n{{ end }}
\n"
38 |
39 | manpageNameParagraphTmpl = "{{ .Content }}
\n"
40 |
41 | thematicBreakTmpl = " \n"
42 | )
43 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/preamble.go:
--------------------------------------------------------------------------------
1 | package html5
2 |
3 | const (
4 | preambleTmpl = `{{ if .Wrapper }}
5 |
6 | {{ end }}{{ .Content }}{{ if .Wrapper }}
7 | {{ if .ToC }}{{ .ToC }}{{ end }}
8 | {{ end }}`
9 | )
10 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/quoted_text.go:
--------------------------------------------------------------------------------
1 | package html5
2 |
3 | const (
4 | boldTextTmpl = `{{ .Content }} `
5 | italicTextTmpl = `{{ .Content }} `
6 | monospaceTextTmpl = `{{ .Content }}
`
7 | subscriptTextTmpl = `{{ .Content }} `
8 | superscriptTextTmpl = `{{ .Content }} `
9 | markedTextTmpl = `{{ if .Roles }}{{ .Content }}{{ if .Roles }} {{ else }}{{ end }}`
10 | )
11 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/section.go:
--------------------------------------------------------------------------------
1 | package html5
2 |
3 | // initializes the sgml
4 | const (
5 | sectionContentTmpl = `
6 | {{ .Header }}{{ if eq .Level 1 }}
7 | {{ end }}{{ .Content }}{{ if eq .Level 1 }}
8 | {{ end }}
9 | `
10 | sectionTitleTmpl = `{{ if .Number }}{{ .Number }}. {{ end }}{{ .Content }}
11 | `
12 | )
13 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/special_character_test.go:
--------------------------------------------------------------------------------
1 | package html5_test
2 |
3 | import (
4 | . "github.com/bytesparadise/libasciidoc/testsupport"
5 |
6 | . "github.com/onsi/ginkgo/v2"
7 | . "github.com/onsi/gomega"
8 | )
9 |
10 | var _ = Describe("special characters", func() {
11 |
12 | It("should parse in paragraph", func() {
13 | source := "* ' &"
14 | expected := `
15 |
<b>*</b> ' &
16 |
17 | `
18 | Expect(RenderHTML(source)).To(MatchHTML(expected))
19 | })
20 |
21 | It("should parse in delimited block", func() {
22 | source := "```" + "\n" +
23 | "* ' &" + "\n" +
24 | "```"
25 | expected := `
26 |
27 |
<b>*</b> ' &
28 |
29 |
30 | `
31 | Expect(RenderHTML(source)).To(MatchHTML(expected))
32 | })
33 |
34 | It("should parse in paragraph and delimited block", func() {
35 | source := "* ' &" + "\n\n" +
36 | "```" + "\n" +
37 | "* ' &" + "\n" +
38 | "```"
39 | expected := `
40 |
<b>*</b> ' &
41 |
42 |
43 |
44 |
<b>*</b> ' &
45 |
46 |
47 | `
48 | Expect(RenderHTML(source)).To(MatchHTML(expected))
49 | })
50 | })
51 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/table.go:
--------------------------------------------------------------------------------
1 | package html5
2 |
3 | const (
4 | tableTmpl = "\n" +
12 | "{{ if .Title }}{{ .Caption }}{{ .Title }} \n{{ end }}" +
13 | "{{ if .Body }}" +
14 | "\n" +
15 | "{{ range $i, $w := .Columns }} \n{{ end}}" +
18 | " \n" +
19 | "{{ .Header }}" +
20 | "{{ .Body }}" +
21 | "{{ .Footer }}" +
22 | "{{ end }}" +
23 | "
\n"
24 |
25 | tableBodyTmpl = "{{ if .Content }}\n{{ .Content }} \n{{ end }}"
26 |
27 | tableHeaderTmpl = "{{ if .Content }}\n\n{{ .Content }} \n \n{{ end }}"
28 |
29 | tableHeaderCellTmpl = " {{ .Content }} \n"
30 |
31 | tableFooterTmpl = "{{ if .Content }}
\n\n{{ .Content }} \n \n{{ end }}"
32 |
33 | tableFooterCellTmpl = "
{{ .Content }}
\n"
34 |
35 | tableRowTmpl = "
\n{{ .Content }} \n"
36 |
37 | tableCellTmpl = "
{{ .Content }} \n"
38 |
39 | tableCellBlockTmpl = "
{{ trimLineFeedSuffix .Content }}
"
40 | )
41 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/table_of_contents.go:
--------------------------------------------------------------------------------
1 | package html5
2 |
3 | const (
4 | tocRootTmpl = "
\n" +
5 | "
{{ .Title }}
\n" +
6 | "{{ .Sections }}" +
7 | "
\n"
8 |
9 | tocSectionTmpl = "
\n"
10 |
11 | tocEntryTmpl = "
{{ if .Number }}{{ .Number }}. {{ end }}{{ .Title }} " +
12 | "{{ if .Content }}\n{{ .Content }}{{ end }}\n"
13 | )
14 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/templates_test.go:
--------------------------------------------------------------------------------
1 | package html5_test
2 |
3 | import (
4 | "reflect"
5 |
6 | "github.com/bytesparadise/libasciidoc/pkg/renderer/sgml/html5"
7 | . "github.com/onsi/ginkgo/v2"
8 | . "github.com/onsi/gomega"
9 | )
10 |
11 | var _ = Describe("fields", func() {
12 |
13 | It("template fields are not empty", func() {
14 | tmp := html5.Templates() // sgml.Templates
15 | typ := reflect.TypeOf(tmp)
16 | val := reflect.ValueOf(tmp)
17 |
18 | for i := 0; i < typ.NumField(); i++ {
19 | fn := typ.Field(i).Name
20 | fv := val.FieldByName(fn)
21 |
22 | s, ok := fv.Interface().(string)
23 | Expect(ok).To(BeTrue())
24 | Expect(s).NotTo(BeEmpty())
25 | }
26 | })
27 | })
28 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/thematic_break_test.go:
--------------------------------------------------------------------------------
1 | package html5_test
2 |
3 | import (
4 | . "github.com/bytesparadise/libasciidoc/testsupport"
5 |
6 | . "github.com/onsi/ginkgo/v2"
7 | . "github.com/onsi/gomega"
8 | )
9 |
10 | var _ = Describe("thenatic breaks", func() {
11 |
12 | It("simple", func() {
13 | source := `before
14 |
15 | '''
16 |
17 | after`
18 | expected := `
21 |
22 |
25 | `
26 | Expect(RenderHTML(source)).To(MatchHTML(expected))
27 | })
28 | })
29 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html5/unordered_list.go:
--------------------------------------------------------------------------------
1 | package html5
2 |
3 | const (
4 | unorderedListTmpl = `
\n" +
9 | "{{ if .Title }}
{{ .Title }}
\n{{ end }}" +
10 | "
\n" +
11 | "{{ .Content }} \n
\n"
12 |
13 | unorderedListElementTmpl = "
\n{{ .Content }} \n"
14 | )
15 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/html_escape.go:
--------------------------------------------------------------------------------
1 | package sgml
2 |
3 | import (
4 | "html"
5 | "strings"
6 | )
7 |
8 | // escapeString is like html5.Escape func except but bypasses
9 | // a few replacements. It is a bit more conservative.
10 | func escapeString(s string) string {
11 | return htmlEscaper.Replace(s)
12 | }
13 |
14 | func unescapeString(s string) string {
15 | return html.UnescapeString(s)
16 | }
17 |
18 | var htmlEscaper = strings.NewReplacer(
19 | `<`, "<", // keep as-is (we do not want `<`)
20 | `>`, ">", // keep `&lgt;` as-is (we do not want `>`)
21 | `&`, "&", // keep `&s` as-is (we do not want `&`) // TODO: still needed?
22 | ``, "", // assume this is for an character entity and this keep as-is
23 | // standard escape combinations
24 | `&`, "&",
25 | `<`, "<",
26 | `>`, ">",
27 | `'`, "'", // "'" is shorter than "'" and apos was not in HTML until HTML5.
28 | `"`, """, // """ is shorter than """.
29 | )
30 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/index_term.go:
--------------------------------------------------------------------------------
1 | package sgml
2 |
3 | import (
4 | "github.com/bytesparadise/libasciidoc/pkg/types"
5 | )
6 |
7 | func (r *sgmlRenderer) renderIndexTerm(ctx *context, t *types.IndexTerm) (string, error) {
8 | return r.renderInlineElements(ctx, t.Term)
9 | }
10 |
11 | func (r *sgmlRenderer) renderConcealedIndexTerm(_ *types.ConcealedIndexTerm) (string, error) {
12 | return "", nil // do not render in SGML
13 | }
14 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/inline_button.go:
--------------------------------------------------------------------------------
1 | package sgml
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/bytesparadise/libasciidoc/pkg/types"
7 | "github.com/pkg/errors"
8 | )
9 |
10 | func (r *sgmlRenderer) renderInlineButton(b *types.InlineButton) (string, error) {
11 | result := &strings.Builder{}
12 | tmpl, err := r.inlineButton()
13 | if err != nil {
14 | return "", errors.Wrap(err, "unable to load inline button template")
15 | }
16 | if err = tmpl.Execute(result, b.Attributes[types.AttrButtonLabel]); err != nil {
17 | return "", errors.Wrap(err, "unable to render inline button")
18 | }
19 | return result.String(), nil
20 | }
21 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/inline_elements.go:
--------------------------------------------------------------------------------
1 | package sgml
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/bytesparadise/libasciidoc/pkg/types"
7 | )
8 |
9 | func (r *sgmlRenderer) renderInlineElements(ctx *context, elements []interface{}) (string, error) {
10 | if len(elements) == 0 {
11 | return "", nil
12 | }
13 | // log.Debugf("rendering line with %d element(s)...", len(elements))
14 | buf := &strings.Builder{}
15 | for i, element := range elements {
16 | renderedElement, err := r.renderElement(ctx, element)
17 | if err != nil {
18 | return "", err
19 | }
20 | if i == len(elements)-1 {
21 | if _, ok := element.(*types.StringElement); ok { // TODO: only for StringElement? or for any kind of element?
22 | // trim trailing spaces before returning the line
23 | buf.WriteString(strings.TrimRight(string(renderedElement), " "))
24 | } else {
25 | buf.WriteString(renderedElement)
26 | }
27 | } else {
28 | buf.WriteString(renderedElement)
29 | }
30 | }
31 | // if log.IsLevelEnabled(log.DebugLevel) {
32 | // log.Debugf("rendered inline elements: '%s'", buf.String())
33 | // }
34 | return buf.String(), nil
35 | }
36 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/inline_menu.go:
--------------------------------------------------------------------------------
1 | package sgml
2 |
3 | import (
4 | "github.com/bytesparadise/libasciidoc/pkg/types"
5 | )
6 |
7 | func (r *sgmlRenderer) renderInlineMenu(m *types.InlineMenu) (string, error) {
8 | return r.execute(r.inlineMenu, struct {
9 | Path []string
10 | }{
11 | Path: m.Path,
12 | })
13 | }
14 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/link.go:
--------------------------------------------------------------------------------
1 | package sgml
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/bytesparadise/libasciidoc/pkg/types"
7 | "github.com/pkg/errors"
8 | )
9 |
10 | func (r *sgmlRenderer) renderLink(ctx *context, l *types.InlineLink) (string, error) {
11 | text := ""
12 | class := ""
13 | id := l.Attributes.GetAsStringWithDefault(types.AttrID, "")
14 | roles, err := r.renderElementRoles(ctx, l.Attributes)
15 | if err != nil {
16 | return "", errors.Wrap(err, "unable to render link")
17 | }
18 | // TODO; support `mailto:` positional attributes
19 | if t, exists := l.Attributes[types.AttrInlineLinkText]; exists {
20 | switch t := t.(type) {
21 | case string:
22 | text = htmlEscaper.Replace(t)
23 | case []interface{}:
24 | var err error
25 | if text, err = r.renderInlineElements(ctx, t); err != nil {
26 | return "", errors.Wrap(err, "unable to render link")
27 | }
28 | }
29 | class = roles // can be empty (and it's fine)
30 | } else {
31 | text = htmlEscaper.Replace(l.Location.ToDisplayString())
32 | if l.Location != nil && l.Location.Scheme != "mailto:" {
33 | class = "bare"
34 | }
35 | if len(roles) > 0 {
36 | class = strings.Join([]string{class, roles}, " ") // support case where class == "" (for email addresses)
37 | }
38 | }
39 | target := l.Attributes.GetAsStringWithDefault(types.AttrInlineLinkTarget, "")
40 | noopener := target == "_blank" || l.Attributes.HasOption("noopener")
41 | return r.execute(r.link, struct {
42 | ID string
43 | URL string
44 | Text string
45 | Class string
46 | Target string
47 | NoOpener bool
48 | }{
49 | ID: id,
50 | URL: htmlEscaper.Replace(l.Location.ToString()),
51 | Text: text,
52 | Class: class,
53 | Target: target,
54 | NoOpener: noopener,
55 | })
56 | }
57 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/literal_blocks.go:
--------------------------------------------------------------------------------
1 | package sgml
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/bytesparadise/libasciidoc/pkg/types"
7 | "github.com/pkg/errors"
8 | log "github.com/sirupsen/logrus"
9 | )
10 |
11 | func (r *sgmlRenderer) renderLiteralBlock(ctx *context, b *types.DelimitedBlock) (string, error) {
12 | log.Debugf("rendering literal block")
13 | content, err := r.renderElements(ctx, b.Elements)
14 | if err != nil {
15 | return "", errors.Wrap(err, "unable to render literal block content")
16 | }
17 | roles, err := r.renderElementRoles(ctx, b.Attributes)
18 | if err != nil {
19 | return "", errors.Wrap(err, "unable to render literal block roles")
20 | }
21 | title, err := r.renderElementTitle(ctx, b.Attributes)
22 | if err != nil {
23 | return "", errors.Wrap(err, "unable to render literal block title")
24 | }
25 |
26 | return r.execute(r.literalBlock, struct {
27 | Context *context
28 | ID string
29 | Title string
30 | Roles string
31 | Content string
32 | }{
33 | Context: ctx,
34 | ID: r.renderElementID(b.Attributes),
35 | Title: title,
36 | Roles: roles,
37 | Content: strings.Trim(content, "\n"),
38 | })
39 | }
40 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/passthrough.go:
--------------------------------------------------------------------------------
1 | package sgml
2 |
3 | import (
4 | htmltemplate "html/template"
5 | "strings"
6 |
7 | "github.com/bytesparadise/libasciidoc/pkg/types"
8 | "github.com/pkg/errors"
9 | )
10 |
11 | func (r *sgmlRenderer) renderPassthroughParagraph(ctx *context, p *types.Paragraph) (string, error) {
12 | content, err := r.renderPassthroughContent(ctx, p.Elements)
13 | if err != nil {
14 | return "", err
15 | }
16 | return content + "\n", nil
17 | }
18 |
19 | func (r *sgmlRenderer) renderInlinePassthrough(ctx *context, p *types.InlinePassthrough) (string, error) {
20 | renderedContent, err := r.renderPassthroughContent(ctx, p.Elements)
21 | if err != nil {
22 | return "", errors.Wrap(err, "unable to render passthrough")
23 | }
24 | switch p.Kind {
25 | case types.SinglePlusPassthrough:
26 | // rendered passthrough content is in an HTML-escaped form
27 | buf := &strings.Builder{}
28 | htmltemplate.HTMLEscape(buf, []byte(renderedContent))
29 | return buf.String(), nil
30 | default:
31 | return renderedContent, nil
32 | }
33 | }
34 |
35 | // renderPassthroughMacro renders the passthrough content in its raw from
36 | func (r *sgmlRenderer) renderPassthroughContent(ctx *context, elements []interface{}) (string, error) {
37 | result := &strings.Builder{}
38 | for _, element := range elements {
39 | switch element := element.(type) {
40 | case *types.StringElement:
41 | // "string" elements must be rendered as-is, ie, without any HTML escaping.
42 | _, err := result.WriteString(element.Content)
43 | if err != nil {
44 | return "", err
45 | }
46 | default:
47 | renderedElement, err := r.renderElement(ctx, element)
48 | if err != nil {
49 | return "", err
50 | }
51 | _, err = result.WriteString(renderedElement)
52 | if err != nil {
53 | return "", err
54 | }
55 |
56 | }
57 | }
58 | return strings.Trim(result.String(), "\n"), nil // remove leading and trailing empty lines
59 | }
60 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/preamble.go:
--------------------------------------------------------------------------------
1 | package sgml
2 |
3 | import (
4 | "github.com/bytesparadise/libasciidoc/pkg/types"
5 | "github.com/pkg/errors"
6 | )
7 |
8 | func (r *sgmlRenderer) renderPreamble(ctx *context, p *types.Preamble) (string, error) {
9 | // log.Debugf("rendering preamble...")
10 | // the
wrapper is only necessary
11 | // if the document has a section 0
12 |
13 | content, err := r.renderElements(ctx, p.Elements)
14 | if err != nil {
15 | return "", errors.Wrap(err, "error rendering preamble elements")
16 | }
17 | toc, err := r.renderTableOfContents(ctx, p.TableOfContents)
18 | if err != nil {
19 | return "", errors.Wrap(err, "error rendering preamble elements")
20 | }
21 | return r.execute(r.preamble, struct {
22 | Context *context
23 | Wrapper bool
24 | Content string
25 | ToC string
26 | }{
27 | Context: ctx,
28 | Wrapper: ctx.hasHeader,
29 | Content: string(content),
30 | ToC: string(toc),
31 | })
32 | }
33 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/predefined_attribute.go:
--------------------------------------------------------------------------------
1 | package sgml
2 |
3 | import (
4 | "github.com/bytesparadise/libasciidoc/pkg/types"
5 | )
6 |
7 | func (r *sgmlRenderer) renderPredefinedAttribute(a *types.PredefinedAttribute) (string, error) {
8 | return predefinedAttribute(a.Name), nil
9 | }
10 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/predefined_attribute_test.go:
--------------------------------------------------------------------------------
1 | package sgml
2 |
3 | import (
4 | . "github.com/onsi/ginkgo/v2"
5 | . "github.com/onsi/gomega"
6 | )
7 |
8 | var _ = DescribeTable("predefined attributes",
9 | func(code, rendered string) {
10 | Expect(predefinedAttribute(code)).To(Equal(rendered))
11 | },
12 | Entry("sp", "sp", " "),
13 | Entry("blank", "blank", ""),
14 | Entry("empty", "empty", ""),
15 | Entry("nbsp", "nbsp", " "),
16 | Entry("zwsp", "zwsp", ""),
17 | Entry("wj", "wj", ""),
18 | Entry("apos", "apos", "'"),
19 | Entry("quot", "quot", """),
20 | Entry("lsquo", "lsquo", "‘"),
21 | Entry("rsquo", "rsquo", "’"),
22 | Entry("ldquo", "ldquo", "“"),
23 | Entry("rdquo", "rdquo", "”"),
24 | Entry("deg", "deg", "°"),
25 | Entry("plus", "plus", "+"),
26 | Entry("brvbar", "brvbar", "¦"),
27 | Entry("vbar", "vbar", "|"),
28 | Entry("amp", "amp", "&"),
29 | Entry("lt", "lt", "<"),
30 | Entry("gt", "gt", ">"),
31 | Entry("startsb", "startsb", "["),
32 | Entry("endsb", "endsb", "]"),
33 | Entry("caret", "caret", "^"),
34 | Entry("asterisk", "asterisk", "*"),
35 | Entry("tilde", "tilde", "~"),
36 | Entry("backslash", "backslash", `\`),
37 | Entry("backtick", "backtick", "`"),
38 | Entry("two-colons", "two-colons", "::"),
39 | Entry("two-semicolons", "two-semicolons", ";"),
40 | Entry("cpp", "cpp", "C++"),
41 | )
42 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/quoted_text.go:
--------------------------------------------------------------------------------
1 | package sgml
2 |
3 | import (
4 | "strings"
5 | texttemplate "text/template"
6 |
7 | "github.com/bytesparadise/libasciidoc/pkg/types"
8 | "github.com/pkg/errors"
9 | )
10 |
11 | // TODO: The bold, italic, and monospace items should be refactored to support semantic tags instead.
12 |
13 | func (r *sgmlRenderer) renderQuotedText(ctx *context, t *types.QuotedText) (string, error) {
14 | elementsBuffer := &strings.Builder{}
15 | for _, element := range t.Elements {
16 | b, err := r.renderElement(ctx, element)
17 | if err != nil {
18 | return "", errors.Wrap(err, "unable to render text quote")
19 | }
20 | _, err = elementsBuffer.WriteString(b)
21 | if err != nil {
22 | return "", errors.Wrapf(err, "unable to render text quote")
23 | }
24 | }
25 | roles, err := r.renderElementRoles(ctx, t.Attributes)
26 | if err != nil {
27 | return "", errors.Wrap(err, "unable to render quoted text roles")
28 | }
29 | var tmpl *texttemplate.Template
30 | switch t.Kind {
31 | case types.SingleQuoteBold, types.DoubleQuoteBold:
32 | tmpl, err = r.boldText()
33 | case types.SingleQuoteItalic, types.DoubleQuoteItalic:
34 | tmpl, err = r.italicText()
35 | case types.SingleQuoteMarked, types.DoubleQuoteMarked:
36 | tmpl, err = r.markedText()
37 | case types.SingleQuoteMonospace, types.DoubleQuoteMonospace:
38 | tmpl, err = r.monospaceText()
39 | case types.SingleQuoteSubscript:
40 | tmpl, err = r.subscriptText()
41 | case types.SingleQuoteSuperscript:
42 | tmpl, err = r.superscriptText()
43 | default:
44 | return "", errors.Errorf("unsupported quoted text kind: '%v'", t.Kind)
45 | }
46 | if err != nil {
47 | return "", errors.Wrap(err, "unable to load quoted text template")
48 | }
49 | result := &strings.Builder{}
50 | if err := tmpl.Execute(result, struct {
51 | ID string
52 | Roles string
53 | Attributes types.Attributes
54 | Content string
55 | }{
56 | Attributes: t.Attributes,
57 | ID: r.renderElementID(t.Attributes),
58 | Roles: roles,
59 | Content: string(elementsBuffer.String()),
60 | }); err != nil {
61 | return "", errors.Wrap(err, "unable to render quoted text")
62 | }
63 | return result.String(), nil
64 | }
65 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/section.go:
--------------------------------------------------------------------------------
1 | package sgml
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/bytesparadise/libasciidoc/pkg/types"
7 |
8 | "github.com/pkg/errors"
9 | log "github.com/sirupsen/logrus"
10 | )
11 |
12 | func (r *sgmlRenderer) renderSection(ctx *context, s *types.Section) (string, error) {
13 | // log.Debugf("rendering section level %d", s.Level)
14 | title, err := r.renderSectionTitle(ctx, s)
15 | if err != nil {
16 | return "", errors.Wrap(err, "error while rendering section title")
17 | }
18 |
19 | content, err := r.renderElements(ctx, s.Elements)
20 | if err != nil {
21 | return "", errors.Wrap(err, "error while rendering section content")
22 | }
23 | roles, err := r.renderElementRoles(ctx, s.Attributes)
24 | if err != nil {
25 | return "", errors.Wrap(err, "unable to render section roles")
26 | }
27 | return r.execute(r.sectionContent, struct {
28 | Context *context
29 | Header string
30 | Content string
31 | Elements []interface{}
32 | ID string
33 | Roles string
34 | Level int
35 | }{
36 | Context: ctx,
37 | Header: title,
38 | Level: s.Level,
39 | Elements: s.Elements,
40 | ID: r.renderElementID(s.Attributes),
41 | Roles: roles,
42 | Content: string(content),
43 | })
44 | }
45 |
46 | func (r *sgmlRenderer) renderSectionTitle(ctx *context, s *types.Section) (string, error) {
47 | renderedContent, err := r.renderInlineElements(ctx, s.Title)
48 | if err != nil {
49 | return "", errors.Wrap(err, "error while rendering section title")
50 | }
51 | renderedContentStr := strings.TrimSpace(renderedContent)
52 | var number string
53 | if ctx.sectionNumbering != nil {
54 | id := s.GetID()
55 | log.Debugf("number for section '%s': '%s'", id, number)
56 | number = ctx.sectionNumbering[id]
57 | }
58 | return r.execute(r.sectionTitle, struct {
59 | Level int
60 | LevelPlusOne int
61 | ID string
62 | Number string
63 | Content string
64 | }{
65 | Level: s.Level,
66 | LevelPlusOne: s.Level + 1, // Level 1 is
.
67 | ID: r.renderElementID(s.Attributes),
68 | Number: number,
69 | Content: renderedContentStr,
70 | })
71 | }
72 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/sgml_suite_test.go:
--------------------------------------------------------------------------------
1 | package sgml_test
2 |
3 | import (
4 | "testing"
5 |
6 | . "github.com/onsi/ginkgo/v2"
7 | . "github.com/onsi/gomega"
8 | )
9 |
10 | func TestSgml(t *testing.T) {
11 | RegisterFailHandler(Fail)
12 | RunSpecs(t, "Sgml Suite")
13 | }
14 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/special_character.go:
--------------------------------------------------------------------------------
1 | package sgml
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/bytesparadise/libasciidoc/pkg/types"
7 | )
8 |
9 | func (r *sgmlRenderer) renderSpecialCharacter(s *types.SpecialCharacter) (string, error) {
10 | switch s.Name {
11 | case `&`:
12 | return "&", nil
13 | case `<`:
14 | return "<", nil
15 | case `>`:
16 | return ">", nil
17 | default:
18 | return "", fmt.Errorf("unknown special character: '%s'", s.Name)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/string.go:
--------------------------------------------------------------------------------
1 | package sgml
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | "unicode"
7 |
8 | "github.com/bytesparadise/libasciidoc/pkg/types"
9 | )
10 |
11 | func (r *sgmlRenderer) renderStringElement(ctx *context, str *types.StringElement) (string, error) {
12 | // NB: For all SGML flavors we are aware of, the numeric entities from
13 | // Unicode are supported. We generally avoid named entities.
14 | result := str.Content
15 | if !ctx.UseUnicode() {
16 | // convert to entities
17 | result = asciiEntify(result)
18 | }
19 | return result, nil
20 | }
21 |
22 | func asciiEntify(source string) string {
23 | out := &strings.Builder{}
24 | out.Grow(len(source))
25 | for _, r := range source {
26 | // This will certain characters that should be escaped alone. Run them through
27 | // escape first if that is a concern.
28 | if r < 128 && (unicode.IsPrint(r) || unicode.IsSpace(r)) {
29 | out.WriteRune(r)
30 | continue
31 | }
32 | // take care that the entity is unsigned (should always be)
33 | fmt.Fprintf(out, "%d;", uint32(r)) // TODO: avoid `fmt.Fprintf`, use `fmt.Fprint` instead?
34 | }
35 | return out.String()
36 | }
37 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/symbol.go:
--------------------------------------------------------------------------------
1 | package sgml
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/bytesparadise/libasciidoc/pkg/types"
7 | )
8 |
9 | var symbols = map[string]string{
10 | "(C)": "©",
11 | "(R)": "®",
12 | "(TM)": "™",
13 | "...": "…", // include the 'zero width' character (``) to prevent increased letter spacing in justification
14 | "'": "’",
15 | "'`": "‘",
16 | "`'": "’",
17 | "\"`": "“",
18 | "`\"": "”",
19 | "->": "→",
20 | "<-": "←",
21 | "=>": "⇒",
22 | "<=": "⇐",
23 | "--": "—", // include the 'zero width' character (``) to prevent increased letter spacing in justification
24 | " -- ": " — ", // surrounded by thin spaces
25 | }
26 |
27 | func (r *sgmlRenderer) renderSymbol(s *types.Symbol) (string, error) {
28 | if str, found := symbols[s.Name]; found {
29 | // if s.Prefix != "" {
30 | // return s.Prefix + str, nil
31 | // }
32 | return str, nil
33 | }
34 | return "", fmt.Errorf("symbol '%s' is not defined", s.Name)
35 | }
36 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/user_macro.go:
--------------------------------------------------------------------------------
1 | package sgml
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/bytesparadise/libasciidoc/pkg/types"
7 | )
8 |
9 | func (r *sgmlRenderer) renderUserMacro(ctx *context, m *types.UserMacro) (string, error) {
10 | buf := &strings.Builder{}
11 | macro, ok := ctx.config.Macros[m.Name]
12 | if !ok {
13 | if m.Kind == types.BlockMacro {
14 | // fallback to paragraph
15 | p, _ := types.NewParagraph(
16 | &types.StringElement{Content: m.RawText},
17 | )
18 | return r.renderParagraph(ctx, p)
19 | }
20 | // fallback to render raw text
21 | return m.RawText, nil
22 | }
23 | if err := macro.Execute(buf, m); err != nil {
24 | return "", err
25 | }
26 | return buf.String(), nil
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/xhtml5/article.go:
--------------------------------------------------------------------------------
1 | package xhtml5
2 |
3 | const (
4 | articleTmpl = "\n" +
5 | "\n" +
6 | "\n" +
7 | " \n" +
8 | " \n" +
9 | " \n" +
10 | "{{ if .Generator }} \n{{ end }}" +
11 | "{{ if .Authors }} \n{{ end }}" +
12 | "{{ range $css := .CSS }} \n{{ end }}" +
13 | "{{ .Title }} \n" +
14 | "\n" +
15 | "\n" +
18 | "{{ if .IncludeHTMLBodyHeader }}{{ .Header }}{{ end }}" +
19 | " \n" +
20 | "{{ .Content }}" +
21 | "
\n" +
22 | "{{ if .IncludeHTMLBodyFooter }}\n{{ end }}" +
28 | "\n" +
29 | "\n"
30 | )
31 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/xhtml5/blank_line.go:
--------------------------------------------------------------------------------
1 | package xhtml5
2 |
3 | const (
4 | lineBreakTmpl = " "
5 | )
6 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/xhtml5/blank_line_test.go:
--------------------------------------------------------------------------------
1 | package xhtml5_test
2 |
3 | import (
4 | . "github.com/bytesparadise/libasciidoc/testsupport"
5 | . "github.com/onsi/ginkgo/v2"
6 | . "github.com/onsi/gomega"
7 | )
8 |
9 | var _ = Describe("blank lines", func() {
10 |
11 | It("blank line between 2 paragraphs", func() {
12 | source := `first paragraph
13 |
14 | second paragraph`
15 | expected := `
16 |
first paragraph
17 |
18 |
19 |
second paragraph
20 |
21 | `
22 | Expect(RenderXHTML(source)).To(MatchHTML(expected))
23 | })
24 |
25 | It("blank line with spaces and tabs between 2 paragraphs", func() {
26 | source := `first paragraph
27 |
28 | second paragraph`
29 | expected := `
30 |
first paragraph
31 |
32 |
33 |
second paragraph
34 |
35 | `
36 | Expect(RenderXHTML(source)).To(MatchHTML(expected))
37 | })
38 |
39 | It("blank lines (tabs) at end of document", func() {
40 | source := `first paragraph
41 |
42 |
43 | `
44 | expected := `
45 |
first paragraph
46 |
47 | `
48 | Expect(RenderXHTML(source)).To(MatchHTML(expected))
49 | })
50 |
51 | It("blank lines (spaces) at end of document", func() {
52 | source := `first paragraph
53 |
54 |
55 | `
56 | expected := `
57 |
first paragraph
58 |
59 | `
60 | Expect(RenderXHTML(source)).To(MatchHTML(expected))
61 | })
62 |
63 | It("hard break", func() {
64 | source := `first line +
65 | second line`
66 | expected := `
67 |
first line
68 | second line
69 |
70 | `
71 | Expect(RenderXHTML(source)).To(MatchHTML(expected))
72 | })
73 |
74 | It("thematic break", func() {
75 | source := "- - -"
76 | expected := " \n"
77 | Expect(RenderXHTML(source)).To(MatchHTML(expected))
78 | })
79 | })
80 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/xhtml5/comment_test.go:
--------------------------------------------------------------------------------
1 | package xhtml5_test
2 |
3 | import (
4 | . "github.com/bytesparadise/libasciidoc/testsupport"
5 |
6 | . "github.com/onsi/ginkgo/v2"
7 | . "github.com/onsi/gomega"
8 | )
9 |
10 | var _ = Describe("comments", func() {
11 |
12 | Context("single line comments", func() {
13 |
14 | It("single line comment alone", func() {
15 | source := `// A single-line comment.`
16 | expected := ""
17 | Expect(RenderXHTML(source)).To(MatchHTML(expected))
18 | })
19 |
20 | It("single line comment at end of line", func() {
21 | source := `foo // A single-line comment.`
22 | expected := `
23 |
foo // A single-line comment.
24 |
25 | `
26 | Expect(RenderXHTML(source)).To(MatchHTML(expected))
27 | })
28 |
29 | It("single line comment within a paragraph", func() {
30 | source := `a first line
31 | // A single-line comment.
32 | another line`
33 | expected := `
34 |
a first line
35 | another line
36 |
37 | `
38 | Expect(RenderXHTML(source)).To(MatchHTML(expected))
39 | })
40 | })
41 |
42 | Context("comment blocks", func() {
43 |
44 | It("comment block alone", func() {
45 | source := `////
46 | a *comment* block
47 | with multiple lines
48 | ////`
49 | expected := ""
50 | Expect(RenderXHTML(source)).To(MatchHTML(expected))
51 | })
52 |
53 | It("comment block with paragraphs around", func() {
54 | source := `a first paragraph
55 |
56 | ////
57 | a *comment* block
58 | with multiple lines
59 | ////
60 |
61 | a second paragraph`
62 | expected := `
63 |
a first paragraph
64 |
65 |
66 |
a second paragraph
67 |
68 | `
69 | Expect(RenderXHTML(source)).To(MatchHTML(expected))
70 | })
71 | })
72 |
73 | })
74 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/xhtml5/delimited_block_fenced_test.go:
--------------------------------------------------------------------------------
1 | package xhtml5_test
2 |
3 | import (
4 | . "github.com/bytesparadise/libasciidoc/testsupport"
5 |
6 | . "github.com/onsi/ginkgo/v2"
7 | . "github.com/onsi/gomega"
8 | )
9 |
10 | var _ = Describe("fenced blocks", func() {
11 |
12 | Context("as delimited blocks", func() {
13 |
14 | It("fenced block with surrounding empty lines", func() {
15 | source := "```\n\nsome source code \n\nhere \n\n\n\n```"
16 | expected := `
17 |
18 |
some source code
19 |
20 | here
21 |
22 |
23 | `
24 | Expect(RenderXHTML(source)).To(MatchHTML(expected))
25 | })
26 |
27 | It("fenced block with empty lines", func() {
28 | source := "```\n\n\n\n```"
29 | expected := `
34 | `
35 | Expect(RenderXHTML(source)).To(MatchHTML(expected))
36 | })
37 |
38 | It("fenced block with id and title", func() {
39 | source := "[#id-for-fences]\n.fenced block title\n```\nsome source code\n\nhere\n\n\n\n```"
40 | expected := `
41 |
fenced block title
42 |
43 |
some source code
44 |
45 | here
46 |
47 |
48 | `
49 | Expect(RenderXHTML(source)).To(MatchHTML(expected))
50 | })
51 |
52 | It("fenced block with external link inside", func() {
53 | source := "```" + "\n" +
54 | "a https://website.com" + "\n" +
55 | "and more text on the" + "\n" +
56 | "next lines" + "\n\n" +
57 | "```"
58 | expected := `
59 |
60 |
a https://website.com
61 | and more text on the
62 | next lines
63 |
64 |
65 | `
66 | Expect(RenderXHTML(source)).To(MatchHTML(expected))
67 | })
68 | })
69 | })
70 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/xhtml5/delimited_block_markdown_quote_test.go:
--------------------------------------------------------------------------------
1 | package xhtml5_test
2 |
3 | import (
4 | . "github.com/bytesparadise/libasciidoc/testsupport"
5 |
6 | . "github.com/onsi/ginkgo/v2"
7 | . "github.com/onsi/gomega"
8 | )
9 |
10 | var _ = Describe("markdown-style quote blocks", func() {
11 |
12 | Context("as delimited blocks", func() {
13 |
14 | It("with single marker without author", func() {
15 | source := `> some text
16 | on *multiple lines*`
17 |
18 | expected := `
19 |
20 |
21 |
some text
22 | on multiple lines
23 |
24 |
25 |
26 | `
27 | Expect(RenderXHTML(source)).To(MatchHTML(expected))
28 | })
29 |
30 | It("with marker on each line without author", func() {
31 | source := `> some text
32 | > on *multiple lines*`
33 |
34 | expected := `
35 |
36 |
37 |
some text
38 | on multiple lines
39 |
40 |
41 |
42 | `
43 | Expect(RenderXHTML(source)).To(MatchHTML(expected))
44 | })
45 |
46 | It("with marker on each line with author", func() {
47 | source := `> some text
48 | > on *multiple lines*
49 | > -- John Doe`
50 | expected := `
51 |
52 |
53 |
some text
54 | on multiple lines
55 |
56 |
57 |
58 | — John Doe
59 |
60 |
61 | `
62 | Expect(RenderXHTML(source)).To(MatchHTML(expected))
63 | })
64 |
65 | It("with marker on each line with author and title", func() {
66 | source := `.title
67 | > some text
68 | > on *multiple lines*
69 | > -- John Doe`
70 | expected := `
71 |
title
72 |
73 |
74 |
some text
75 | on multiple lines
76 |
77 |
78 |
79 | — John Doe
80 |
81 |
82 | `
83 | Expect(RenderXHTML(source)).To(MatchHTML(expected))
84 | })
85 |
86 | It("with author only", func() {
87 | source := `> -- John Doe`
88 | expected := `
89 |
90 |
91 |
92 | — John Doe
93 |
94 |
95 | `
96 | Expect(RenderXHTML(source)).To(MatchHTML(expected))
97 | })
98 | })
99 | })
100 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/xhtml5/delimited_block_passthrough_test.go:
--------------------------------------------------------------------------------
1 | package xhtml5_test
2 |
3 | import (
4 | . "github.com/bytesparadise/libasciidoc/testsupport"
5 |
6 | . "github.com/onsi/ginkgo/v2"
7 | . "github.com/onsi/gomega"
8 | )
9 |
10 | var _ = Describe("passthrough blocks", func() {
11 |
12 | Context("as delimited blocks", func() {
13 |
14 | It("with title", func() {
15 | source := `.a title
16 | ++++
17 | _foo_
18 |
19 | *bar*
20 | ++++`
21 | expected := `_foo_
22 |
23 | *bar*
24 | `
25 | Expect(RenderXHTML(source)).To(MatchHTML(expected))
26 | })
27 |
28 | It("with special characters", func() {
29 | source := `++++
30 |
31 |
32 |
33 | ++++`
34 | expected := `
35 |
36 |
37 | `
38 | Expect(RenderXHTML(source)).To(MatchHTML(expected))
39 | })
40 |
41 | })
42 |
43 | Context("open block", func() {
44 |
45 | It("2-line paragraph followed by another paragraph", func() {
46 | source := `[pass]
47 | _foo_
48 | *bar*
49 |
50 | another paragraph`
51 | expected := `_foo_
52 | *bar*
53 |
54 |
another paragraph
55 |
56 | `
57 | Expect(RenderXHTML(source)).To(MatchHTML(expected))
58 | })
59 | })
60 | })
61 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/xhtml5/delimited_block_quote_tmpl.go:
--------------------------------------------------------------------------------
1 | package xhtml5
2 |
3 | const (
4 | quoteBlockTmpl = `\n" +
6 | "{{ if .Title }}
{{ .Title }}
\n{{ end }}" +
7 | "
\n" +
8 | "{{ .Content }}" +
9 | " \n" +
10 | "{{ if .Attribution.First }}
\n" +
11 | "— {{ .Attribution.First }}" +
12 | "{{ if .Attribution.Second }} \n{{ .Attribution.Second }} {{ end }}\n" +
13 | "
\n{{ end }}" +
14 | "
\n"
15 | )
16 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/xhtml5/delimited_block_sidebar_test.go:
--------------------------------------------------------------------------------
1 | package xhtml5_test
2 |
3 | import (
4 | . "github.com/bytesparadise/libasciidoc/testsupport"
5 |
6 | . "github.com/onsi/ginkgo/v2"
7 | . "github.com/onsi/gomega"
8 | )
9 |
10 | var _ = Describe("sidebar blocks", func() {
11 |
12 | Context("as delimited blocks", func() {
13 |
14 | It("sidebar block with paragraph", func() {
15 | source := `****
16 | some *verse* content
17 |
18 | ****`
19 | expected := `
26 | `
27 | Expect(RenderXHTML(source)).To(MatchHTML(expected))
28 | })
29 |
30 | It("sidebar block with id, title, paragraph and sourcecode block", func() {
31 | source := `[#id-for-sidebar]
32 | .title for sidebar
33 | ****
34 | some *verse* content
35 |
36 | ----
37 | foo
38 | bar
39 | ----
40 | ****`
41 | expected := `
55 | `
56 | Expect(RenderXHTML(source)).To(MatchHTML(expected))
57 | })
58 | })
59 | })
60 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/xhtml5/delimited_block_verse_tmpl.go:
--------------------------------------------------------------------------------
1 | package xhtml5
2 |
3 | const (
4 | verseBlockTmpl = `\n" +
6 | "{{ if .Title }}
{{ .Title }}
\n{{ end }}" +
7 | "
{{ .Content }} \n" +
8 | "{{ if .Attribution.First }}
\n— {{ .Attribution.First }}" +
9 | "{{ if .Attribution.Second }} \n{{ .Attribution.Second }} {{ end }}\n" +
10 | "
\n{{ end }}" +
11 | "
\n"
12 | )
13 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/xhtml5/document_details.go:
--------------------------------------------------------------------------------
1 | package xhtml5
2 |
3 | const (
4 | documentDetailsTmpl = `{{ if .Authors }}
5 | {{ .Authors }}{{ end }}{{ if .RevNumber }}
6 | {{ if .RevLabel }}{{ .RevLabel }} {{ end }}{{ .RevNumber }}{{ if .RevDate }},{{ end }} {{ end }}{{ if .RevDate }}
7 | {{ .RevDate }} {{ end }}{{ if .RevRemark }}
8 | {{ end }}
9 |
10 | `
11 |
12 | documentAuthorDetailsTmpl = `{{ if .Name }}{{ .Name }} {{ end }}{{ if .Email }}
13 | {{ .Email }} {{ end }}`
14 | )
15 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/xhtml5/footnote_reference.go:
--------------------------------------------------------------------------------
1 | package xhtml5
2 |
3 | const (
4 | footnotesTmpl = "\n"
5 | )
6 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/xhtml5/icon.go:
--------------------------------------------------------------------------------
1 | package xhtml5
2 |
3 | const (
4 | // Inline icons are presented as the icon as an image, or font icon, or the text "[class]" where class
5 | // is the Alt text, and defaults to the icon type. Unlike asciidoctor, we do place class settings on
6 | // the image to match rotate, flip, and size, allowing images to be manipulated with css style just
7 | // like the icons can. We also allow for an ID to be placed on the icon, as another enhancement.
8 | //
9 | // Only the img tag needs to be made XHTML safe.
10 |
11 | iconImageTmpl = ` `
21 | )
22 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/xhtml5/image.go:
--------------------------------------------------------------------------------
1 | package xhtml5
2 |
3 | const (
4 | blockImageTmpl = "\n" +
7 | "
\n" +
8 | `{{ if .Href }}
{{ end }}` +
9 | ` {{ if .Href }} {{ end }}\n" +
13 | "
\n" +
14 | "{{ if .Title }}
{{ .Caption }}{{ .Title }}
\n{{ end }}" +
15 | "
\n"
16 |
17 | inlineImageTmpl = `` +
18 | `{{ if .Href }}{{ end }}` +
19 | ` {{ if .Href }} {{ end }} `
24 | )
25 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/xhtml5/labeled_list.go:
--------------------------------------------------------------------------------
1 | package xhtml5
2 |
3 | const (
4 | labeledListHorizontalItemTmpl = "{{ if not .Continuation }}\n" +
5 | "\n{{ else }} \n{{ end }}" +
6 | "{{ .Term }}\n" +
7 | "{{ if .Content }} \n\n{{ .Content }} \n \n{{ end }}"
8 | )
9 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/xhtml5/paragraph.go:
--------------------------------------------------------------------------------
1 | package xhtml5
2 |
3 | const (
4 | verseParagraphTmpl = "\n" +
5 | "{{ if .Title }}
{{ .Title }}
\n{{ end }}" +
6 | "
{{ .Content }} \n" +
7 | "{{ if .Attribution.First }}
\n— {{ .Attribution.First }}" +
8 | "{{ if .Attribution.Second }} \n{{ .Attribution.Second }} \n{{ else }}\n{{ end }}" +
9 | "
\n{{ end }}
\n"
10 |
11 | quoteParagraphTmpl = "\n" +
12 | "{{ if .Title }}
{{ .Title }}
\n{{ end }}" +
13 | "
\n" +
14 | "{{ .Content }}\n" +
15 | " \n" +
16 | "{{ if .Attribution.First }}
\n— {{ .Attribution.First }}" +
17 | "{{ if .Attribution.Second }} \n{{ .Attribution.Second }} \n{{ else }}\n{{ end }}" +
18 | "
\n{{ end }}
\n"
19 |
20 | thematicBreakTmpl = " \n"
21 | )
22 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/xhtml5/string_test.go:
--------------------------------------------------------------------------------
1 | package xhtml5_test
2 |
3 | import (
4 | . "github.com/bytesparadise/libasciidoc/testsupport"
5 |
6 | . "github.com/onsi/ginkgo/v2"
7 | . "github.com/onsi/gomega"
8 | )
9 |
10 | var _ = Describe("strings", func() {
11 |
12 | It("text with ellipsis", func() {
13 | source := `some text...`
14 | expected := `
17 | `
18 | Expect(RenderXHTML(source)).To(MatchHTML(expected))
19 | })
20 |
21 | It("text with copyright", func() {
22 | source := `Copyright (C)`
23 | expected := `
26 | `
27 | Expect(RenderXHTML(source)).To(MatchHTML(expected))
28 | })
29 |
30 | It("text with trademark", func() {
31 | source := `TheRightThing(TM)`
32 | expected := `
35 | `
36 | Expect(RenderXHTML(source)).To(MatchHTML(expected))
37 | })
38 |
39 | It("text with registered", func() {
40 | source := `TheRightThing(R)`
41 | expected := `
44 | `
45 | Expect(RenderXHTML(source)).To(MatchHTML(expected))
46 | })
47 |
48 | It("title with registered", func() {
49 | // We will often want to use these symbols in headers.
50 | source := `== Registered(R)`
51 | expected := `
52 |
Registered®
53 |
54 |
55 |
56 | `
57 | Expect(RenderXHTML(source)).To(MatchHTML(expected))
58 | })
59 | })
60 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/xhtml5/suite_test.go:
--------------------------------------------------------------------------------
1 | package xhtml5_test
2 |
3 | import (
4 | "bytes"
5 | "strings"
6 |
7 | "github.com/bytesparadise/libasciidoc"
8 | "github.com/bytesparadise/libasciidoc/pkg/configuration"
9 | "github.com/bytesparadise/libasciidoc/pkg/types"
10 | . "github.com/onsi/ginkgo/v2"
11 | . "github.com/onsi/gomega"
12 | log "github.com/sirupsen/logrus"
13 |
14 | "testing"
15 |
16 | _ "github.com/bytesparadise/libasciidoc/testsupport"
17 | )
18 |
19 | func RenderXHTML(actual string, settings ...configuration.Setting) (string, error) {
20 | output, _, err := RenderXHTMLWithMetadata(actual, settings...)
21 | return output, err
22 | }
23 |
24 | func RenderXHTMLWithMetadata(actual string, settings ...configuration.Setting) (string, types.Metadata, error) {
25 | allSettings := append([]configuration.Setting{configuration.WithFilename("test.adoc"), configuration.WithBackEnd("xhtml5")}, settings...)
26 | config := configuration.NewConfiguration(allSettings...)
27 |
28 | contentReader := strings.NewReader(actual)
29 | resultWriter := bytes.NewBuffer(nil)
30 | metadata, err := libasciidoc.Convert(contentReader, resultWriter, config)
31 | if err != nil {
32 | return "", types.Metadata{}, err
33 | }
34 | if log.IsLevelEnabled(log.DebugLevel) {
35 | log.Debug(resultWriter.String())
36 | }
37 | return resultWriter.String(), metadata, nil
38 | }
39 |
40 | func TestXHtml5(t *testing.T) {
41 | RegisterFailHandler(Fail)
42 | RunSpecs(t, "XHtml5 Suite")
43 | }
44 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/xhtml5/table.go:
--------------------------------------------------------------------------------
1 | package xhtml5
2 |
3 | const (
4 | tableTmpl = "\n" +
12 | "{{ if .Title }}{{ .Caption }}{{ .Title }} \n{{ end }}" +
13 | "{{ if .Body }}" +
14 | "\n" +
15 | "{{ range $i, $w := .Columns }} \n{{ end}}" +
18 | " \n" +
19 | "{{ .Header }}" +
20 | "{{ .Body }}" +
21 | "{{ .Footer }}" +
22 | "{{ end }}" +
23 | "
\n"
24 | )
25 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/xhtml5/thematic_break_test.go:
--------------------------------------------------------------------------------
1 | package xhtml5_test
2 |
3 | import (
4 | . "github.com/bytesparadise/libasciidoc/testsupport"
5 |
6 | . "github.com/onsi/ginkgo/v2"
7 | . "github.com/onsi/gomega"
8 | )
9 |
10 | var _ = Describe("thematic breaks", func() {
11 |
12 | It("simple", func() {
13 | source := `before
14 |
15 | '''
16 |
17 | after`
18 | expected := `
21 |
22 |
25 | `
26 | Expect(RenderXHTML(source)).To(MatchHTML(expected))
27 | })
28 | })
29 |
--------------------------------------------------------------------------------
/pkg/renderer/sgml/xhtml5/xhtml5.go:
--------------------------------------------------------------------------------
1 | package xhtml5
2 |
3 | import (
4 | "io"
5 |
6 | "github.com/bytesparadise/libasciidoc/pkg/configuration"
7 | "github.com/bytesparadise/libasciidoc/pkg/renderer/sgml"
8 | "github.com/bytesparadise/libasciidoc/pkg/renderer/sgml/html5"
9 | "github.com/bytesparadise/libasciidoc/pkg/types"
10 | )
11 |
12 | // Render renders the document to the output, using the SGML renderer configured with the XHTML5 templates
13 | func Render(doc *types.Document, config *configuration.Configuration, output io.Writer) (types.Metadata, error) {
14 | templates := html5.Templates()
15 | // XHTML5 overrides of HTML5.
16 | templates.Article = articleTmpl
17 | templates.BlockImage = blockImageTmpl
18 | templates.LineBreak = lineBreakTmpl
19 | templates.DocumentAuthorDetails = documentAuthorDetailsTmpl
20 | templates.DocumentDetails = documentDetailsTmpl
21 | templates.Footnotes = footnotesTmpl
22 | templates.IconImage = iconImageTmpl
23 | templates.InlineImage = inlineImageTmpl
24 | templates.LabeledListHorizontalElement = labeledListHorizontalItemTmpl
25 | templates.Table = tableTmpl
26 | templates.ThematicBreak = thematicBreakTmpl
27 | templates.QuoteBlock = quoteBlockTmpl
28 | templates.QuoteParagraph = quoteParagraphTmpl
29 | templates.VerseBlock = verseBlockTmpl
30 | templates.VerseParagraph = verseParagraphTmpl
31 |
32 | return sgml.Render(doc, config, output, templates)
33 | }
34 |
--------------------------------------------------------------------------------
/pkg/types/document_xrefs.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | // ElementReferences the element references in the document
4 | type ElementReferences map[string]interface{}
5 |
6 | // ElementReferencesCollector the visitor that traverses the whole document structure in search for elements with an ID
7 |
--------------------------------------------------------------------------------
/pkg/types/footnotes.go:
--------------------------------------------------------------------------------
1 | package types
2 |
--------------------------------------------------------------------------------
/pkg/types/predefined_attributes.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | // PredefinedAttributes the predefined document attributes
4 | // May be converted into HTML entities
5 | var predefinedAttributes = []string{
6 | "sp",
7 | "blank",
8 | "empty",
9 | "nbsp",
10 | "zwsp",
11 | "wj",
12 | "apos",
13 | "quot",
14 | "lsquo",
15 | "rsquo",
16 | "ldquo",
17 | "rdquo",
18 | "deg",
19 | "plus",
20 | "brvbar",
21 | "vbar",
22 | "amp",
23 | "lt",
24 | "gt",
25 | "startsb",
26 | "endsb",
27 | "caret",
28 | "asterisk",
29 | "tilde",
30 | "backslash",
31 | "backtick",
32 | "two-colons",
33 | "two-semicolons",
34 | "cpp",
35 | }
36 |
37 | func isPrefedinedAttribute(a string) bool {
38 | for _, v := range predefinedAttributes {
39 | if v == a {
40 | return true
41 | }
42 | }
43 | return false
44 | }
45 |
--------------------------------------------------------------------------------
/pkg/types/types_suite_test.go:
--------------------------------------------------------------------------------
1 | package types_test
2 |
3 | import (
4 | "testing"
5 |
6 | _ "github.com/bytesparadise/libasciidoc/testsupport"
7 |
8 | . "github.com/onsi/ginkgo/v2"
9 | . "github.com/onsi/gomega"
10 | )
11 |
12 | func TestTypes(t *testing.T) {
13 | RegisterFailHandler(Fail)
14 | RunSpecs(t, "Types Suite")
15 | }
16 |
--------------------------------------------------------------------------------
/pkg/validator/validator_suite_test.go:
--------------------------------------------------------------------------------
1 | package validator_test
2 |
3 | import (
4 | "testing"
5 |
6 | . "github.com/onsi/ginkgo/v2"
7 | . "github.com/onsi/gomega"
8 | )
9 |
10 | func TestValidator(t *testing.T) {
11 | RegisterFailHandler(Fail)
12 | RunSpecs(t, "Validator Suite")
13 | }
14 |
--------------------------------------------------------------------------------
/test/compat/article.adoc:
--------------------------------------------------------------------------------
1 | = AsciiDoc Article Title
2 | Firstname Lastname
3 | 1.0, July 29, 2014, Asciidoctor 1.5 article template
4 | :toc:
5 | :icons: font
6 | :quick-uri: https://asciidoctor.org/docs/asciidoc-syntax-quick-reference/
7 |
8 | Content entered directly below the header but before the first section heading is called the preamble.
9 |
10 | == First level heading
11 |
12 | This is a paragraph with a *bold* word and an _italicized_ word.
13 |
14 | .Image caption
15 | image::image-file-name.png[I am the image alt text.]
16 |
17 | This is another paragraph.footnote:[I am footnote text and will be displayed at the bottom of the article.]
18 |
19 | === Second level heading
20 |
21 | .Unordered list title
22 | * list item 1
23 | ** nested list item
24 | *** nested nested list item 1
25 | *** nested nested list item 2
26 | * list item 2
27 |
28 | This is a paragraph.
29 |
30 | .Example block title
31 | ====
32 | Content in an example block is subject to normal substitutions.
33 | ====
34 |
35 | .Sidebar title
36 | ****
37 | Sidebars contain aside text and are subject to normal substitutions.
38 | ****
39 |
40 | ==== Third level heading
41 |
42 | [#id-for-listing-block]
43 | .Listing block title
44 | ----
45 | Content in a listing block is subject to verbatim substitutions.
46 | Listing block content is commonly used to preserve code input.
47 | ----
48 |
49 | ===== Fourth level heading
50 |
51 | .Table title
52 | |===
53 | |Column heading 1 |Column heading 2
54 |
55 | |Column 1, row 1
56 | |Column 2, row 1
57 |
58 | |Column 1, row 2
59 | |Column 2, row 2
60 | |===
61 |
62 | ====== Fifth level heading
63 |
64 | [quote, firstname lastname, movie title]
65 | ____
66 | I am a block quote or a prose excerpt.
67 | I am subject to normal substitutions.
68 | ____
69 |
70 | [verse, firstname lastname, poem title and more]
71 | ____
72 | I am a verse block.
73 | Indents and endlines are preserved in verse blocks.
74 | ____
75 |
76 | == First level heading
77 |
78 | TIP: There are five admonition labels: Tip, Note, Important, Caution and Warning.
79 |
80 | // I am a comment and won't be rendered.
81 |
82 | . ordered list item
83 | .. nested ordered list item
84 | . ordered list item
85 |
86 | The text at the end of this sentence is cross referenced to <<_third_level_heading,the third level heading>>
87 |
88 | == First level heading
89 |
90 | This is a link to the https://asciidoctor.org/docs/user-manual/[Asciidoctor User Manual].
91 | This is an attribute reference {quick-uri}[which links this text to the Asciidoctor Quick Reference Guide].
92 |
--------------------------------------------------------------------------------
/test/compat/basic.adoc:
--------------------------------------------------------------------------------
1 | = Introduction to AsciiDoc
2 | Doc Writer
3 |
4 | A preface about https://asciidoc.org[AsciiDoc].
5 |
6 | == First Section
7 |
8 | * item 1
9 | * item 2
10 |
11 | [source,ruby]
12 | puts "Hello, World!"
13 |
--------------------------------------------------------------------------------
/test/compat/basic.html:
--------------------------------------------------------------------------------
1 |
8 |
9 |
First Section
10 |
11 |
12 |
13 |
14 | item 1
15 |
16 |
17 | item 2
18 |
19 |
20 |
21 |
22 |
23 |
puts "Hello, World!"
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/test/compat/demo-shorter.adoc:
--------------------------------------------------------------------------------
1 | [[purpose]]
2 | .Purpose
3 | ****
4 | This document exercises many of the features of AsciiDoc to test the {library} implementation.
5 | ****
--------------------------------------------------------------------------------
/test/compat/demo-shorter.html:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/test/compat/encoding.adoc:
--------------------------------------------------------------------------------
1 | Gregory Romé has written an AsciiDoc plugin for the Redmine project management application.
2 |
3 | https://github.com/foo-users/foo
4 | へと `vicmd` キーマップを足してみている試み、
5 | アニメーションgifです。
6 |
7 | tag::romé[]
8 | Gregory Romé has written an AsciiDoc plugin for the Redmine project management application.
9 | end::romé[]
10 |
11 | == Überschrift
12 |
13 | * Codierungen sind verrückt auf älteren Versionen von Ruby
14 |
--------------------------------------------------------------------------------
/test/compat/encoding.html:
--------------------------------------------------------------------------------
1 |
2 |
Gregory Romé has written an AsciiDoc plugin for the Redmine project management application.
3 |
4 |
9 |
10 |
tag::romé[]
11 | Gregory Romé has written an AsciiDoc plugin for the Redmine project management application.
12 | end::romé[]
13 |
14 |
15 |
Überschrift
16 |
25 |
26 |
--------------------------------------------------------------------------------
/test/compat/exline-mono.adoc:
--------------------------------------------------------------------------------
1 | :encoding: UTF-8
2 |
3 | The document is assumed to be encoded as `{encoding}`.
4 |
--------------------------------------------------------------------------------
/test/compat/exline-mono.html:
--------------------------------------------------------------------------------
1 |
2 |
The document is assumed to be encoded as UTF-8
.
3 |
4 |
--------------------------------------------------------------------------------
/test/compat/include.adoc:
--------------------------------------------------------------------------------
1 | I come from an include file.
--------------------------------------------------------------------------------
/test/compat/includes/chapter-a.adoc:
--------------------------------------------------------------------------------
1 | = Chapter A
2 |
3 | content
4 |
--------------------------------------------------------------------------------
/test/compat/includes/child-include.adoc:
--------------------------------------------------------------------------------
1 | first line of child
2 |
3 | include::grandchild-include.adoc[]
4 |
5 | last line of child
6 |
--------------------------------------------------------------------------------
/test/compat/includes/grandchild-include.adoc:
--------------------------------------------------------------------------------
1 | first line of grandchild
2 |
3 | last line of grandchild
4 |
--------------------------------------------------------------------------------
/test/compat/lists.adoc:
--------------------------------------------------------------------------------
1 | = Document Title
2 | Doc Writer
3 |
4 | Preamble paragraph.
5 |
6 | NOTE: This is test, only a test.
7 |
8 | == Lists
9 |
10 | .Unordered, basic
11 | * Edgar Allen Poe
12 | * Sheri S. Tepper
13 | * Bill Bryson
14 |
15 | .Unordered, max nesting
16 | * level 1
17 | ** level 2
18 | *** level 3
19 | **** level 4
20 | ***** level 5
21 | * level 1
22 |
23 | .Checklist
24 | - [*] checked
25 | - [x] also checked
26 | - [ ] not checked
27 | - normal list item
28 |
29 | .Ordered, basic
30 | . Step 1
31 | . Step 2
32 | . Step 3
33 |
34 | .Ordered, nested
35 | . Step 1
36 | . Step 2
37 | .. Step 2a
38 | .. Step 2b
39 | . Step 3
40 |
41 | .Ordered, max nesting
42 | . level 1
43 | .. level 2
44 | ... level 3
45 | .... level 4
46 | ..... level 5
47 | . level 1
48 |
49 | .Labeled, single-line
50 | first term:: definition of first term
51 | section term:: definition of second term
52 |
53 | .Labeled, multi-line
54 | first term::
55 | definition of first term
56 | second term::
57 | definition of second term
58 |
59 | .Q&A
60 | [qanda]
61 | What is Asciidoctor?::
62 | An implementation of the AsciiDoc processor in Ruby.
63 | What is the answer to the Ultimate Question?:: 42
64 |
65 | .Mixed
66 | Operating Systems::
67 | Linux:::
68 | . Fedora
69 | * Desktop
70 | . Ubuntu
71 | * Desktop
72 | * Server
73 | BSD:::
74 | . FreeBSD
75 | . NetBSD
76 |
77 | Cloud Providers::
78 | PaaS:::
79 | . OpenShift
80 | . CloudBees
81 | IaaS:::
82 | . Amazon EC2
83 | . Rackspace
84 |
85 | .Unordered, complex
86 | * level 1
87 | ** level 2
88 | *** level 3
89 | This is a new line inside an unordered list using {plus} symbol.
90 | We can even force content to start on a separate line... +
91 | Amazing, isn't it?
92 | **** level 4
93 | +
94 | The {plus} symbol is on a new line.
95 |
96 | ***** level 5
97 |
--------------------------------------------------------------------------------
/test/compat/master.adoc:
--------------------------------------------------------------------------------
1 | = Master Document
2 |
3 | preamble
4 |
5 | include::includes/chapter-a.adoc[leveloffset=+1]
6 |
--------------------------------------------------------------------------------
/test/compat/master.html:
--------------------------------------------------------------------------------
1 |
8 |
16 |
--------------------------------------------------------------------------------
/test/compat/parent-include.adoc:
--------------------------------------------------------------------------------
1 | first line of parent
2 |
3 | include::includes/child-include.adoc[]
4 |
5 | last line of parent
6 |
--------------------------------------------------------------------------------
/test/compat/parent-include.html:
--------------------------------------------------------------------------------
1 |
2 |
first line of parent
3 |
4 |
5 |
first line of child
6 |
7 |
8 |
first line of grandchild
9 |
10 |
11 |
last line of grandchild
12 |
13 |
14 |
last line of child
15 |
16 |
17 |
last line of parent
18 |
19 |
--------------------------------------------------------------------------------
/test/compat/sample.adoc:
--------------------------------------------------------------------------------
1 | = Document Title
2 | Doc Writer
3 | :idprefix: id_
4 |
5 | Preamble paragraph.
6 |
7 | NOTE: This is test, only a test.
8 |
9 | == Section A
10 |
11 | *Section A* paragraph.
12 |
13 | === Section A Subsection
14 |
15 | *Section A* 'subsection' paragraph.
16 |
17 | == Section B
18 |
19 | *Section B* paragraph.
20 |
21 | |===
22 | |a |b |c
23 | |1 |2 |3
24 | |===
25 |
26 | .Section B list
27 | * Item 1
28 | * Item 2
29 | * Item 3
30 |
--------------------------------------------------------------------------------
/test/compat/sample.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Preamble paragraph.
5 |
6 |
7 |
8 |
9 |
10 | Note
11 |
12 |
13 | This is test, only a test.
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
Section A
22 |
23 |
24 |
Section A paragraph.
25 |
26 |
27 |
Section A Subsection
28 |
29 |
Section A 'subsection' paragraph.
30 |
31 |
32 |
33 |
34 |
35 |
Section B
36 |
37 |
38 |
Section B paragraph.
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | a
49 | b
50 | c
51 |
52 |
53 | 1
54 | 2
55 | 3
56 |
57 |
58 |
59 |
60 |
Section B list
61 |
62 |
63 | Item 1
64 |
65 |
66 | Item 2
67 |
68 |
69 | Item 3
70 |
71 |
72 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/test/compat/sscript.adoc:
--------------------------------------------------------------------------------
1 | _v_~rocket~ is the value
2 | ^3^He is the isotope
3 | log~4~x^n^ is the expression
4 | M^me^ White is the address
5 | the 10^th^ point has coordinate (x~10~, y~10~)
6 |
--------------------------------------------------------------------------------
/test/compat/sscript.html:
--------------------------------------------------------------------------------
1 |
2 |
v rocket is the value
3 | 3 He is the isotope
4 | log4 xn is the expression
5 | Mme White is the address
6 | the 10th point has coordinate (x10 , y10 )
7 |
8 |
--------------------------------------------------------------------------------
/test/compat_test.go:
--------------------------------------------------------------------------------
1 | package test_test
2 |
3 | import (
4 | "bytes"
5 | "os"
6 | "os/exec"
7 | "path/filepath"
8 | "runtime"
9 | "strings"
10 |
11 | "github.com/bytesparadise/libasciidoc"
12 | "github.com/bytesparadise/libasciidoc/pkg/configuration"
13 | . "github.com/bytesparadise/libasciidoc/testsupport"
14 |
15 | . "github.com/onsi/ginkgo/v2"
16 | . "github.com/onsi/gomega"
17 | log "github.com/sirupsen/logrus"
18 | )
19 |
20 | var autocrlf string
21 |
22 | func init() {
23 | cmd := exec.Command("git", "config", "--get", "core.autocrlf")
24 | // Attach buffer to command
25 | cmdOutput := bytes.NewBuffer(nil)
26 | cmd.Stdout = cmdOutput
27 | // execute command
28 | err := cmd.Run()
29 | if err != nil {
30 | log.Errorf("failed to check the `git config --get autocrlf': %v", err)
31 | return
32 | }
33 | autocrlf = strings.Trim(cmdOutput.String(), "\r\n")
34 | log.Warnf("git autocrlf='%s'", autocrlf)
35 |
36 | }
37 |
38 | var _ = DescribeTable("compat", args("compat/*.adoc")...)
39 |
40 | func args(pattern string) []interface{} {
41 | result := []interface{}{}
42 | result = append(result, compare)
43 | files, _ := filepath.Glob(pattern)
44 | for _, file := range files {
45 | if filepath.Base(file) == "include.adoc" {
46 | // skip
47 | continue
48 | }
49 | result = append(result, Entry(file, file))
50 | }
51 | return result
52 |
53 | }
54 |
55 | func compare(filename string) {
56 | actual := &strings.Builder{}
57 | _, err := libasciidoc.ConvertFile(actual, configuration.NewConfiguration(
58 | configuration.WithFilename(filename),
59 | configuration.WithBackEnd("html5"),
60 | configuration.WithAttribute("libasciidoc-version", "0.7.0"),
61 | ))
62 | Expect(err).NotTo(HaveOccurred())
63 | // retrieve the reference document
64 | path := strings.TrimSuffix(filename, ".adoc") + ".html"
65 | content, err := os.ReadFile(path)
66 | Expect(err).NotTo(HaveOccurred())
67 | expected := string(content)
68 | // if tests are executed on windows platform and git 'autocrlf' is set to 'true',
69 | // then we need to remove the `\r` characters that were added in the 'expected'
70 | // source at the time of the checkout
71 | if runtime.GOOS == "windows" && autocrlf == "true" {
72 | expected = strings.Replace(expected, "\r", "", -1)
73 | }
74 | // compare actual vs reference
75 | Expect(actual.String()).To(MatchHTML(expected))
76 | }
77 |
--------------------------------------------------------------------------------
/test/doc.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | // this package contains adoc file with various features that are verified to be supported by Libasciidoc
4 | // i.e., they should produce the same output as Asciidoctor
5 |
--------------------------------------------------------------------------------
/test/images/favicon-glasses-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bytesparadise/libasciidoc/d90d46fa316478894fb43f9e4277807140e563ea/test/images/favicon-glasses-16x16.png
--------------------------------------------------------------------------------
/test/includes/attributes.adoc:
--------------------------------------------------------------------------------
1 | :author: Xavier
2 | :leveloffset: +1
3 |
4 | some content
--------------------------------------------------------------------------------
/test/includes/chapter-a.adoc:
--------------------------------------------------------------------------------
1 | = Chapter A
2 |
3 | content
--------------------------------------------------------------------------------
/test/includes/child-include-preamble.adoc:
--------------------------------------------------------------------------------
1 | child preamble
2 |
3 | == child section 1
4 |
5 | first line of child
6 |
7 | include::grandchild-include.adoc[leveloffset=+1]
8 |
9 | === child section 2
10 |
11 | last line of child
12 |
--------------------------------------------------------------------------------
/test/includes/child-include.adoc:
--------------------------------------------------------------------------------
1 | = child title
2 |
3 | first line of child
4 |
5 | include::grandchild-include.adoc[]
6 |
7 | last line of child
8 |
--------------------------------------------------------------------------------
/test/includes/grandchild-include.adoc:
--------------------------------------------------------------------------------
1 | == grandchild title
2 |
3 | first line of grandchild
4 |
5 | last line of grandchild
6 |
--------------------------------------------------------------------------------
/test/includes/hello_world.go.txt:
--------------------------------------------------------------------------------
1 | package includes
2 |
3 | import "fmt"
4 |
5 | func helloworld() {
6 | fmt.Println("hello, world!")
7 | }
8 |
--------------------------------------------------------------------------------
/test/includes/include.foo:
--------------------------------------------------------------------------------
1 | *some strong content*
2 |
3 | include::hello_world.go.txt[]
4 |
--------------------------------------------------------------------------------
/test/includes/parent-include-absolute-offset.adoc:
--------------------------------------------------------------------------------
1 | = parent title
2 |
3 | first line of parent
4 |
5 | include::child-include-preamble.adoc[leveloffset=3]
6 |
7 | last line of parent
8 |
--------------------------------------------------------------------------------
/test/includes/parent-include-relative-offset.adoc:
--------------------------------------------------------------------------------
1 | = parent title
2 |
3 | first line of parent
4 |
5 | include::child-include-preamble.adoc[leveloffset=+1]
6 |
7 | last line of parent
8 |
--------------------------------------------------------------------------------
/test/includes/parent-include.adoc:
--------------------------------------------------------------------------------
1 | :leveloffset: +1
2 |
3 | = parent title
4 |
5 | first line of parent
6 |
7 | include::child-include.adoc[]
8 |
9 | last line of parent <1>
10 |
11 | :leveloffset!:
--------------------------------------------------------------------------------
/test/includes/sample.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | *foo*
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/test/includes/table_common.txt:
--------------------------------------------------------------------------------
1 | | Header A | Header B
2 |
3 |
--------------------------------------------------------------------------------
/test/includes/table_parent.adoc:
--------------------------------------------------------------------------------
1 | |===
2 | include::table_common.txt[]
3 | include::table_specific.txt[]
4 | |===
--------------------------------------------------------------------------------
/test/includes/table_specific.txt:
--------------------------------------------------------------------------------
1 | | Column A.1 | Column A.2
2 | | Column B.1 | Column B.2
--------------------------------------------------------------------------------
/test/includes/tag-include-unclosed.adoc:
--------------------------------------------------------------------------------
1 | // tag::doc[]
2 | // tag::section[]
3 | == Section 1
4 | // end::section[]
5 |
6 | // tag::unclosed[]
7 |
8 | // tag::content[]
9 | content
10 |
11 | // end::content[]
12 | // end::doc[]
13 |
14 | end
--------------------------------------------------------------------------------
/test/includes/tag-include.adoc:
--------------------------------------------------------------------------------
1 | // tag::doc[]
2 | // tag::section[]
3 | == Section 1
4 | // end::section[]
5 |
6 | // tag::content[]
7 | content
8 |
9 | // end::content[]
10 | // end::doc[]
11 |
12 | end
--------------------------------------------------------------------------------
/test/test_suite_test.go:
--------------------------------------------------------------------------------
1 | package test_test
2 |
3 | import (
4 | "testing"
5 |
6 | . "github.com/onsi/ginkgo/v2"
7 | . "github.com/onsi/gomega"
8 |
9 | _ "github.com/bytesparadise/libasciidoc/testsupport"
10 | )
11 |
12 | func TestTest(t *testing.T) {
13 | RegisterFailHandler(Fail)
14 | RunSpecs(t, "Test Suite")
15 | }
16 |
--------------------------------------------------------------------------------
/testsupport/document_fragment_matcher.go:
--------------------------------------------------------------------------------
1 | package testsupport
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/bytesparadise/libasciidoc/pkg/types"
7 |
8 | "github.com/davecgh/go-spew/spew"
9 | "github.com/google/go-cmp/cmp"
10 | gomegatypes "github.com/onsi/gomega/types"
11 | "github.com/pkg/errors"
12 | log "github.com/sirupsen/logrus"
13 | )
14 |
15 | // MatchDocumentFragment a custom matcher to verify that a document matches the given expectation
16 | // Similar to the standard `Equal` matcher, but display a diff when the values don't match
17 | func MatchDocumentFragment(expected types.DocumentFragment) gomegatypes.GomegaMatcher {
18 | return &documentFragmentMatcher{
19 | expected: expected,
20 | }
21 | }
22 |
23 | type documentFragmentMatcher struct {
24 | expected types.DocumentFragment
25 | diffs string
26 | }
27 |
28 | func (m *documentFragmentMatcher) Match(actual interface{}) (success bool, err error) {
29 | if _, ok := actual.(types.DocumentFragment); !ok {
30 | return false, errors.Errorf("MatchDocumentFragment matcher expects a 'types.DocumentFragment' (actual: %T)", actual)
31 | }
32 | if diff := cmp.Diff(m.expected, actual, opts...); diff != "" {
33 | if log.IsLevelEnabled(log.DebugLevel) {
34 | log.Debugf("actual document fragment:\n%s", spew.Sdump(actual))
35 | log.Debugf("expected document fragment:\n%s", spew.Sdump(m.expected))
36 | }
37 | m.diffs = diff
38 | return false, nil
39 | }
40 | return true, nil
41 | }
42 |
43 | func (m *documentFragmentMatcher) FailureMessage(_ interface{}) (message string) {
44 | return fmt.Sprintf("expected document fragments to match:\n%s", m.diffs)
45 | }
46 |
47 | func (m *documentFragmentMatcher) NegatedFailureMessage(_ interface{}) (message string) {
48 | return fmt.Sprintf("expected document fragments not to match:\n%s", m.diffs)
49 | }
50 |
--------------------------------------------------------------------------------
/testsupport/document_fragment_matcher_test.go:
--------------------------------------------------------------------------------
1 | package testsupport_test
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/bytesparadise/libasciidoc/pkg/types"
7 | "github.com/bytesparadise/libasciidoc/testsupport"
8 |
9 | "github.com/google/go-cmp/cmp"
10 | . "github.com/onsi/ginkgo/v2"
11 | . "github.com/onsi/gomega"
12 | )
13 |
14 | var _ = Describe("document fragments matcher", func() {
15 |
16 | // given
17 | expected := types.DocumentFragment{
18 | Elements: []interface{}{
19 | &types.RawLine{
20 | Content: "a paragraph.",
21 | },
22 | },
23 | }
24 | matcher := testsupport.MatchDocumentFragment(expected)
25 |
26 | It("should match", func() {
27 | // given
28 | actual := types.DocumentFragment{
29 | Elements: []interface{}{
30 | &types.RawLine{
31 | Content: "a paragraph.",
32 | },
33 | },
34 | }
35 | // when
36 | result, err := matcher.Match(actual)
37 | // then
38 | Expect(err).ToNot(HaveOccurred())
39 | Expect(result).To(BeTrue())
40 | })
41 |
42 | It("should not match", func() {
43 | // given
44 | actual := types.DocumentFragment{
45 | Elements: []interface{}{
46 | &types.RawLine{
47 | Content: "something else",
48 | },
49 | },
50 | }
51 | // when
52 | result, err := matcher.Match(actual)
53 | // then
54 | Expect(err).ToNot(HaveOccurred())
55 | Expect(result).To(BeFalse())
56 | diffs := cmp.Diff(expected, actual)
57 | Expect(matcher.FailureMessage(actual)).To(Equal(fmt.Sprintf("expected document fragments to match:\n%s", diffs)))
58 | Expect(matcher.NegatedFailureMessage(actual)).To(Equal(fmt.Sprintf("expected document fragments not to match:\n%s", diffs)))
59 | })
60 |
61 | It("should return error when invalid type is input", func() {
62 | // when
63 | result, err := matcher.Match(1)
64 | // then
65 | Expect(err).To(HaveOccurred())
66 | Expect(err.Error()).To(Equal("MatchDocumentFragment matcher expects a 'types.DocumentFragment' (actual: int)"))
67 | Expect(result).To(BeFalse())
68 | })
69 |
70 | })
71 |
--------------------------------------------------------------------------------
/testsupport/document_fragments_matcher.go:
--------------------------------------------------------------------------------
1 | package testsupport
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/bytesparadise/libasciidoc/pkg/types"
7 | "github.com/davecgh/go-spew/spew"
8 | "github.com/google/go-cmp/cmp"
9 | gomegatypes "github.com/onsi/gomega/types"
10 | "github.com/pkg/errors"
11 | log "github.com/sirupsen/logrus"
12 | )
13 |
14 | // MatchDocumentFragments a custom matcher to verify that a document matches the given expectation
15 | // Similar to the standard `Equal` matcher, but display a diff when the values don't match
16 | func MatchDocumentFragments(expected []types.DocumentFragment) gomegatypes.GomegaMatcher {
17 | return &documentFragmentsMatcher{
18 | expected: expected,
19 | }
20 | }
21 |
22 | type documentFragmentsMatcher struct {
23 | expected []types.DocumentFragment
24 | diffs string
25 | }
26 |
27 | func (m *documentFragmentsMatcher) Match(actual interface{}) (success bool, err error) {
28 | if _, ok := actual.([]types.DocumentFragment); !ok {
29 | return false, errors.Errorf("MatchDocumentFragments matcher expects a '[]types.DocumentFragment' (actual: %T)", actual)
30 | }
31 | if diff := cmp.Diff(m.expected, actual, opts...); diff != "" {
32 | if log.IsLevelEnabled(log.DebugLevel) {
33 | log.Debugf("actual document fragments:\n%s", spew.Sdump(actual))
34 | log.Debugf("expected document fragments:\n%s", spew.Sdump(m.expected))
35 | }
36 | m.diffs = diff
37 | return false, nil
38 | }
39 | return true, nil
40 | }
41 |
42 | func (m *documentFragmentsMatcher) FailureMessage(_ interface{}) (message string) {
43 | return fmt.Sprintf("expected document fragments to match:\n%s", m.diffs)
44 | }
45 |
46 | func (m *documentFragmentsMatcher) NegatedFailureMessage(_ interface{}) (message string) {
47 | return fmt.Sprintf("expected document fragments not to match:\n%s", m.diffs)
48 | }
49 |
--------------------------------------------------------------------------------
/testsupport/document_fragments_matcher_test.go:
--------------------------------------------------------------------------------
1 | package testsupport_test
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/bytesparadise/libasciidoc/pkg/types"
7 | "github.com/bytesparadise/libasciidoc/testsupport"
8 | "github.com/google/go-cmp/cmp"
9 |
10 | . "github.com/onsi/ginkgo/v2"
11 | . "github.com/onsi/gomega"
12 | )
13 |
14 | var _ = Describe("document fragment matcher", func() {
15 |
16 | // given
17 | expected := []types.DocumentFragment{
18 | {
19 | Elements: []interface{}{
20 | &types.RawLine{
21 | Content: "a paragraph.",
22 | },
23 | },
24 | },
25 | }
26 | matcher := testsupport.MatchDocumentFragments(expected)
27 |
28 | It("should match", func() {
29 | // given
30 | actual := []types.DocumentFragment{
31 | {
32 | Elements: []interface{}{
33 | &types.RawLine{
34 | Content: "a paragraph.",
35 | },
36 | },
37 | },
38 | }
39 | // when
40 | result, err := matcher.Match(actual)
41 | // then
42 | Expect(err).ToNot(HaveOccurred())
43 | Expect(result).To(BeTrue())
44 | })
45 |
46 | It("should not match", func() {
47 | // given
48 | actual := []types.DocumentFragment{
49 | {
50 | Elements: []interface{}{
51 | &types.RawLine{
52 | Content: "something else",
53 | },
54 | },
55 | },
56 | }
57 | // when
58 | result, err := matcher.Match(actual)
59 | // then
60 | Expect(err).ToNot(HaveOccurred())
61 | Expect(result).To(BeFalse())
62 | diffs := cmp.Diff(expected, actual)
63 | Expect(matcher.FailureMessage(actual)).To(Equal(fmt.Sprintf("expected document fragments to match:\n%s", diffs)))
64 | Expect(matcher.NegatedFailureMessage(actual)).To(Equal(fmt.Sprintf("expected document fragments not to match:\n%s", diffs)))
65 | })
66 |
67 | It("should return error when invalid type is input", func() {
68 | // when
69 | result, err := matcher.Match(1)
70 | // then
71 | Expect(err).To(HaveOccurred())
72 | Expect(err.Error()).To(Equal("MatchDocumentFragments matcher expects a '[]types.DocumentFragment' (actual: int)"))
73 | Expect(result).To(BeFalse())
74 | })
75 |
76 | })
77 |
--------------------------------------------------------------------------------
/testsupport/document_matcher.go:
--------------------------------------------------------------------------------
1 | package testsupport
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/bytesparadise/libasciidoc/pkg/types"
7 |
8 | "github.com/davecgh/go-spew/spew"
9 | "github.com/google/go-cmp/cmp"
10 | "github.com/google/go-cmp/cmp/cmpopts"
11 | gomegatypes "github.com/onsi/gomega/types"
12 | "github.com/pkg/errors"
13 | log "github.com/sirupsen/logrus"
14 | )
15 |
16 | // MatchDocument a custom matcher to verify that a document matches the given expectation
17 | // Similar to the standard `Equal` matcher, but display a diff when the values don't match
18 | func MatchDocument(expected *types.Document) gomegatypes.GomegaMatcher {
19 | return &documentMatcher{
20 | expected: expected,
21 | }
22 | }
23 |
24 | type documentMatcher struct {
25 | expected *types.Document
26 | diffs string
27 | }
28 |
29 | var opts = []cmp.Option{cmpopts.IgnoreUnexported(
30 | types.List{},
31 | types.DelimitedBlock{},
32 | types.Footnotes{},
33 | types.TableOfContents{},
34 | types.AttributeDeclaration{},
35 | types.AttributeReference{},
36 | types.AttributeReset{},
37 | types.CounterSubstitution{},
38 | types.PredefinedAttribute{},
39 | )}
40 |
41 | func (m *documentMatcher) Match(actual interface{}) (success bool, err error) {
42 | if _, ok := actual.(*types.Document); !ok {
43 | return false, errors.Errorf("MatchDocument matcher expects a 'types.Document' (actual: %T)", actual)
44 | }
45 | if diff := cmp.Diff(m.expected, actual, opts...); diff != "" {
46 | if log.IsLevelEnabled(log.DebugLevel) {
47 | log.Debugf("actual document:\n%s", spew.Sdump(actual))
48 | log.Debugf("expected document:\n%s", spew.Sdump(m.expected))
49 | }
50 | m.diffs = diff
51 | return false, nil
52 | }
53 | return true, nil
54 | }
55 |
56 | func (m *documentMatcher) FailureMessage(_ interface{}) (message string) {
57 | return fmt.Sprintf("expected documents to match:\n%s", m.diffs)
58 | }
59 |
60 | func (m *documentMatcher) NegatedFailureMessage(_ interface{}) (message string) {
61 | return fmt.Sprintf("expected documents not to match:\n%s", m.diffs)
62 | }
63 |
--------------------------------------------------------------------------------
/testsupport/document_matcher_test.go:
--------------------------------------------------------------------------------
1 | package testsupport_test
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/bytesparadise/libasciidoc/pkg/types"
7 | "github.com/bytesparadise/libasciidoc/testsupport"
8 | "github.com/google/go-cmp/cmp"
9 |
10 | . "github.com/onsi/ginkgo/v2"
11 | . "github.com/onsi/gomega"
12 | )
13 |
14 | var _ = Describe("document matcher", func() {
15 |
16 | // given
17 | expected := &types.Document{
18 | Elements: []interface{}{
19 | &types.Paragraph{
20 | Elements: []interface{}{
21 | &types.StringElement{
22 | Content: "a paragraph.",
23 | },
24 | },
25 | },
26 | },
27 | }
28 | matcher := testsupport.MatchDocument(expected)
29 |
30 | It("should match", func() {
31 | // given
32 | actual := &types.Document{
33 | Elements: []interface{}{
34 | &types.Paragraph{
35 | Elements: []interface{}{
36 | &types.StringElement{
37 | Content: "a paragraph.",
38 | },
39 | },
40 | },
41 | },
42 | }
43 | // when
44 | result, err := matcher.Match(actual)
45 | // then
46 | Expect(err).ToNot(HaveOccurred())
47 | Expect(result).To(BeTrue())
48 | })
49 |
50 | It("should not match", func() {
51 | // given
52 | actual := &types.Document{
53 | Elements: []interface{}{
54 | &types.Paragraph{
55 | Elements: []interface{}{
56 | &types.StringElement{
57 | Content: "another paragraph.",
58 | },
59 | },
60 | },
61 | },
62 | }
63 | // when
64 | result, err := matcher.Match(actual)
65 | // then
66 | Expect(err).ToNot(HaveOccurred())
67 | Expect(result).To(BeFalse())
68 | diffs := cmp.Diff(expected, actual)
69 | Expect(matcher.FailureMessage(actual)).To(Equal(fmt.Sprintf("expected documents to match:\n%s", diffs)))
70 | Expect(matcher.NegatedFailureMessage(actual)).To(Equal(fmt.Sprintf("expected documents not to match:\n%s", diffs)))
71 | })
72 |
73 | It("should return error when invalid type is input", func() {
74 | // when
75 | result, err := matcher.Match(1)
76 | // then
77 | Expect(err).To(HaveOccurred())
78 | Expect(err.Error()).To(Equal("MatchDocument matcher expects a 'types.Document' (actual: int)"))
79 | Expect(result).To(BeFalse())
80 | })
81 |
82 | })
83 |
--------------------------------------------------------------------------------
/testsupport/inline_elements_matcher.go:
--------------------------------------------------------------------------------
1 | package testsupport
2 |
3 | import (
4 | "fmt"
5 | "reflect"
6 |
7 | "github.com/davecgh/go-spew/spew"
8 | "github.com/google/go-cmp/cmp"
9 | gomegatypes "github.com/onsi/gomega/types"
10 | "github.com/pkg/errors"
11 | )
12 |
13 | // MatchInlineElements a custom matcher to verify that a document matches the given expectation
14 | // Similar to the standard `Equal` matcher, but display a diff when the values don't match
15 | func MatchInlineElements(expected []interface{}) gomegatypes.GomegaMatcher {
16 | return &inlineElementsMatcher{
17 | expected: expected,
18 | }
19 | }
20 |
21 | type inlineElementsMatcher struct {
22 | expected []interface{}
23 | diffs string
24 | }
25 |
26 | func (m *inlineElementsMatcher) Match(actual interface{}) (success bool, err error) {
27 | if _, ok := actual.([]interface{}); !ok {
28 | return false, errors.Errorf("MatchInlineElements matcher expects a '[]interface{}' (actual: %T)", actual)
29 | }
30 | if !reflect.DeepEqual(m.expected, actual) {
31 | m.diffs = cmp.Diff(spew.Sdump(m.expected), spew.Sdump(actual))
32 | return false, nil
33 | }
34 | return true, nil
35 | }
36 |
37 | func (m *inlineElementsMatcher) FailureMessage(_ interface{}) (message string) {
38 | return fmt.Sprintf("expected elements to match:\n%s", m.diffs)
39 | }
40 |
41 | func (m *inlineElementsMatcher) NegatedFailureMessage(_ interface{}) (message string) {
42 | return fmt.Sprintf("expected elements not to match:\n%s", m.diffs)
43 | }
44 |
--------------------------------------------------------------------------------
/testsupport/inline_elements_matcher_test.go:
--------------------------------------------------------------------------------
1 | package testsupport_test
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/bytesparadise/libasciidoc/pkg/types"
7 | "github.com/bytesparadise/libasciidoc/testsupport"
8 | "github.com/google/go-cmp/cmp"
9 |
10 | "github.com/davecgh/go-spew/spew"
11 | . "github.com/onsi/ginkgo/v2"
12 | . "github.com/onsi/gomega"
13 | )
14 |
15 | var _ = Describe("inline elements matcher", func() {
16 |
17 | // given
18 | expected := []interface{}{
19 | &types.StringElement{
20 | Content: "a paragraph.",
21 | },
22 | }
23 | matcher := testsupport.MatchInlineElements(expected)
24 |
25 | It("should match", func() {
26 | // given
27 | actual := []interface{}{
28 | &types.StringElement{
29 | Content: "a paragraph.",
30 | },
31 | }
32 | // when
33 | result, err := matcher.Match(actual)
34 | // then
35 | Expect(err).ToNot(HaveOccurred())
36 | Expect(result).To(BeTrue())
37 | })
38 |
39 | It("should not match", func() {
40 | // given
41 | actual := []interface{}{
42 | &types.StringElement{
43 | Content: "another paragraph.",
44 | },
45 | }
46 | // when
47 | result, err := matcher.Match(actual)
48 | // then
49 | Expect(err).ToNot(HaveOccurred())
50 | Expect(result).To(BeFalse())
51 | diffs := cmp.Diff(spew.Sdump(expected), spew.Sdump(actual))
52 | Expect(matcher.FailureMessage(actual)).To(Equal(fmt.Sprintf("expected elements to match:\n%s", diffs)))
53 | Expect(matcher.NegatedFailureMessage(actual)).To(Equal(fmt.Sprintf("expected elements not to match:\n%s", diffs)))
54 | })
55 |
56 | It("should return error when invalid type is input", func() {
57 | // when
58 | result, err := matcher.Match(1)
59 | // then
60 | Expect(err).To(HaveOccurred())
61 | Expect(err.Error()).To(Equal("MatchInlineElements matcher expects a '[]interface{}' (actual: int)"))
62 | Expect(result).To(BeFalse())
63 | })
64 |
65 | })
66 |
--------------------------------------------------------------------------------
/testsupport/log_init.go:
--------------------------------------------------------------------------------
1 | package testsupport
2 |
3 | import (
4 | "flag"
5 | "os"
6 |
7 | "github.com/davecgh/go-spew/spew"
8 | log "github.com/sirupsen/logrus"
9 | "github.com/spf13/pflag"
10 | )
11 |
12 | func init() {
13 | lvl := parseLogLevel()
14 | log.SetLevel(lvl)
15 | log.Warnf("Running test with logs in '%s' level", lvl.String())
16 | log.SetFormatter(&log.TextFormatter{
17 | DisableQuote: true, // see https://github.com/sirupsen/logrus/issues/608#issuecomment-745137306
18 | })
19 |
20 | // also, configuration for spew (when dumping structures to compare results)
21 | spew.Config.DisableCapacities = true
22 | spew.Config.DisablePointerAddresses = true
23 | spew.Config.DisablePointerMethods = true
24 | spew.Config.DisableUnexported = true
25 | }
26 |
27 | func parseLogLevel() log.Level {
28 | var logLevel string
29 | // needed to let ginkgo parse the flag, otherwise, `ginkgo -- --loglevel=...` will fail with `flag provided but not defined: -loglevel`
30 | flag.StringVar(&logLevel, "loglevel", "error", "log level to set [debug|info|warn|error|fatal|panic]")
31 | // parse with a custom flagset in which all other flags (ginkgo's) are ignored
32 | f := pflag.NewFlagSet("passthroughs", pflag.ContinueOnError)
33 | f.ParseErrorsWhitelist.UnknownFlags = true
34 | f.StringVarP(&logLevel, "loglevel", "l", "error", "log level to set [debug|info|warn|error|fatal|panic]")
35 | if err := f.Parse(os.Args[1:]); err != nil {
36 | panic(err)
37 | }
38 | lvl, err := log.ParseLevel(logLevel)
39 | if err != nil {
40 | panic(err)
41 | }
42 | return lvl
43 | }
44 |
--------------------------------------------------------------------------------
/testsupport/metadata_matcher.go:
--------------------------------------------------------------------------------
1 | package testsupport
2 |
3 | import (
4 | "fmt"
5 | "reflect"
6 |
7 | "github.com/bytesparadise/libasciidoc/pkg/types"
8 | "github.com/google/go-cmp/cmp"
9 |
10 | "github.com/davecgh/go-spew/spew"
11 | . "github.com/onsi/ginkgo/v2"
12 | gomegatypes "github.com/onsi/gomega/types"
13 | "github.com/pkg/errors"
14 | )
15 |
16 | func MatchMetadata(expected types.Metadata) gomegatypes.GomegaMatcher {
17 | return &metadataMatcher{
18 | expected: expected,
19 | }
20 | }
21 |
22 | type metadataMatcher struct {
23 | expected types.Metadata
24 | diffs string
25 | }
26 |
27 | func (m *metadataMatcher) Match(actual interface{}) (success bool, err error) {
28 | if _, ok := actual.(types.Metadata); !ok {
29 | return false, errors.Errorf("MatchMetadata matcher expects a 'types.Metadata' (actual: %T)", actual)
30 | }
31 | if !reflect.DeepEqual(m.expected, actual) {
32 | GinkgoT().Logf("actual HTML:\n'%s'", actual)
33 | GinkgoT().Logf("expected HTML:\n'%s'", m.expected)
34 | m.diffs = cmp.Diff(spew.Sdump(m.expected), spew.Sdump(actual))
35 | return false, nil
36 | }
37 | return true, nil
38 | }
39 |
40 | func (m *metadataMatcher) FailureMessage(_ interface{}) (message string) {
41 | return fmt.Sprintf("expected document metadata to match:\n%s", m.diffs)
42 | }
43 |
44 | func (m *metadataMatcher) NegatedFailureMessage(_ interface{}) (message string) {
45 | return fmt.Sprintf("expected document metadata not to match:\n%s", m.diffs)
46 | }
47 |
--------------------------------------------------------------------------------
/testsupport/metadata_matcher_test.go:
--------------------------------------------------------------------------------
1 | package testsupport_test
2 |
3 | import (
4 | "fmt"
5 | "time"
6 |
7 | "github.com/bytesparadise/libasciidoc/pkg/configuration"
8 | "github.com/bytesparadise/libasciidoc/pkg/types"
9 | "github.com/bytesparadise/libasciidoc/testsupport"
10 | "github.com/davecgh/go-spew/spew"
11 | "github.com/google/go-cmp/cmp"
12 |
13 | . "github.com/onsi/ginkgo/v2"
14 | . "github.com/onsi/gomega"
15 | )
16 |
17 | var _ = Describe("metadata matcher", func() {
18 |
19 | now := time.Now()
20 |
21 | It("should match", func() {
22 | // given
23 | actual := types.Metadata{
24 | Title: "cheesecake",
25 | LastUpdated: now.Format(configuration.LastUpdatedFormat),
26 | }
27 | matcher := testsupport.MatchMetadata(types.Metadata{
28 | Title: "cheesecake",
29 | LastUpdated: now.Format(configuration.LastUpdatedFormat),
30 | }) // same content
31 | // when
32 | result, err := matcher.Match(actual)
33 | // then
34 | Expect(err).ToNot(HaveOccurred())
35 | Expect(result).To(BeTrue())
36 | })
37 |
38 | It("should not match", func() {
39 | // given
40 | actual := types.Metadata{
41 | Title: "cheesecake",
42 | LastUpdated: now.Format(configuration.LastUpdatedFormat),
43 | }
44 | expected := types.Metadata{
45 | Title: "chocolate",
46 | LastUpdated: now.Format(configuration.LastUpdatedFormat),
47 | } // not the same content
48 | matcher := testsupport.MatchMetadata(expected)
49 | // when
50 | result, err := matcher.Match(actual)
51 | // then
52 | Expect(err).ToNot(HaveOccurred())
53 | Expect(result).To(BeFalse())
54 | diffs := cmp.Diff(spew.Sdump(expected), spew.Sdump(actual))
55 | Expect(matcher.FailureMessage(actual)).To(Equal(fmt.Sprintf("expected document metadata to match:\n%s", diffs)))
56 | Expect(matcher.NegatedFailureMessage(actual)).To(Equal(fmt.Sprintf("expected document metadata not to match:\n%s", diffs)))
57 | })
58 |
59 | It("should return error when invalid type is input", func() {
60 | // given
61 | matcher := testsupport.MatchMetadata(types.Metadata{
62 | Title: "cheesecake",
63 | LastUpdated: now.Format(configuration.LastUpdatedFormat),
64 | })
65 | // when
66 | result, err := matcher.Match(1)
67 | // then
68 | Expect(err).To(HaveOccurred())
69 | Expect(err.Error()).To(Equal("MatchMetadata matcher expects a 'types.Metadata' (actual: int)"))
70 | Expect(result).To(BeFalse())
71 | })
72 | })
73 |
--------------------------------------------------------------------------------
/testsupport/parse_document.go:
--------------------------------------------------------------------------------
1 | package testsupport
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/bytesparadise/libasciidoc/pkg/configuration"
7 | "github.com/bytesparadise/libasciidoc/pkg/parser"
8 | "github.com/bytesparadise/libasciidoc/pkg/types"
9 |
10 | "github.com/pkg/errors"
11 | log "github.com/sirupsen/logrus"
12 | )
13 |
14 | // ParseDocument parses the actual value into a Document
15 | func ParseDocument(actual string, options ...interface{}) (*types.Document, error) {
16 | allSettings := []configuration.Setting{
17 | configuration.WithFilename("test.adoc"),
18 | }
19 | opts := []parser.Option{}
20 | for _, o := range options {
21 | switch o := o.(type) {
22 | case configuration.Setting:
23 | allSettings = append(allSettings, o)
24 | case parser.Option:
25 | opts = append(opts, o)
26 | default:
27 | return nil, errors.Errorf("unexpected type of option: '%T'", o)
28 | }
29 | }
30 | c := configuration.NewConfiguration(allSettings...)
31 | p, err := parser.Preprocess(strings.NewReader(actual), c, opts...)
32 | if err != nil {
33 | return nil, err
34 | }
35 | if log.IsLevelEnabled(log.DebugLevel) {
36 | log.Debugf("preparsed document:\n%s", p)
37 | }
38 | return parser.ParseDocument(strings.NewReader(p), c, opts...)
39 | }
40 |
--------------------------------------------------------------------------------
/testsupport/parse_document_fragments.go:
--------------------------------------------------------------------------------
1 | package testsupport
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/bytesparadise/libasciidoc/pkg/configuration"
7 | "github.com/bytesparadise/libasciidoc/pkg/parser"
8 | "github.com/bytesparadise/libasciidoc/pkg/types"
9 | )
10 |
11 | // ParseDocumentFragments parses the actual source with the options
12 | func ParseDocumentFragments(actual string, options ...parser.Option) ([]types.DocumentFragment, error) {
13 | r := strings.NewReader(actual)
14 | ctx := parser.NewParseContext(configuration.NewConfiguration(), options...)
15 | done := make(chan interface{})
16 | defer close(done)
17 | // ctx.Opts = append(ctx.Opts, parser.Debug(true))
18 | fragmentStream := parser.ParseDocumentFragments(ctx, r, done)
19 | result := []types.DocumentFragment{}
20 | for f := range fragmentStream {
21 | result = append(result, f)
22 | }
23 | return result, nil
24 | }
25 |
--------------------------------------------------------------------------------
/testsupport/parse_document_fragments_test.go:
--------------------------------------------------------------------------------
1 | package testsupport_test
2 |
3 | import (
4 | "github.com/bytesparadise/libasciidoc/pkg/types"
5 | "github.com/bytesparadise/libasciidoc/testsupport"
6 |
7 | . "github.com/onsi/ginkgo/v2"
8 | . "github.com/onsi/gomega"
9 | )
10 |
11 | var _ = Describe("parse document fragment groups", func() {
12 |
13 | expected := []types.DocumentFragment{
14 | {
15 | Position: types.Position{
16 | Start: 0,
17 | End: 13,
18 | },
19 | Elements: []interface{}{
20 | &types.Paragraph{
21 | Elements: []interface{}{
22 | &types.RawLine{
23 | Content: "hello, world!",
24 | },
25 | },
26 | },
27 | },
28 | },
29 | }
30 |
31 | It("should match", func() {
32 | // given
33 | actual := "hello, world!"
34 | // when
35 | result, err := testsupport.ParseDocumentFragments(actual)
36 | // then
37 | Expect(err).ToNot(HaveOccurred())
38 | Expect(result).To(Equal(expected))
39 | })
40 |
41 | It("should not match", func() {
42 | // given
43 | actual := "foo"
44 | // when
45 | result, err := testsupport.ParseDocumentFragments(actual)
46 | // then
47 | Expect(err).ToNot(HaveOccurred())
48 | Expect(result).NotTo(Equal(expected))
49 | })
50 | })
51 |
--------------------------------------------------------------------------------
/testsupport/parse_document_test.go:
--------------------------------------------------------------------------------
1 | package testsupport_test
2 |
3 | import (
4 | "github.com/bytesparadise/libasciidoc/pkg/types"
5 | "github.com/bytesparadise/libasciidoc/testsupport"
6 |
7 | . "github.com/onsi/ginkgo/v2"
8 | . "github.com/onsi/gomega"
9 | )
10 |
11 | var _ = Describe("parse document", func() {
12 |
13 | expected := &types.Document{
14 | Elements: []interface{}{
15 | &types.Paragraph{
16 | Elements: []interface{}{
17 | &types.StringElement{
18 | Content: "hello, world!",
19 | },
20 | },
21 | },
22 | },
23 | }
24 |
25 | It("should match", func() {
26 | // given
27 | actual := "hello, world!"
28 | // when
29 | result, err := testsupport.ParseDocument(actual)
30 | // then
31 | Expect(err).ToNot(HaveOccurred())
32 | Expect(result).To(Equal(expected))
33 | })
34 |
35 | It("should not match", func() {
36 | // given
37 | actual := "foo"
38 | // when
39 | result, err := testsupport.ParseDocument(actual)
40 | // then
41 | Expect(err).ToNot(HaveOccurred())
42 | Expect(result).NotTo(Equal(expected))
43 | })
44 |
45 | })
46 |
--------------------------------------------------------------------------------
/testsupport/preparse_document.go:
--------------------------------------------------------------------------------
1 | package testsupport
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/bytesparadise/libasciidoc/pkg/configuration"
7 | "github.com/bytesparadise/libasciidoc/pkg/parser"
8 | log "github.com/sirupsen/logrus"
9 |
10 | "github.com/pkg/errors"
11 | )
12 |
13 | func PreparseDocument(source string, options ...interface{}) (string, error) {
14 | settings := []configuration.Setting{
15 | configuration.WithFilename("test.adoc"),
16 | configuration.WithAttribute("basebackend-html", true), // TODO: still needed?
17 | }
18 | opts := []parser.Option{}
19 | for _, o := range options {
20 | switch o := o.(type) {
21 | case configuration.Setting:
22 | settings = append(settings, o)
23 | case parser.Option:
24 | opts = append(opts, o)
25 | default:
26 | return "", errors.Errorf("unexpected type of option: '%T'", o)
27 | }
28 | }
29 | result, err := parser.Preprocess(strings.NewReader(source), configuration.NewConfiguration(settings...), opts...)
30 | if log.IsLevelEnabled(log.DebugLevel) && err == nil {
31 | log.Debugf("preparsed document:\n%s", result)
32 | }
33 | return result, err
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/testsupport/render_html5.go:
--------------------------------------------------------------------------------
1 | package testsupport
2 |
3 | import (
4 | "bytes"
5 | "os"
6 | "strings"
7 |
8 | "github.com/bytesparadise/libasciidoc"
9 | "github.com/bytesparadise/libasciidoc/pkg/configuration"
10 | "github.com/bytesparadise/libasciidoc/pkg/types"
11 | log "github.com/sirupsen/logrus"
12 | )
13 |
14 | // RenderHTML renders the HTML body using the given source
15 | func RenderHTML(actual string, settings ...configuration.Setting) (string, error) {
16 | output, _, err := RenderHTMLWithMetadata(actual, settings...)
17 | return output, err
18 | }
19 |
20 | // RenderHTML renders the HTML body using the given source
21 | func RenderHTMLWithMetadata(actual string, settings ...configuration.Setting) (string, types.Metadata, error) {
22 | allSettings := append([]configuration.Setting{configuration.WithFilename("test.adoc"), configuration.WithBackEnd("html5")}, settings...)
23 | config := configuration.NewConfiguration(allSettings...)
24 | contentReader := strings.NewReader(actual)
25 | resultWriter := bytes.NewBuffer(nil)
26 | metadata, err := libasciidoc.Convert(contentReader, resultWriter, config)
27 | if err != nil {
28 | log.Error(err)
29 | return "", types.Metadata{}, err
30 | }
31 | if log.IsLevelEnabled(log.DebugLevel) {
32 | log.Debug(resultWriter.String())
33 | }
34 | return resultWriter.String(), metadata, nil
35 | }
36 |
37 | // RenderHTML renders the HTML body using the given source
38 | func RenderHTMLFromFile(filename string, settings ...configuration.Setting) (string, types.Metadata, error) {
39 | info, err := os.Stat(filename)
40 | if err != nil {
41 | log.Error(err)
42 | return "", types.Metadata{}, err
43 | }
44 |
45 | allSettings := append([]configuration.Setting{
46 | configuration.WithLastUpdated(info.ModTime()),
47 | configuration.WithFilename(filename),
48 | configuration.WithBackEnd("html5")},
49 | settings...)
50 | config := configuration.NewConfiguration(allSettings...)
51 | f, err := os.Open(filename)
52 | if err != nil {
53 | log.Error(err)
54 | return "", types.Metadata{}, err
55 | }
56 | defer func() { f.Close() }()
57 | resultWriter := bytes.NewBuffer(nil)
58 | metadata, err := libasciidoc.Convert(f, resultWriter, config)
59 | if err != nil {
60 | log.Error(err)
61 | return "", types.Metadata{}, err
62 | }
63 | if log.IsLevelEnabled(log.DebugLevel) {
64 | log.Debug(resultWriter.String())
65 | }
66 | return resultWriter.String(), metadata, nil
67 | }
68 |
--------------------------------------------------------------------------------
/testsupport/render_html5_test.go:
--------------------------------------------------------------------------------
1 | package testsupport_test
2 |
3 | import (
4 | "github.com/bytesparadise/libasciidoc/testsupport"
5 |
6 | . "github.com/onsi/ginkgo/v2"
7 | . "github.com/onsi/gomega"
8 | )
9 |
10 | var _ = Describe("html5 body renderer", func() {
11 |
12 | It("should match", func() {
13 | // given
14 | actual := "hello, world!"
15 | // when
16 | result, err := testsupport.RenderHTML(actual)
17 | // then
18 | expected := `
21 | `
22 | Expect(err).NotTo(HaveOccurred())
23 | Expect(result).To(Equal(expected))
24 | })
25 | })
26 |
--------------------------------------------------------------------------------
/testsupport/table_of_contents_matcher.go:
--------------------------------------------------------------------------------
1 | package testsupport
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/bytesparadise/libasciidoc/pkg/types"
7 | "github.com/davecgh/go-spew/spew"
8 | "github.com/google/go-cmp/cmp"
9 | gomegatypes "github.com/onsi/gomega/types"
10 | "github.com/pkg/errors"
11 | log "github.com/sirupsen/logrus"
12 | )
13 |
14 | // MatchTableOfContents a custom matcher to verify that a TableOfContents matches the given expectation
15 | // Similar to the standard `Equal` matcher, but display a diff when the values don't match
16 | func MatchTableOfContents(expected *types.TableOfContents) gomegatypes.GomegaMatcher {
17 | return &tableOfContentsMatcher{
18 | expected: expected,
19 | }
20 | }
21 |
22 | type tableOfContentsMatcher struct {
23 | expected *types.TableOfContents
24 | diffs string
25 | }
26 |
27 | func (m *tableOfContentsMatcher) Match(actual interface{}) (success bool, err error) {
28 | if _, ok := actual.(*types.TableOfContents); !ok {
29 | return false, errors.Errorf("MatchDocumentFragment matcher expects a '*types.TableOfContents' (actual: %T)", actual)
30 | }
31 | if diff := cmp.Diff(m.expected, actual, opts...); diff != "" {
32 | if log.IsLevelEnabled(log.DebugLevel) {
33 | log.Debugf("actual table of contents:\n%s", spew.Sdump(actual))
34 | log.Debugf("expected table of contents:\n%s", spew.Sdump(m.expected))
35 | }
36 | m.diffs = diff
37 | return false, nil
38 | }
39 | return true, nil
40 | }
41 |
42 | func (m *tableOfContentsMatcher) FailureMessage(_ interface{}) (message string) {
43 | return fmt.Sprintf("expected table of contents to match:\n%s", m.diffs)
44 | }
45 |
46 | func (m *tableOfContentsMatcher) NegatedFailureMessage(_ interface{}) (message string) {
47 | return fmt.Sprintf("expected table of contents not to match:\n%s", m.diffs)
48 | }
49 |
--------------------------------------------------------------------------------
/testsupport/table_of_contents_matcher_test.go:
--------------------------------------------------------------------------------
1 | package testsupport_test
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/bytesparadise/libasciidoc/pkg/types"
7 | "github.com/bytesparadise/libasciidoc/testsupport"
8 | "github.com/google/go-cmp/cmp"
9 | "github.com/google/go-cmp/cmp/cmpopts"
10 |
11 | . "github.com/onsi/ginkgo/v2"
12 | . "github.com/onsi/gomega"
13 | )
14 |
15 | var _ = Describe("document fragments matcher", func() {
16 |
17 | // given
18 | expected := &types.TableOfContents{
19 | Sections: []*types.ToCSection{
20 | {
21 | Title: "root",
22 | },
23 | },
24 | }
25 | matcher := testsupport.MatchTableOfContents(expected)
26 |
27 | It("should match", func() {
28 | // given
29 | actual := &types.TableOfContents{
30 | Sections: []*types.ToCSection{
31 | {
32 | Title: "root",
33 | },
34 | },
35 | }
36 | // when
37 | result, err := matcher.Match(actual)
38 | // then
39 | Expect(err).ToNot(HaveOccurred())
40 | Expect(result).To(BeTrue())
41 | })
42 |
43 | It("should not match", func() {
44 | // given
45 | actual := &types.TableOfContents{
46 | Sections: []*types.ToCSection{
47 | {
48 | Title: "something else",
49 | },
50 | },
51 | }
52 | // when
53 | result, err := matcher.Match(actual)
54 | // then
55 | Expect(err).ToNot(HaveOccurred())
56 | Expect(result).To(BeFalse())
57 | diffs := cmp.Diff(expected, actual, cmpopts.IgnoreUnexported(types.TableOfContents{}))
58 | Expect(matcher.FailureMessage(actual)).To(Equal(fmt.Sprintf("expected table of contents to match:\n%s", diffs)))
59 | Expect(matcher.NegatedFailureMessage(actual)).To(Equal(fmt.Sprintf("expected table of contents not to match:\n%s", diffs)))
60 | })
61 |
62 | It("should return error when invalid type is input", func() {
63 | // when
64 | result, err := matcher.Match(1)
65 | // then
66 | Expect(err).To(HaveOccurred())
67 | Expect(err.Error()).To(Equal("MatchDocumentFragment matcher expects a '*types.TableOfContents' (actual: int)"))
68 | Expect(result).To(BeFalse())
69 | })
70 |
71 | })
72 |
--------------------------------------------------------------------------------
/testsupport/testsupport_suite_test.go:
--------------------------------------------------------------------------------
1 | package testsupport_test
2 |
3 | import (
4 | "testing"
5 |
6 | _ "github.com/bytesparadise/libasciidoc/testsupport"
7 |
8 | . "github.com/onsi/ginkgo/v2"
9 | . "github.com/onsi/gomega"
10 | )
11 |
12 | func TestTestsupport(t *testing.T) {
13 | RegisterFailHandler(Fail)
14 | RunSpecs(t, "Testsupport Suite")
15 | }
16 |
--------------------------------------------------------------------------------
/tools.go:
--------------------------------------------------------------------------------
1 | //+build tools
2 |
3 | package libasciidoc
4 |
5 | import (
6 | _ "github.com/mna/pigeon"
7 | _ "github.com/onsi/ginkgo/v2/ginkgo"
8 | )
9 |
--------------------------------------------------------------------------------