pax_global_header 0000666 0000000 0000000 00000000064 14531042673 0014517 g ustar 00root root 0000000 0000000 52 comment=25edb1eb96afff1def09f847387ab643eaf57e01
earmark_parser-1.4.39/ 0000775 0000000 0000000 00000000000 14531042673 0014613 5 ustar 00root root 0000000 0000000 earmark_parser-1.4.39/.github/ 0000775 0000000 0000000 00000000000 14531042673 0016153 5 ustar 00root root 0000000 0000000 earmark_parser-1.4.39/.github/workflows/ 0000775 0000000 0000000 00000000000 14531042673 0020210 5 ustar 00root root 0000000 0000000 earmark_parser-1.4.39/.github/workflows/elixir.yml 0000664 0000000 0000000 00000003332 14531042673 0022230 0 ustar 00root root 0000000 0000000 name: Elixir CI
on:
pull_request:
push:
branches:
- master
jobs:
mix_test_old:
name: mix test (Elixir ${{matrix.elixir}} | OTP ${{matrix.otp}})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- elixir: 1.11.x
otp: 22
os: ubuntu-20.04
- elixir: 1.11.x
otp: 23
os: ubuntu-20.04
- elixir: 1.12.x
otp: 23
os: ubuntu-20.04
- elixir: 1.13.x
otp: 24
os: ubuntu-20.04
- elixir: 1.14.x
otp: 25
os: ubuntu-latest
# - elixir: 1.15.x
# otp: 26
# os: ubuntu-latest
# warnings_as_errors: true
env:
MIX_ENV: test
steps:
- uses: actions/checkout@v3
- uses: erlef/setup-beam@v1
with:
otp-version: ${{matrix.otp}}
elixir-version: ${{matrix.elixir}}
- name: Install Dependencies
run: |
mix local.hex --force
mix local.rebar --force
mix deps.get --only test
- name: Cache build artifacts
uses: actions/cache@v3
with:
path: |
~/.hex
~/.mix
_build
key: ${{ matrix.otp }}-${{ matrix.elixir }}-build
- run: mix compile --warnings-as-errors
if: matrix.warnings_as_errors
env:
CC: gcc-10
CXX: g++-10
- run: mix test
env:
CC: gcc-10
CXX: g++-10
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
earmark_parser-1.4.39/.gitignore 0000664 0000000 0000000 00000000341 14531042673 0016601 0 ustar 00root root 0000000 0000000 /_build
/deps
erl_crash.dump
*.ez
tmp/*
.tool-versions
.mix_tasks
notes/*
/earmark
*.beam
doc/
# yecc, leex generated files
src/*.erl
cover
lib/tools
#*#
/cover/
/doc/
/.fetch
*.ez
earmark_parser-*.tar
.vscode/
test/debug/
earmark_parser-1.4.39/DEVNOTES.md 0000664 0000000 0000000 00000001216 14531042673 0016364 0 ustar 00root root 0000000 0000000 # TODOS
## Release 1.5.0
### For Lists
- Change `Block.List.blocks` to `Block.List.list_items`
- Remove `Block.List.lines`
- Remove `Block.List.spaced?`
- Refactor parser into a `ParagraphParser`?
# Definitions
## [Paragraph Continuation Text](https://github.github.com/gfm/#paragraph-continuation-text)
...is text that will be parsed as part of the content of a paragraph, but does not occur at the beginning of the paragraph.
Example:
```markdown
This is **not** a PCT
this is a PCT
```
## [Thematic Break](https://github.github.com/gfm/#thematic-breaks)
just what we scan as `Line.Ruler`
earmark_parser-1.4.39/LICENSE 0000664 0000000 0000000 00000026135 14531042673 0015627 0 ustar 00root root 0000000 0000000 Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
earmark_parser-1.4.39/README.md 0000664 0000000 0000000 00000056767 14531042673 0016117 0 ustar 00root root 0000000 0000000
# EarmarkParser A Pure Elixir Markdown Parser
[](https://github.com/robertdober/earmark_parser/actions/workflows/elixir.yml)
[](https://coveralls.io/github/RobertDober/earmark_parser?branch=master)
[](https://hex.pm/packages/earmark_parser)
[](https://hex.pm/packages/earmark_parser)
[](https://hex.pm/packages/earmark_parser)
## Table Of Contents
- [Table Of Contents](#table-of-contents)
- [Usage](#usage)
- [EarmarkParser](#earmarkparser)
- [API](#api)
- [EarmarkParser.as_ast](#earmarkparseras_ast)
- [Options](#options)
- [Supports](#supports)
- [Extensions](#extensions)
- [Links](#links)
- [Links supported by default](#links-supported-by-default)
- [Autolinks](#autolinks)
- [Additional link parsing via options](#additional-link-parsing-via-options)
- [Pure links](#pure-links)
- [Wikilinks...](#wikilinks)
- [Sub and Sup HTML Elements](#sub-and-sup-html-elements)
- [Mathematical expressions](#mathematical-expressions)
- [Github Flavored Markdown](#github-flavored-markdown)
- [Strike Through](#strike-through)
- [GFM Tables](#gfm-tables)
- [Syntax Highlighting](#syntax-highlighting)
- [Footnotes](#footnotes)
- [Breaks](#breaks)
- [Enabling **all** options that are disabled by default](#enabling-all-options-that-are-disabled-by-default)
- [Tables](#tables)
- [HTML Blocks](#html-blocks)
- [HTML Comments](#html-comments)
- [Lists](#lists)
- [Adding Attributes with the IAL extension](#adding-attributes-with-the-ial-extension)
- [To block elements](#to-block-elements)
- [To links or images](#to-links-or-images)
- [Limitations](#limitations)
- [Annotations](#annotations)
- [Annotated Paragraphs](#annotated-paragraphs)
- [Annotated HTML elements](#annotated-html-elements)
- [Commenting your Markdown](#commenting-your-markdown)
- [EarmarkParser.as_ast/2](#earmarkparseras_ast2)
- [EarmarkParser.version/0](#earmarkparserversion0)
- [Contributing](#contributing)
- [Author](#author)
- [LICENSE](#license)
## Usage
### EarmarkParser
### API
#### EarmarkParser.as_ast
This is the structure of the result of `as_ast`.
{:ok, ast, []} = EarmarkParser.as_ast(markdown)
{:ok, ast, deprecation_messages} = EarmarkParser.as_ast(markdown)
{:error, ast, error_messages} = EarmarkParser.as_ast(markdown)
For examples see the functiondoc below.
#### Options
Options can be passed into `as_ast/2` according to the documentation of `EarmarkParser.Options`.
{status, ast, errors} = EarmarkParser.as_ast(markdown, options)
## Supports
Standard [Gruber markdown][gruber].
[gruber]:
## Extensions
### Links
#### Links supported by default
##### Oneline HTML Link tags
```elixir
iex(1)> EarmarkParser.as_ast(~s{link})
{:ok, [{"a", [{"href", "href"}], ["link"], %{verbatim: true}}], []}
```
##### Markdown links
New style ...
```elixir
iex(2)> EarmarkParser.as_ast(~s{[title](destination)})
{:ok, [{"p", [], [{"a", [{"href", "destination"}], ["title"], %{}}], %{}}], []}
```
and old style
```elixir
iex(3)> EarmarkParser.as_ast("[foo]: /url \"title\"\n\n[foo]\n")
{:ok, [{"p", [], [{"a", [{"href", "/url"}, {"title", "title"}], ["foo"], %{}}], %{}}], []}
```
#### Autolinks
```elixir
iex(4)> EarmarkParser.as_ast("")
{:ok, [{"p", [], [{"a", [{"href", "https://elixir-lang.com"}], ["https://elixir-lang.com"], %{}}], %{}}], []}
```
#### Additional link parsing via options
#### Pure links
**N.B.** that the `pure_links` option is `true` by default
```elixir
iex(5)> EarmarkParser.as_ast("https://github.com")
{:ok, [{"p", [], [{"a", [{"href", "https://github.com"}], ["https://github.com"], %{}}], %{}}], []}
```
But can be deactivated
```elixir
iex(6)> EarmarkParser.as_ast("https://github.com", pure_links: false)
{:ok, [{"p", [], ["https://github.com"], %{}}], []}
```
#### Wikilinks...
are disabled by default
```elixir
iex(7)> EarmarkParser.as_ast("[[page]]")
{:ok, [{"p", [], ["[[page]]"], %{}}], []}
```
and can be enabled
```elixir
iex(8)> EarmarkParser.as_ast("[[page]]", wikilinks: true)
{:ok, [{"p", [], [{"a", [{"href", "page"}], ["page"], %{wikilink: true}}], %{}}], []}
```
### Sub and Sup HTML Elements
This feature is not enabled by default but can be enabled with the option `sub_sup: true`
Therefore we will get
```elixir
iex(9)> EarmarkParser.as_ast("H~2~O or a^n^ + b^n^ = c^n^")
{:ok, [{"p", [], ["H~2~O or a^n^ + b^n^ = c^n^"], %{}}], []}
```
But by specifying `sub_sup: true`
```elixir
iex(10)> EarmarkParser.as_ast("H~2~O or a^n^ + b^n^ = c^n^", sub_sup: true)
{:ok, [{"p", [], ["H", {"sub", [], ["2"], %{}}, "O or a", {"sup", [], ["n"], %{}}, " + b", {"sup", [], ["n"], %{}}, " = c", {"sup", [], ["n"], %{}}], %{}}], []}
```
### Mathematical expressions
> Note: math syntax within Markdown is not standardized, so this option is a subject to change in future releases.
This feature is not enabled by default but can be enabled with the option `math: true`.
When enabled, LaTeX formatted math can be written within Markdown. For more information, see [LaTeX/Mathematics](https://en.wikibooks.org/wiki/LaTeX/Mathematics) in Wikibooks.
#### Inline expressions
Inline-style expression can be written by surrounding the expression with dollar symbols (`$`).
```elixir
iex> EarmarkParser.as_ast("$x = 1$", math: true)
{:ok, [{"p", [], [{"code", [{"class", "math-inline"}], ["x = 1"], %{line: 1}}], %{}}], []}
```
There must be no space between `$` and the surrounded expression. If you want to use a dollar sign in the same line as a math expression, you can escape the dollar with backslash (`\\$`).
#### Expressions as blocks
Display-style expression can be written by surrounding the expression with two dollar signs (`$$`).
```elixir
iex> EarmarkParser.as_ast("$$x = 1$$", math: true)
{:ok, [{"p", [], [{"code", [{"class", "math-display"}], ["x = 1"], %{line: 1}}], %{}}], []}
```
### Github Flavored Markdown
GFM is supported by default, however as GFM is a moving target and all GFM extension do not make sense in a general context, EarmarkParser does not support all of it, here is a list of what is supported:
#### Strike Through
```elixir
iex(11)> EarmarkParser.as_ast("~~hello~~")
{:ok, [{"p", [], [{"del", [], ["hello"], %{}}], %{}}], []}
```
#### GFM Tables
Are not enabled by default
```elixir
iex(12)> as_ast("a|b\\n-|-\\nc|d\\n")
{:ok, [{"p", [], ["a|b\\n-|-\\nc|d\\n"], %{}}], []}
```
But can be enabled with `gfm_tables: true`
```elixir
iex(13)> as_ast("a|b\n-|-\nc|d\n", gfm_tables: true)
{:ok,
[
{
"table",
[],
[
{"thead", [], [{"tr", [], [{"th", [{"style", "text-align: left;"}], ["a"], %{}}, {"th", [{"style", "text-align: left;"}], ["b"], %{}}], %{}}], %{}},
{"tbody", [], [{"tr", [], [{"td", [{"style", "text-align: left;"}], ["c"], %{}}, {"td", [{"style", "text-align: left;"}], ["d"], %{}}], %{}}], %{}}
],
%{}
}
],
[]}
```
#### Syntax Highlighting
All backquoted or fenced code blocks with a language string are rendered with the given
language as a _class_ attribute of the _code_ tag.
For example:
```elixir
iex(14)> [
...(14)> "```elixir",
...(14)> " @tag :hello",
...(14)> "```"
...(14)> ] |> as_ast()
{:ok, [{"pre", [], [{"code", [{"class", "elixir"}], [" @tag :hello"], %{}}], %{}}], []}
```
will be rendered as shown in the doctest above.
If you want to integrate with a syntax highlighter with different conventions you can add more classes by specifying prefixes that will be
put before the language string.
Prism.js for example needs a class `language-elixir`. In order to achieve that goal you can add `language-`
as a `code_class_prefix` to `EarmarkParser.Options`.
In the following example we want more than one additional class, so we add more prefixes.
```elixir
iex(15)> [
...(15)> "```elixir",
...(15)> " @tag :hello",
...(15)> "```"
...(15)> ] |> as_ast(%EarmarkParser.Options{code_class_prefix: "lang- language-"})
{:ok, [{"pre", [], [{"code", [{"class", "elixir lang-elixir language-elixir"}], [" @tag :hello"], %{}}], %{}}], []}
```
#### Footnotes
**N.B.** Footnotes are disabled by default, use `as_ast(..., footnotes: true)` to enable them
Footnotes are now a **superset** of GFM Footnotes. This implies some changes
- Footnote definitions (`[^footnote_id]`) must come at the end of your document (_GFM_)
- Footnotes that are not referenced are not rendered anymore (_GFM_)
- Footnote definitions can contain any markup with the exception of footnote definitions
# iex(16)> markdown = [
# ...(16)> "My reference[^to_footnote]",
# ...(16)> "",
# ...(16)> "[^1]: I am not rendered",
# ...(16)> "[^to_footnote]: Important information"]
# ...(16)> {:ok, ast, []} = as_ast(markdown, footnotes: true)
# ...(16)> ast
# [
# {"p", [], ["My reference",
# {"a",
# [{"href", "#fn:to_footnote"}, {"id", "fnref:to_footnote"}, {"class", "footnote"}, {"title", "see footnote"}],
# ["to_footnote"], %{}}
# ], %{}},
# {"div",
# [{"class", "footnotes"}],
# [{"hr", [], [], %{}},
# {"ol", [],
# [{"li", [{"id", "fn:to_footnote"}],
# [{"a", [{"title", "return to article"}, {"class", "reversefootnote"}, {"href", "#fnref:to_footnote"}], ["↩"], %{}},
# {"p", [], ["Important information"], %{}}], %{}}
# ], %{}}], %{}}
# ]
For more complex examples of footnotes, please refer to
[these tests](https://github.com/RobertDober/earmark_parser/tree/master/test/acceptance/ast/footnotes/multiple_fn_test.exs)
#### Breaks
Hard linebreaks are disabled by default
```elixir
iex(17)> ["* a"," b", "c"]
...(17)> |> as_ast()
{:ok,
[{"ul", [], [{"li", [], ["a\nb\nc"], %{}}], %{}}],
[]}
```
But can be enabled with `breaks: true`
```elixir
iex(18)> ["* a"," b", "c"]
...(18)> |> as_ast(breaks: true)
{:ok, [{"ul", [], [{"li", [], ["a", {"br", [], [], %{}}, "b", {"br", [], [], %{}}, "c"], %{}}], %{}}], []}
```
#### Enabling **all** options that are disabled by default
Can be achieved with the `all: true` option
```elixir
iex(19)> [
...(19)> "a^n^",
...(19)> "b~2~",
...(19)> "[[wikilink]]"]
...(19)> |> as_ast(all: true)
{:ok, [
{"p", [], ["a", {"sup", [], ["n"], %{}}, {"br", [], [], %{}}, "b", {"sub", [], ["2"], %{}}, {"br", [], [], %{}}, {"a", [{"href", "wikilink"}], ["wikilink"], %{wikilink: true}}], %{}}
],
[]}
```
#### Tables
Are supported as long as they are preceded by an empty line.
State | Abbrev | Capital
----: | :----: | -------
Texas | TX | Austin
Maine | ME | Augusta
Tables may have leading and trailing vertical bars on each line
| State | Abbrev | Capital |
| ----: | :----: | ------- |
| Texas | TX | Austin |
| Maine | ME | Augusta |
Tables need not have headers, in which case all column alignments
default to left.
| Texas | TX | Austin |
| Maine | ME | Augusta |
Currently we assume there are always spaces around interior vertical unless
there are exterior bars.
However in order to be more GFM compatible the `gfm_tables: true` option
can be used to interpret only interior vertical bars as a table if a separation
line is given, therefore
Language|Rating
--------|------
Elixir | awesome
is a table (if and only if `gfm_tables: true`) while
Language|Rating
Elixir | awesome
never is.
#### HTML Blocks
HTML is not parsed recursively or detected in all conditions right now, though GFM compliance
is a goal.
But for now the following holds:
A HTML Block defined by a tag starting a line and the same tag starting a different line is parsed
as one HTML AST node, marked with %{verbatim: true}
E.g.
```elixir
iex(20)> lines = [ "", "some", "
more text" ]
...(20)> EarmarkParser.as_ast(lines)
{:ok, [{"div", [], ["", "some"], %{verbatim: true}}, "more text"], []}
```
And a line starting with an opening tag and ending with the corresponding closing tag is parsed in similar
fashion
```elixir
iex(21)> EarmarkParser.as_ast(["spaniel"])
{:ok, [{"span", [{"class", "superspan"}], ["spaniel"], %{verbatim: true}}], []}
```
What is HTML?
We differ from strict GFM by allowing **all** tags not only HTML5 tags this holds for one liners....
```elixir
iex(22)> {:ok, ast, []} = EarmarkParser.as_ast(["", "better"])
...(22)> ast
[
{"stupid", [], [], %{verbatim: true}},
{"not", [], ["better"], %{verbatim: true}}]
```
and for multi line blocks
```elixir
iex(23)> {:ok, ast, []} = EarmarkParser.as_ast([ "", "world", ""])
...(23)> ast
[{"hello", [], ["world"], %{verbatim: true}}]
```
#### HTML Comments
Are recognized if they start a line (after ws and are parsed until the next `-->` is found
all text after the next '-->' is ignored
E.g.
```elixir
iex(24)> EarmarkParser.as_ast(" text -->\nafter")
{:ok, [{:comment, [], [" Comment", "comment line", "comment "], %{comment: true}}, {"p", [], ["after"], %{}}], []}
```
#### Lists
Lists are pretty much GFM compliant, but some behaviors concerning the interpreation of the markdown inside a List Item's first
paragraph seem not worth to be interpreted, examples are blockquote in a tight [list item](ttps://babelmark.github.io/?text=*+aa%0A++%3E+Second)
which we can only have in a [loose one](https://babelmark.github.io/?text=*+aa%0A++%0A++%3E+Second)
Or a headline in a [tight list item](https://babelmark.github.io/?text=*+bb%0A++%23+Headline) which, again is only available in the
[loose version](https://babelmark.github.io/?text=*+bb%0A%0A++%23+Headline) in EarmarkParser.
furthermore [this example](https://babelmark.github.io/?text=*+aa%0A++%60%60%60%0ASecond%0A++%60%60%60) demonstrates how weird
and definitely not useful GFM's own interpretation can get.
Therefore we stick to a more predictable approach.
```elixir
iex(25)> markdown = [
...(25)> "* aa",
...(25)> " ```",
...(25)> "Second",
...(25)> " ```" ]
...(25)> as_ast(markdown)
{:ok, [{"ul", [], [{"li", [], ["aa", {"pre", [], [{"code", [], ["Second"], %{}}], %{}}], %{}}], %{}}], []}
```
Also we do support the immediate style of block content inside lists
```elixir
iex(26)> as_ast("* > Nota Bene!")
{:ok, [{"ul", [], [{"li", [], [{"blockquote", [], [{"p", [], ["Nota Bene!"], %{}}], %{}}], %{}}], %{}}], []}
```
or
```elixir
iex(27)> as_ast("1. # Breaking...")
{:ok, [{"ol", [], [{"li", [], [{"h1", [], ["Breaking..."], %{}}], %{}}], %{}}], []}
```
### Adding Attributes with the IAL extension
#### To block elements
HTML attributes can be added to any block-level element. We use
the Kramdown syntax: add the line `{:` _attrs_ `}` following the block.
```elixir
iex(28)> markdown = ["# Headline", "{:.from-next-line}"]
...(28)> as_ast(markdown)
{:ok, [{"h1", [{"class", "from-next-line"}], ["Headline"], %{}}], []}
```
Headers can also have the IAL string at the end of the line
```elixir
iex(29)> markdown = ["# Headline{:.from-same-line}"]
...(29)> as_ast(markdown)
{:ok, [{"h1", [{"class", "from-same-line"}], ["Headline"], %{}}], []}
```
A special use case is headers inside blockquotes which allow for some nifty styling in `ex_doc`*
see [this PR](https://github.com/elixir-lang/ex_doc/pull/1400) if you are interested in the technical
details
```elixir
iex(30)> markdown = ["> # Headline{:.warning}"]
...(30)> as_ast(markdown)
{:ok, [{"blockquote", [], [{"h1", [{"class", "warning"}], ["Headline"], %{}}], %{}}], []}
```
This also works for headers inside lists
```elixir
iex(31)> markdown = ["- # Headline{:.warning}"]
...(31)> as_ast(markdown)
{:ok, [{"ul", [], [{"li", [], [{"h1", [{"class", "warning"}], ["Headline"], %{}}], %{}}], %{}}], []}
```
It still works for inline code, as it did before
```elixir
iex(32)> markdown = "`Enum.map`{:lang=elixir}"
...(32)> as_ast(markdown)
{:ok, [{"p", [], [{"code", [{"class", "inline"}, {"lang", "elixir"}], ["Enum.map"], %{}}], %{}}], []}
```
_attrs_ can be one or more of:
* `.className`
* `#id`
* name=value, name="value", or name='value'
For example:
# Warning
{: .red}
Do not turn off the engine
if you are at altitude.
{: .boxed #warning spellcheck="true"}
#### To links or images
It is possible to add IAL attributes to generated links or images in the following
format.
```elixir
iex(33)> markdown = "[link](url) {: .classy}"
...(33)> EarmarkParser.as_ast(markdown)
{ :ok, [{"p", [], [{"a", [{"class", "classy"}, {"href", "url"}], ["link"], %{}}], %{}}], []}
```
For both cases, malformed attributes are ignored and warnings are issued.
```elixir
iex(34)> [ "Some text", "{:hello}" ] |> Enum.join("\n") |> EarmarkParser.as_ast()
{:error, [{"p", [], ["Some text"], %{}}], [{:warning, 2,"Illegal attributes [\"hello\"] ignored in IAL"}]}
```
It is possible to escape the IAL in both forms if necessary
```elixir
iex(35)> markdown = "[link](url)\\{: .classy}"
...(35)> EarmarkParser.as_ast(markdown)
{:ok, [{"p", [], [{"a", [{"href", "url"}], ["link"], %{}}, "{: .classy}"], %{}}], []}
```
This of course is not necessary in code blocks or text lines
containing an IAL-like string, as in the following example
```elixir
iex(36)> markdown = "hello {:world}"
...(36)> EarmarkParser.as_ast(markdown)
{:ok, [{"p", [], ["hello {:world}"], %{}}], []}
```
## Limitations
* Block-level HTML is correctly handled only if each HTML
tag appears on its own line. So
will work. However. the following won't
hello
* John Gruber's tests contain an ambiguity when it comes to
lines that might be the start of a list inside paragraphs.
One test says that
This is the text
* of a paragraph
that I wrote
is a single paragraph. The "*" is not significant. However, another
test has
* A list item
* an another
and expects this to be a nested list. But, in reality, the second could just
be the continuation of a paragraph.
I've chosen always to use the second interpretation—a line that looks like
a list item will always be a list item.
* Rendering of block and inline elements.
Block or void HTML elements that are at the absolute beginning of a line end
the preceding paragraph.
Thusly
mypara
Becomes
mypara
While
mypara
will be transformed into
mypara
## Annotations
**N.B.** this is an experimental feature from v1.4.16-pre on and might change or be removed again
The idea is that each markdown line can be annotated, as such annotations change the semantics of Markdown
they have to be enabled with the `annotations` option.
If the `annotations` option is set to a string (only one string is supported right now, but a list might
be implemented later on, hence the name), the last occurrence of that string in a line and all text following
it will be added to the line as an annotation.
Depending on how that line will eventually be parsed, this annotation will be added to the meta map (the 4th element
in an AST quadruple) with the key `:annotation`
In the current version the annotation will only be applied to verbatim HTML tags and paragraphs
Let us show some examples now:
### Annotated Paragraphs
```elixir
iex(37)> as_ast("hello %> annotated", annotations: "%>")
{:ok, [{"p", [], ["hello "], %{annotation: "%> annotated"}}], []}
```
If we annotate more than one line in a para the first annotation takes precedence
```elixir
iex(38)> as_ast("hello %> annotated\nworld %> discarded", annotations: "%>")
{:ok, [{"p", [], ["hello \nworld "], %{annotation: "%> annotated"}}], []}
```
### Annotated HTML elements
In one line
```elixir
iex(39)> as_ast(" --> do not go there"
...(41)> ] |> as_ast(annotations: "-->")
{:ok, [
{"h1", [], ["Headline"], %{}},
{"ul", [], [{"li", [], ["item1 "], %{}}, {"li", [], ["item2 "], %{}}], %{}},
{"p", [], [{"a", [{"href", "http://somewhere/to/go"}], ["http://somewhere/to/go"], %{}}, " "], %{annotation: "--> do not go there"}}
], []
}
```
### EarmarkParser.as_ast/2
iex(42)> markdown = "My `code` is **best**"
...(42)> {:ok, ast, []} = EarmarkParser.as_ast(markdown)
...(42)> ast
[{"p", [], ["My ", {"code", [{"class", "inline"}], ["code"], %{}}, " is ", {"strong", [], ["best"], %{}}], %{}}]
```elixir
iex(43)> markdown = "```elixir\nIO.puts 42\n```"
...(43)> {:ok, ast, []} = EarmarkParser.as_ast(markdown, code_class_prefix: "lang-")
...(43)> ast
[{"pre", [], [{"code", [{"class", "elixir lang-elixir"}], ["IO.puts 42"], %{}}], %{}}]
```
**Rationale**:
The AST is exposed in the spirit of [Floki's](https://hex.pm/packages/floki).
### EarmarkParser.version/0
Accesses current hex version of the `EarmarkParser` application. Convenience for
`iex` usage.
## Contributing
Pull Requests are happily accepted.
Please be aware of one _caveat_ when correcting/improving `README.md`.
The `README.md` is generated by the mix task `readme` from `README.template` and
docstrings by means of `%moduledoc` or `%functiondoc` directives.
Please identify the origin of the generated text you want to correct and then
apply your changes there.
Then issue the mix task `readme`, this is important to have a correctly updated `README.md` after the merge of
your PR.
Thank you all who have already helped with Earmark/EarmarkParser, your names are duely noted in [RELEASE.md](RELEASE.md).
## Author
Copyright © 2014,5,6,7,8,9;2020 Dave Thomas, The Pragmatic Programmers
@/+pragdave, dave@pragprog.com
Copyright © 2020,1,2,3 Robert Dober
robert.dober@gmail.com
## LICENSE
Same as Elixir, which is Apache License v2.0. Please refer to [LICENSE](LICENSE) for details.
earmark_parser-1.4.39/README.md.eex 0000664 0000000 0000000 00000003521 14531042673 0016653 0 ustar 00root root 0000000 0000000
# EarmarkParser A Pure Elixir Markdown Parser
[](https://github.com/robertdober/earmark_parser/actions/workflows/elixir.yml)
[](https://coveralls.io/github/RobertDober/earmark_parser?branch=master)
[](https://hex.pm/packages/earmark_parser)
[](https://hex.pm/packages/earmark_parser)
[](https://hex.pm/packages/earmark_parser)
## Table Of Contents
<%= xtra.toc :self, gh_links: true, min_level: 2, max_level: 4, remove_gaps: true %>
## Usage
<%= xtra.moduledoc "EarmarkParser", include: :all, wrap_code_blocks: "elixir", headline: 3 %>
## Contributing
Pull Requests are happily accepted.
Please be aware of one _caveat_ when correcting/improving `README.md`.
The `README.md` is generated by the mix task `readme` from `README.template` and
docstrings by means of `%moduledoc` or `%functiondoc` directives.
Please identify the origin of the generated text you want to correct and then
apply your changes there.
Then issue the mix task `readme`, this is important to have a correctly updated `README.md` after the merge of
your PR.
Thank you all who have already helped with Earmark/EarmarkParser, your names are duely noted in [RELEASE.md](RELEASE.md).
## Author
Copyright © 2014,5,6,7,8,9;2020 Dave Thomas, The Pragmatic Programmers
@/+pragdave, dave@pragprog.com
Copyright © 2020,1,2,3 Robert Dober
robert.dober@gmail.com
## LICENSE
Same as Elixir, which is Apache License v2.0. Please refer to [LICENSE](LICENSE) for details.
earmark_parser-1.4.39/RELEASE.md 0000664 0000000 0000000 00000072575 14531042673 0016235 0 ustar 00root root 0000000 0000000
## 1.5.0 2023-??-??
- [Depreacting message to be passed in as an array in options, and fixing it](https://github.com/robertdober/earmark_parser/issues/86)
- [Parsing HTML]
## [EarmarkParser](https://hex.pm/packages/earmark_parser) 1.4.38 2023-11-10
- [Add metadata line to inline code spans](https://github.com/RobertDober/earmark_parser/pull/140)
Kudos to [Wojtek Mach](https://github.com/wojtekmach)
## [EarmarkParser](https://hex.pm/packages/earmark_parser) 1.4.37 2023-10-01
- [José fixed more deprecation warnings for Elixir 1.6](https://github.com/RobertDober/earmark_parser/pull/138)
- [José namespaced yecc and leex files for me <3](https://github.com/RobertDober/earmark_parser/pull/137)
## [EarmarkParser](https://hex.pm/packages/earmark_parser) 1.4.36 2023-09-22
- Correting deprection version for smarty_pants
- Checking for result type `EarmarkParser.t` of `EarmarkParser.as_ast`
## [EarmarkParser](https://hex.pm/packages/earmark_parser) 1.4.35 2023-09-12
- Better error messages for bad data passed into `EarmarkParser.as_ast`
- Using minipeg instead of leaky string_lexer
## [EarmarkParser](https://hex.pm/packages/earmark_parser) 1.4.34 2023-09-11
- PR [Strip spaces in front of code blocks](https://github.com/RobertDober/earmark_parser/pull/132)
Kudos to [Lukas Larsson](https://github.com/garazdawi)
## [EarmarkParser](https://hex.pm/packages/earmark_parser) 1.4.33 2023-07-04
- PR [Avoid warnings in String#slice/2 with Elixir 1.16](https://github.com/RobertDober/earmark_parser/pull/128)
Kudos to [José Valim](https://github.com/josevalim)
## [EarmarkParser](https://hex.pm/packages/earmark_parser) 1.4.32 2023-04-29
- PR [Fix of a crash on unquoted html attributes](https://github.com/RobertDober/earmark_parser/pull/127)
Kudos to [Sebastian Seilund](https://github.com/sebastianseilund)
## [EarmarkParser](https://hex.pm/packages/earmark_parser) 1.4.31 2023-03-03
- PR [Fix catastrophic backtracking in IAL regex](https://github.com/RobertDober/earmark_parser/pull/125)
Special Kudos for spotting **and** fixing this [Alex Martsinovich](https://github.com/martosaur)
- Bugfix for [Strikethrough not working if option `breaks: true`](https://github.com/RobertDober/earmark_parser/issues/123)
Kudos to [Mayel de Borniol](https://github.com/mayel) for providing tests
## 1.4.30 2023-01-27
- [Fixed a problem with headers that close with # but have a # inside too ](https://github.com/RobertDober/earmark_parser/pull/122)
Kudos to [Alex Martsinovich](https://github.com/martosaur)
- Adding a non regression test for `~` inside links (was broken earlier)
Kudos to [Faried Nawaz](https://github.com/faried)
## 1.4.29 2022-10-20
- Bugfix for [Strike Through Only Working on start of line #115](https://github.com/RobertDober/earmark_parser/issues/115)
## 1.4.28 2022-10-01
- [Do not wrap labelled wikilinks in `` tags](https://github.com/RobertDober/earmark_parser/pull/112)
Kudos to [Ben Olive](https://github.com/sionide21)
- Add option `all: true` enabling all options which are disabled by default, which are:
`breaks`, `footnotes`, `gfm_tables`, `sub_sup`, `wikilinks`
- Fix bug for `a^n^` not being parsed as sup
## 1.4.27 2022-09-30
- [Nice addition of sub and sup elements](https://github.com/RobertDober/earmark_parser/tree/i108-sub-and-sup)
Needs to be enabled with the option `sub_sup: true` renders `~x~` inside `` and `^x^` inside ``
Kudos to [manuel-rubio](https://github.com/manuel-rubio)
- Optimisation in the inline renderer
- Removal of compiler warnings
## 1.4.26 2022-06-15
- Allow unquoted values for HTML attributes
- [Accept valueless HTML attributes](https://github.com/RobertDober/earmark_parser/pull/106)
Kudos to [Tom Conroy](https://github.com/tomconroy)
## 1.4.25 2022-03-24
- [Two PRs to assure lookahead scanning is applied on the top level, where needed most](https://github.com/robertdober/earmark_parser/issues/100)
[and corresponding performance test](https://github.com/robertdober/earmark_parser/issues/101)
Kudos to [jonatanklosko](https://github.com/jonatanklosko)
## 1.4.24 2022-03-20
- Single worded footnote definitions where shadowed by ID Definitions, the tiebreak was resolved in favor of
Footnotes as ID Definitions do not need, and might as a matter of fact almost never, to start with a `^`
[Related Issue](https://github.com/RobertDober/earmark_parser/issues/99)
- Unused import warning removed
## 1.4.23 2022-03-16
Two more list regressions fixed
- multi line inline code was ignored in the body parts of lists
- spaced lists with inline code in their bodies (single and multiline) were rendered tightly (no surrounding `...
`
## 1.4.22 2022-03-14
Fixes all List Regressions introduced in 1.4.19
GFM support for lists remain limited (spaced and tight lists are not 100% compliant) but is better than in 1.4.18
## 1.4.21 2022-03-13
- [Paragraph context lost after indented code blocks](https://github.com/robertdober/earmark_parser/issues/98)
## 1.4.20 2022-02-21
- [Preserve newlines inside HTML code](https://github.com/RobertDober/earmark_parser/pull/97)
Kudos to [José Valim](https://github.com/josevalim)
- [Do not remove ial on blockquote inside triple quoted](https://github.com/RobertDober/earmark_parser/pull/96)
Kudos to [José Valim](https://github.com/josevalim)
- Removed support for Elixir 1.10 (following `ex_doc`'s lead)
- [Correct pure link regex to reject invalid characters](https://github.com/RobertDober/earmark_parser/pull/91)
Kudos to [Akash Hiremath](https://github.com/akash-akya)
- [Intensive work to make pure links GFM Spec compliant](https://github.com/RobertDober/earmark_parser/pull/92)
Kudos to [Akash Hiremath](https://github.com/akash-akya)
## 1.4.19 2022-01-07
- [Fix stop condition on closing HTML in scanners lookup algo](https://github.com/robertdober/earmark_parser/pull/79)
Kudos to [José Valim](https://github.com/josevalim)
- [Typos](https://github.com/robertdober/earmark_parser/pull/78)
Kudos to [kianmeng](https://github.com/kianmeng)
- [Footnotes fixed and upgraded(#26)](https://github.com/robertdober/earmark_parser/pull/76)
Footnotes are now a **superset** of GFM Footnotes. This implies some changes
- Footnote definitions (`[^footnote_id]`) must come at the end of your document (_GFM_)
- Footnotes that are not referenced are not rendered anymore (_GFM_)
- Footnote definitions can contain any markup with the exception of footnote definitions
## 1.4.18 2021-12-04
- [Deprecate options not useful anymore after the removal of parallel scanning (#72)](https://github.com/robertdober/earmark_parser/pull/72)
- [Do not turn off lookahead on indented fences and check for indent only once (#71)](https://github.com/robertdober/earmark_parser/pull/71)
Kudos to [José Valim](https://github.com/josevalim)
- [Add lookahead for fenced code blocks (#70)](https://github.com/robertdober/earmark_parser/pull/70)
* Do lookaheads for fenced code blocks
Prior to this commit, we were trying to parse
all lines between fenced code blocks, which
could be very expensive. Therefore we lookahead
fences and convert them to text.
* Do not scan lines in parallel anymore
* Remove unused code blocks
* Handle fenced blocks wrapped in tags
* Clean up regex
* More corner cases
* Optimize text creation
* Optimize length checks
Kudos to [José Valim](https://github.com/josevalim)
- [5 Whitespace after closing ATX Headers](https://github.com/robertdober/earmark_parser/issues/5)
- [40 Footnotes in lists](https://github.com/robertdober/earmark_parser/issues/40)
- [65 Bad line numbers in spaced lists](https://github.com/robertdober/earmark_parser/issues/65)
- [61 Fixes some double URI encoding in links](https://github.com/robertdober/earmark_parser/issues/61)
- [Deprecate Pedantic Option](https://github.com/robertdober/earmark_parser/pull/60)
## 1.4.17 2021-10-29
- [44 Multiple Query Params in pure links](https://github.com/robertdober/earmark_parser/issues/44)
- [28 Side By Side Reference Style Links fixed](https://github.com/robertdober/earmark_parser/issues/28)
- [52 Deprecate Smartypants Options](https://github.com/robertdober/earmark_parser/issues/52)
## 1.4.16 2021/10/07
- [Inline IALs for headers, rulers and blockquotes](https://github.com/robertdober/earmark_parser/pull/56)
- [Use Extractly instead of homemade readme task → Syntax Highlightening for iex> code blocks](https://github.com/robertdober/earmark_parser/pull/51)
- [Refactoring and Dead Code Elimination in Context](https://github.com/robertdober/earmark_parser/pull/50)
- [Annotations for Paragraphs and verbatim HTML](https://github.com/robertdober/earmark_parser/issues/47)
- [46-fixing-typos](https://github.com/RobertDober/earmark_parser/pull/46)
Kudos to [kianmeng](https://github.com/kianmeng)
## 1.4.15 2021/08/12
- [43-add-option-to-disable-inline-parsing](https://github.com/RobertDober/earmark_parser/pull/43)
Kudos to [jonatanklosko](https://github.com/jonatanklosko)
## 1.4.13 2021/04/18
- [37-ial-in-li-raised-key-error](https://github.com/RobertDober/earmark_parser/pull/37)
- [35-clearer-doc-iff-replacement](https://github.com/RobertDober/earmark_parser/pull/35)
Kudos to [ream88](https://github.com/ream88)
- [33-fix-for-bad-multiclass-ial-rendering](https://github.com/RobertDober/earmark_parser/pull/33)
Kudos to [myrrlyn](https://github.com/myrrlyn)
## 1.4.12 2020/11/27
- [29-broken-changelog-link](https://github.com/robertdober/earmark_parser/pull/29)
Kudos to [optikfluffel](https://github.com/optikfluffel)
- [18-support-wikilinks](https://github.com/robertdober/earmark_parser/pull/18)
Kudos to [sionide21](https://github.com/sionide21)
## 1.4.11 2020/11/26
- [24-treat-single-dash-as-text](https://github.com/robertdober/earmark_parser/issues/24)
Kudos to [Ben Olive](https://github.com/sionide21)
- [22-missing-ws-before-links](https://github.com/robertdober/earmark_parser/issues/22)
Kudos to [Ben Olive](https://github.com/sionide21)
## 1.4.10 2020/07/18
- [1-text-of-footnote-definitions-not-converted](https://github.com/robertdober/earmark_parser/issues/1)
- [19-use-spdx-in-hex](https://github.com/robertdober/earmark_parser/issues/19)
Kudos to [Chulki Lee](https://github.com/chulkilee)
- [10-Missing-space-between-whitspace-separated-items](https://github.com/robertdober/earmark_parser/issues/10)
- [15-hide-private-module](https://github.com/robertdober/earmark_parser/issues/15)
Kudos to [Wojtek Mach](https://github.com/wojtekmach)
- [14-remove-application-from-mix.exs](https://github.com/robertdober/earmark_parser/issues/14)
Kudos to [Wojtek Mach](https://github.com/wojtekmach)
- [13-fix-github-link](https://githuhttps://github.com/RobertDober/earmark_parser/issues?q=is%3Apr+author%3Awojtekmachb.com/robertdober/earmark_parser/issues/13)
Kudos to [Wojtek Mach](https://github.com/wojtekmach)
## 1.4.9 2020/07/01
- [2-accept-any-struct-as-option](https://github.com/pragdave/earmark/issues/2)
Allow client code of Earmark to replace their calls to `Earmark.as_ast` with `EarmarkParser.as_ast` w/o any
changes
## 1.4.8 2020/06/29
This marks the first release of the parser isolated from the rest of Earmark.
It is feature identical to the 1.4.7 release of Earmark.
All releases below were Earmark, all releases above are only EarmarkParser.
# Earmark
## 1.4.7 2020/06/29
- [371-still-spurious-ws-after-inline-tags](https://github.com/pragdave/earmark/issues/371)
## 1.4.6 2020/06/28
- [350-some-complicated-autolinks-cut](https://github.com/pragdave/earmark/issues/350)
- [359-unexpected-ws-in-html](https://github.com/pragdave/earmark/issues/359)
- [337-quadruple-ast-format](https://github.com/pragdave/earmark/issues/337)
- [366-simplify-transform](https://github.com/pragdave/earmark/issues/366)
Kudos to [Eksperimental](https://github.com/eksperimental)
- [353-oneline-html-tags](https://github.com/pragdave/earmark/issues/353)
- [351-html-tags-without-newlines](https://github.com/pragdave/earmark/issues/351)
- [335-content-inside-table-cells-reversed](https://github.com/pragdave/earmark/issues/335)
- [348-no-crashes-for-invalid-URIs](https://github.com/pragdave/earmark/issues/348)
Kudos to José Valim
- [347-dialyxir-errors](https://github.com/pragdave/earmark/issues/347)
Fixed some of them, alas not all
## 1.4.5 2020/06/06
This is mostly a bugfix release, as there were edge cases that resulted in
Earmark crashing, notably
- Bare IAL
- unquoted attributes in html tags
Also autolinks (GFM extension) delivered incorrect URLS where parenthesis were involved,
for better GFM compatibility we therefore
- Fixed broken parenthesis links (99% of all cases)
- introduced the same URL encoding/decoding in links and link names of autolinks as GFM does
And last but not least all numeric options in the CLI can now be written with
underlines for readability.
- [343-error-parsing-unquoted-atts](https://github.com/pragdave/earmark/issues/343)
- [342 parens in pure links](https://github.com/pragdave/earmark/issues/342)
- [340 IAL might cause error](https://github.com/pragdave/earmark/issues/340)
- [339 Typos fix](ihttps://github.com/pragdave/earmark/pull/339)
Kudos to [Ondrej Pinka](https://github.com/onpikono)
- [336 Smartypants: Convert three hyphens to em dash](https://github.com/pragdave/earmark/pull/336)
Kudos to [Jony Stoten](https://github.com/jonnystoten)
- [324 Fix AST for links with nested elements](https://github.com/pragdave/earmark/pull/324)
Kudos to [Wojtek Mach](https://github.com/wojtekmach)
- [320 Nested Blockquotes](https://github.com/pragdave/earmark/issues/320)
## 1.4.4 2020/05/01
- [338 Deprecation warnings in mixfile removed](https://github.com/pragdave/earmark/issues/338)
## 1.4.3 2019/11/23
- [309 fenced code allows for more than 3 backticks/tildes now](https://github.com/pragdave/earmark/issues/309)
- [302 Earmark.version returned a charlist, now a string](https://github.com/pragdave/earmark/issues/302)
- [298 Blockquotes nested in lists only work with an indentation of 2 spaces](https://github.com/pragdave/earmark/issues/298)
## 1.4.2 2019/10/14
- [296 code for tasks removed from package](https://github.com/pragdave/earmark/issues/296)
The additional tasks are only needed for dev and have been removed from the hex package. **Finally**
- [PR#293 Nice fix for broken TOC links in README](https://github.com/pragdave/earmark/pull/293)
Kudos to Ray Gesualdo [raygesualdo](https://github.com/raygesualdo)
- [291 Transformer whitespace inside / around <code> <pre> tags](https://github.com/pragdave/earmark/issues/291)
The spurious whitespace has been removed
- [289 HTML Problem](https://github.com/pragdave/earmark/issues/289)
The AST parser can now correctly distinguish between _generated_ AST (from md) and _parsed_ AST (from HTML)
- [288 Metadata allowed to be added to the AST](https://github.com/pragdave/earmark/issues/288)
The default HTML Transformer ignores metadata in the form of a map with the exception of `%{meta: ...}`
## 1.4.1 2019/09/24
- [282 Always create a `` in tables](https://github.com/pragdave/earmark/issues/282)
Although strictly speaking a `` is only needed when there is a ``, semantic
HTML suggests the presence of `` anyway.
- [281 Urls in links were URL endoded, that is actually a bug ](https://github.com/pragdave/earmark/issues/281)
It is the markdown author's responsibility to url encode her urls, if she does so correctly
we double encoded the url before this fix.
- [279 Languages in code blocks were limited to alphanum names, thus excluding, e.g. C# ](https://github.com/pragdave/earmark/issues/279)
- [278 Implementing better GFM Table support ](https://github.com/pragdave/earmark/issues/278)
Because of compatility issues we use a new option `gfm_tables` defaulting to `false` for this.
Using this option `Earmark` will implement its own table extension **+** GFM tables at the same
time.
- [277 Expose an AST to HTML Transformer](https://github.com/pragdave/earmark/issues/277)
While it should be faster to call `to_ast|>transform` it cannot be used instead of `as_html` yet
as the API is not yet stable and some subtle differences in the output need to be addressed.
## 1.4.0 2019/09/05
- [145 Expose AST for output manipulation]( https://github.com/pragdave/earmark/issues/145)
- [238 Pure Links are default now]( https://github.com/pragdave/earmark/issues/238)
- [256 Align needed Elixir Version with ex_doc (>= 1.7)]( https://github.com/pragdave/earmark/issues/256)
- [259 Deprecated option `sanitize` removed]( https://github.com/pragdave/earmark/issues/259)
- [261 Deprecated Plugins removed]( https://github.com/pragdave/earmark/issues/261)
- [265 Make deprecated `Earmark.parse/2` private]( https://github.com/pragdave/earmark/issues/265)
## 1.3.6 2019/08/30
Hopefully the last patch release of 1.3 before the structural changes of 1.4.
- [#270]( https://github.com/pragdave/earmark/issues/270)
Error messages during parsing of table cells were duplicated in a number, exponential to the number of table cells.
- [#268]( https://github.com/pragdave/earmark/issues/268)
Deprecation warnings concerning pure links showed fixed link to https://github.com/pragdave/earmark, at least a reasonable choice ;),
instead of the text of the link.
- [#266]( https://github.com/pragdave/earmark/issues/266)
According to HTML5 Style Guide better XHTML compatibility by closing void tags e.g. `
` --> `
`
## 1.3.5 2019/08/01
- [#264]( https://github.com/pragdave/earmark/issues/264)
Expose `Earmark.parse/2` but deprecate it.
- [#262]( https://github.com/pragdave/earmark/issues/262)
Remove non XHTML tags and
- [#236]( https://github.com/pragdave/earmark/issues/236)
Deprecation of plugins.
- [#257]( https://github.com/pragdave/earmark/issues/257)
Deprecation of `sanitize` option.
## 1.3.4 2019/07/29
- [#254 pure links inside links](https://github.com/pragdave/earmark/issues/254)
## 1.3.3 2019/07/23
### Bugs
- [#240 code blocks in lists](https://github.com/pragdave/earmark/issues/240)
Bad reindentation inside list items led to code blocks not being verabtim =&rt; Badly formatted hexdoc for Earmark
- [#243 errors in unicode link names](https://github.com/pragdave/earmark/issues/243)
Regexpression was not UTF, thus some links were not correctly parsed
Fixed in PR [244](https://github.com/pragdave/earmark/pull/244)
Thank you [Stéphane ROBINO](https://github.com/StephaneRob)
### Features
- [#158 some pure links implemented](https://github.com/pragdave/earmark/issues/158)
This GFM like behavior is more and more expected, I will issue a PR for `ex_doc` on this as discussed with
[José Valim](https://github.com/josevalim)
Deprecation Warnings are issued by default, but will be suppressed for `ex_doc` in said PR.
- Minor improvements on documentation
In PR [235](https://github.com/pragdave/earmark/pull/235)
Thank you - [Jason Axelson](https://github.com/axelson)
### Other
- Refactoring c.f. PR [246](https://github.com/pragdave/earmark/pull/246)
- Added Elixir version 1.9.0 for Travis c.f. PR #248
### Minor improvements on documentation
### PRs
- [244](https://github.com/pragdave/earmark/pull/244)
- [235](https://github.com/pragdave/earmark/pull/235)
### Kudos:
- [Jason Axelson](https://github.com/axelson)
- [Stéphane ROBINO](https://github.com/StephaneRob)
## 1.3.2 2019/03/23
* Fix for issues
- [#224 titles might be extracted from outside link]( https://github.com/pragdave/earmark/issues/224 )
- [#220 render only first link title always correctly]( https://github.com/pragdave/earmark/issues/220 )
- [#218 replaced iff with longer but clearer if and only if ]( https://github.com/pragdave/earmark/issues/218 )
### Kudos:
[niku](https://github.com/niku) for #218
[Rich Morin](https://github.com/RichMorin) for #220 & #224 as well as discussions
## 1.3.1 2018/12/21
- [#212 spaces at line end force line break]( https://github.com/pragdave/earmark/issues/212 )
- [#211 documentation explaining error messages]( https://github.com/pragdave/earmark/issues/211 )
## 1.3.0 2018/11/15
* Fix for issues
- [#208 Inline code made Commonmark compatible]( https://github.com/pragdave/earmark/issues/208 )
- [#203 escript does not report filename in error messages]( https://github.com/pragdave/earmark/issues/203 )
- [#90 Parsing "...' or '..." as link titles removed]( https://github.com/pragdave/earmark/issues/90 )
### Dev dependencies updated
* credo -> 0.10
## 1.2.7 Not released Milestone merged into 1.3
Special KUDOS for [pareehonos](https://github.com/pareeohnos) for a huge PR concerning the major Feature Request [#145](https://github.com/pragdave/earmark/issues/145)
This cannot be merged yet but certainly is a great contribution to our codebase.
## 1.2.6 2018/08/21
* Fix for issues
- [#198 Escapes inside link texts are ignored]( https://github.com/pragdave/earmark/issues/198 )
- [#197 README task broken in Elixir 1.7]( https://github.com/pragdave/earmark/issues/197 )
- [#191 Allow configurable timeout for parallel map]( https://github.com/pragdave/earmark/issues/191 )
- [#190 do not include generated src/*.erl in the package]( https://github.com/pragdave/earmark/issues/190 )
* [#195 incorrect HTML for inline code blocks and IAL specified classes](https://github.com/pragdave/earmark/issues/195) from [Benjamin Milde]( https://github.com/LostKobrakai )
## 1.2.5 2018/04/02
* Fix for issues
- [#161]( https://github.com/pragdave/earmark/issues/161 )
- [#168]( https://github.com/pragdave/earmark/issues/168 )
- [#172]( https://github.com/pragdave/earmark/issues/172 )
- [#175]( https://github.com/pragdave/earmark/issues/175 )
- [#181]( https://github.com/pragdave/earmark/issues/181 )
* [#178](https://github.com/pragdave/earmark/pull/178) from [jwworth](https://github.com/jwworth)
### Kudos:
[jwworth](https://github.com/jwworth)
## 1.2.4 2017/11/28
* Fix for issue
- [#166]( https://github.com/pragdave/earmark/issues/166 )
* [PR160](https://github.com/pragdave/earmark/pull/160) from [simonwebdesign](https://github.com/simonewebdesign)
* [PR163](https://github.com/pragdave/earmark/pull/163) from [nscyclone](https://github.com/nscyclone)
* [PR164](https://github.com/pragdave/earmark/pull/164) from [joshsmith](https://github.com/joshsmith)
* [PR165](https://github.com/pragdave/earmark/pull/165) from [asummers](https://github.com/asummers)
### Kudos:
[simonwebdesign](https://github.com/simonewebdesign), [nscyclone](https://github.com/nscyclone),
[joshsmith](https://github.com/joshsmith), [asummers](https://github.com/asummers)
## 1.2.3 2017/07/26
* [PR151](https://github.com/pragdave/earmark/pull/151) from [joshuawscott](https://github.com/joshuawscott)
* Fixes for issues
- [#150](https://github.com/pragdave/earmark/issues/150)
- [#147](https://github.com/pragdave/earmark/issues/147)
### Kudos:
[joshuawscott](https://github.com/joshuawscott)
## 1.2.2 2017/05/11
* [PR #144](https://github.com/pragdave/earmark/pull/144) from [KronicDeth](https://github.com/KronicDeth)
### Kudos:
[KronicDeth](https://github.com/KronicDeth)
## 1.2.1 2017/05/03
* [PR #136](https://github.com/pragdave/earmark/pull/136) from [chrisalley](https://github.com/chrisalley)
* Fixes for issues
- [#139](https://github.com/pragdave/earmark/issues/139)
### Kudos:
[chrisalley](https://github.com/chrisalley)
## 1.2 2017/03/10
* [PR #130](https://github.com/pragdave/earmark/pull/130) from [eksperimental](https://github.com/eksperimental)
* [PR #129](https://github.com/pragdave/earmark/pull/129) from [Alekx](https://github.com/alkx)
* [PR #125](//https://github.com/pragdave/earmark/pull/125) from [vyachkonovalov](https://github.com/vyachkonovalov)
* Fixes for issues
- #127
- #131
### Kudos:
[vyachkonovalov](https://github.com/vyachkonovalov), [Alekx](https://github.com/alkx), [eksperimental](https://github.com/eksperimental)
## 1.1.1 2017/02/03
* PR from Natronium pointing out issue #123
* Fixes for issues
- #123
### Kudos:
[Natronium](https://github.com/Natronium)
## 1.1.0 2017/01/22
* PR from Michael Pope
* PR from Pragdave
* PR from christopheradams
* PR from [AndrewDryga](https://github.com/AndrewDryga)
* Fixes for issues
- #106
- #110
- #114
### Kudos:
[AndrewDryga](https://github.com/AndrewDryga), [Christopher Adams](https://github.com/christopheradams),
[Michael Pope](https://github.com/amorphid)
## 1.0.3 2016/11/02
* PR from TBK145 with some dead code elimination.
* Implementation of command line switches for the `earmark` executable. Now any `%Earmark.Options{}` key can be
passed in.
* Fixes for issues
- #99
- #96
- #95
- #103
### Kudos:
Thijs Klaver (TBK145)
## 1.0.2 2016/10/10
* PR from pdebelak with a fix of #55
* PR from jonnystorm with a fix for a special case in issue #85
* test coverage at 100%
* PR from michalmuskala
* Fixed remaining compiler warnings from 1.0.1 (Elixir 1.3)
* PR from pdebelak to fix a factual error in the README
* Fixes for issues
- #55
- #86
- #88
- #89
- #93
### Kudos:
Jonathan Storm (jonnystorm), Michal Muskala (michalmuskala) & Peter Debelak (pdebelak)
## 1.0.1 2016/06/07
* fixing issue #81 by pushing this updated Changelog.md :)
* PR from mschae fixing issue #80 broken hex package
### Kudos:
Michael Schaefermeyer (mschae) & Tobias Pfeiffer (PragTob)
## 1.0.0 2016/06/07
* --version | -v switch for `earmark` escript.
* added security notice about XSS to docs thanks to remiq
* PR from alakra (issue #59) to allow Hyphens and Unicode in fenced code block names
* PR from sntran to fix unsafe conditional variables from PR
* PR from riacataquian to use maps instead of dicts
* PR from gmile to remove duplicate tests
* PR from gmile to upgrade poison dependency
* PR from whatyouhide to fix warnings for Elixir 1.4 with additional help from milmazz
* Travis for 1.2.x and 1.3.1 as well as OTP 19
* Fixes for issues:
- #61
- #66
- #70
- #71
- #72
- #77
- #78
### Kudos:
Remigiusz Jackowski (remiq), Angelo Lakra (alakra), Son Tran-Nguyen (sntran), Mike Kreuzer (mikekreuzer),
Ria Cataquian (riacataquian), Eugene Pirogov (gmile), Andrea Leopardi (whatyouhide) & Milton Mazzarri (milmazz)
## 0.2.1 2016/01/15
* Added 1.2 for Travis
* PR from mneudert to fix HTMLOneLine detection
### Kudos:
Marc Neudert (mneudert)
## 0.2.0 2015/12/28
* PR from eksperimental guaranteeing 100% HTML5
* PR from imbriaco to decouple parsing and html generation and whitespace removal
* Fixes for issues:
- #40
- #41
- #43
- #48
- #50
- #51
* Explicit LICENSE change to Apache 2.0 (#42)
* Loading of test support files only in test environment thanks to José Valim
* IO Capture done correctly thanks to whatyouhide
* Warning for Elixir 1.2 fixed by mschae
### Kudos:
Eksperimental (eksperimental), Mark Imbriaco (imbriaco), Andrea Leopardi(whatyouhide), José Valim &
Michael Schaefermeyer (mschae)
## 0.1.19 2015/10/27
* Fix | in implicit lists, and restructur the parse a little.
Many thanks to Robert Dober
## 0.1.17 2015/05/18
* Add strikethrough support to the HTML renderer. Thanks to
Michael Schaefermeyer (mschae)
## 0.1.16 2015/05/08
* Another fix from José, this time for & in code blocks.
## 0.1.15 2015/03/25
* Allow numbered lists to start anywhere in the first four columns.
(This was previously allowed for unnumbered lists). Fixes #13.
## 0.1.14 2015/03/25
* Fixed a problem where a malformed text heading caused a crash.
We now report what appears to be malformed Markdown and
continue, processing the line as text. Fixes #17.
## 0.1.13 2015/01/31
* José fixed a bug in Regex that revealed a problem with some
Earmark replacement strings. As he's a true gentleman, he then
fixed Earmark.
## 0.1.11 2014/08/18
* Matthew Lyon contributed footnote support.
the answer is clearly 42.[^fn-why] In this case
we need to…
[^fn-why]: 42 is the only two digit number with
the digits 4 and 2 that starts with a 4.
For now, consider it experimental. For that reason, you have
to enable it by passing the `footnotes: true` option.
## 0.1.10 2014/08/13
* The spec is ambiguous when it comes to setext headings. I assumed
that they needed a blank line after them, but common practice says
no. Changed the parser to treat them as headings if there's no
blank.
## 0.1.9 2014/08/05
* Bug fix—extra blank lines could be appended to code blocks.
* Tidied up code block HTML
## 0.1.7 2014/07/26
* Block rendering is now performed in parallel
## 0.1.6 07/25/14
* Added support for Kramdown-style attribute annotators for all block
elements, so you can write
# Warning
{: .red}
Do not turn off the engine
if you are at altitude.
{: .boxed #warning spellcheck="true"}
and generate
Warning
Do not turn
off the engine if you are at altitude.
## 0.1.5 07/20/14
* Merged two performance improvements from José Valim
* Support escaping of pipes in tables, so
a | b
c | d \| e
has two columns, not three.
## 0.1.4 07/14/14
* Allow list bullets to be indented, and deal with potential subsequent
additional indentation of the body of an item.
## 0.1.3 07/14/14
* Added tasks to the Hex file list
## 0.1.2 07/14/14
* Add support for GFM tables
## 0.1.1 07/09/14
* Move readme generation task out of mix.exs and into tasks/
* Fix bug if setext heading started on first line
## 0.1.0 07/09/14
* Initial Release
earmark_parser-1.4.39/coveralls.json 0000664 0000000 0000000 00000000057 14531042673 0017502 0 ustar 00root root 0000000 0000000 {
"skip_files": [ "test/support", "src/" ]
}
earmark_parser-1.4.39/lib/ 0000775 0000000 0000000 00000000000 14531042673 0015361 5 ustar 00root root 0000000 0000000 earmark_parser-1.4.39/lib/earmark_parser.ex 0000664 0000000 0000000 00000054373 14531042673 0020731 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser do
@type ast_meta :: map()
@type ast_tag :: binary()
@type ast_attribute_name :: binary()
@type ast_attribute_value :: binary()
@type ast_attribute :: {ast_attribute_name(), ast_attribute_value()}
@type ast_attributes :: list(ast_attribute())
@type ast_tuple :: {ast_tag(), ast_attributes(), ast(), ast_meta()}
@type ast_node :: binary() | ast_tuple()
@type ast :: list(ast_node())
@type error :: {atom(), non_neg_integer(), binary()}
@type errors :: list(error())
@type t :: {:ok, ast(), []} | {:error, ast(), errors()}
@moduledoc ~S"""
### API
#### EarmarkParser.as_ast
This is the structure of the result of `as_ast`.
{:ok, ast, []} = EarmarkParser.as_ast(markdown)
{:ok, ast, deprecation_messages} = EarmarkParser.as_ast(markdown)
{:error, ast, error_messages} = EarmarkParser.as_ast(markdown)
For examples see the functiondoc below.
#### Options
Options can be passed into `as_ast/2` according to the documentation of `EarmarkParser.Options`.
{status, ast, errors} = EarmarkParser.as_ast(markdown, options)
## Supports
Standard [Gruber markdown][gruber].
[gruber]:
## Extensions
### Links
#### Links supported by default
##### Oneline HTML Link tags
iex(1)> EarmarkParser.as_ast(~s{link})
{:ok, [{"a", [{"href", "href"}], ["link"], %{verbatim: true}}], []}
##### Markdown links
New style ...
iex(2)> EarmarkParser.as_ast(~s{[title](destination)})
{:ok, [{"p", [], [{"a", [{"href", "destination"}], ["title"], %{}}], %{}}], []}
and old style
iex(3)> EarmarkParser.as_ast("[foo]: /url \"title\"\n\n[foo]\n")
{:ok, [{"p", [], [{"a", [{"href", "/url"}, {"title", "title"}], ["foo"], %{}}], %{}}], []}
#### Autolinks
iex(4)> EarmarkParser.as_ast("")
{:ok, [{"p", [], [{"a", [{"href", "https://elixir-lang.com"}], ["https://elixir-lang.com"], %{}}], %{}}], []}
#### Additional link parsing via options
#### Pure links
**N.B.** that the `pure_links` option is `true` by default
iex(5)> EarmarkParser.as_ast("https://github.com")
{:ok, [{"p", [], [{"a", [{"href", "https://github.com"}], ["https://github.com"], %{}}], %{}}], []}
But can be deactivated
iex(6)> EarmarkParser.as_ast("https://github.com", pure_links: false)
{:ok, [{"p", [], ["https://github.com"], %{}}], []}
#### Wikilinks...
are disabled by default
iex(7)> EarmarkParser.as_ast("[[page]]")
{:ok, [{"p", [], ["[[page]]"], %{}}], []}
and can be enabled
iex(8)> EarmarkParser.as_ast("[[page]]", wikilinks: true)
{:ok, [{"p", [], [{"a", [{"href", "page"}], ["page"], %{wikilink: true}}], %{}}], []}
### Sub and Sup HTML Elements
This feature is not enabled by default but can be enabled with the option `sub_sup: true`
Therefore we will get
iex(9)> EarmarkParser.as_ast("H~2~O or a^n^ + b^n^ = c^n^")
{:ok, [{"p", [], ["H~2~O or a^n^ + b^n^ = c^n^"], %{}}], []}
But by specifying `sub_sup: true`
iex(10)> EarmarkParser.as_ast("H~2~O or a^n^ + b^n^ = c^n^", sub_sup: true)
{:ok, [{"p", [], ["H", {"sub", [], ["2"], %{}}, "O or a", {"sup", [], ["n"], %{}}, " + b", {"sup", [], ["n"], %{}}, " = c", {"sup", [], ["n"], %{}}], %{}}], []}
### Mathematical expressions
> Note: math syntax within Markdown is not standardized, so this option is a subject to change in future releases.
This feature is not enabled by default but can be enabled with the option `math: true`.
When enabled, LaTeX formatted math can be written within Markdown. For more information, see [LaTeX/Mathematics](https://en.wikibooks.org/wiki/LaTeX/Mathematics) in Wikibooks.
#### Inline expressions
Inline-style expression can be written by surrounding the expression with dollar symbols (`$`).
iex> EarmarkParser.as_ast("$x = 1$", math: true)
{:ok, [{"p", [], [{"code", [{"class", "math-inline"}], ["x = 1"], %{line: 1}}], %{}}], []}
There must be no space between `$` and the surrounded expression. If you want to use a dollar sign in the same line as a math expression, you can escape the dollar with backslash (`\\$`).
#### Expressions as blocks
Display-style expression can be written by surrounding the expression with two dollar signs (`$$`).
iex> EarmarkParser.as_ast("$$x = 1$$", math: true)
{:ok, [{"p", [], [{"code", [{"class", "math-display"}], ["x = 1"], %{line: 1}}], %{}}], []}
### Github Flavored Markdown
GFM is supported by default, however as GFM is a moving target and all GFM extension do not make sense in a general context, EarmarkParser does not support all of it, here is a list of what is supported:
#### Strike Through
iex(11)> EarmarkParser.as_ast("~~hello~~")
{:ok, [{"p", [], [{"del", [], ["hello"], %{}}], %{}}], []}
#### GFM Tables
Are not enabled by default
iex(12)> as_ast("a|b\\n-|-\\nc|d\\n")
{:ok, [{"p", [], ["a|b\\n-|-\\nc|d\\n"], %{}}], []}
But can be enabled with `gfm_tables: true`
iex(13)> as_ast("a|b\n-|-\nc|d\n", gfm_tables: true)
{:ok,
[
{
"table",
[],
[
{"thead", [], [{"tr", [], [{"th", [{"style", "text-align: left;"}], ["a"], %{}}, {"th", [{"style", "text-align: left;"}], ["b"], %{}}], %{}}], %{}},
{"tbody", [], [{"tr", [], [{"td", [{"style", "text-align: left;"}], ["c"], %{}}, {"td", [{"style", "text-align: left;"}], ["d"], %{}}], %{}}], %{}}
],
%{}
}
],
[]}
#### Syntax Highlighting
All backquoted or fenced code blocks with a language string are rendered with the given
language as a _class_ attribute of the _code_ tag.
For example:
iex(14)> [
...(14)> "```elixir",
...(14)> " @tag :hello",
...(14)> "```"
...(14)> ] |> as_ast()
{:ok, [{"pre", [], [{"code", [{"class", "elixir"}], [" @tag :hello"], %{}}], %{}}], []}
will be rendered as shown in the doctest above.
If you want to integrate with a syntax highlighter with different conventions you can add more classes by specifying prefixes that will be
put before the language string.
Prism.js for example needs a class `language-elixir`. In order to achieve that goal you can add `language-`
as a `code_class_prefix` to `EarmarkParser.Options`.
In the following example we want more than one additional class, so we add more prefixes.
iex(15)> [
...(15)> "```elixir",
...(15)> " @tag :hello",
...(15)> "```"
...(15)> ] |> as_ast(%EarmarkParser.Options{code_class_prefix: "lang- language-"})
{:ok, [{"pre", [], [{"code", [{"class", "elixir lang-elixir language-elixir"}], [" @tag :hello"], %{}}], %{}}], []}
#### Footnotes
**N.B.** Footnotes are disabled by default, use `as_ast(..., footnotes: true)` to enable them
Footnotes are now a **superset** of GFM Footnotes. This implies some changes
- Footnote definitions (`[^footnote_id]`) must come at the end of your document (_GFM_)
- Footnotes that are not referenced are not rendered anymore (_GFM_)
- Footnote definitions can contain any markup with the exception of footnote definitions
# iex(16)> markdown = [
# ...(16)> "My reference[^to_footnote]",
# ...(16)> "",
# ...(16)> "[^1]: I am not rendered",
# ...(16)> "[^to_footnote]: Important information"]
# ...(16)> {:ok, ast, []} = as_ast(markdown, footnotes: true)
# ...(16)> ast
# [
# {"p", [], ["My reference",
# {"a",
# [{"href", "#fn:to_footnote"}, {"id", "fnref:to_footnote"}, {"class", "footnote"}, {"title", "see footnote"}],
# ["to_footnote"], %{}}
# ], %{}},
# {"div",
# [{"class", "footnotes"}],
# [{"hr", [], [], %{}},
# {"ol", [],
# [{"li", [{"id", "fn:to_footnote"}],
# [{"a", [{"title", "return to article"}, {"class", "reversefootnote"}, {"href", "#fnref:to_footnote"}], ["↩"], %{}},
# {"p", [], ["Important information"], %{}}], %{}}
# ], %{}}], %{}}
# ]
For more complex examples of footnotes, please refer to
[these tests](https://github.com/RobertDober/earmark_parser/tree/master/test/acceptance/ast/footnotes/multiple_fn_test.exs)
#### Breaks
Hard linebreaks are disabled by default
iex(17)> ["* a"," b", "c"]
...(17)> |> as_ast()
{:ok,
[{"ul", [], [{"li", [], ["a\nb\nc"], %{}}], %{}}],
[]}
But can be enabled with `breaks: true`
iex(18)> ["* a"," b", "c"]
...(18)> |> as_ast(breaks: true)
{:ok, [{"ul", [], [{"li", [], ["a", {"br", [], [], %{}}, "b", {"br", [], [], %{}}, "c"], %{}}], %{}}], []}
#### Enabling **all** options that are disabled by default
Can be achieved with the `all: true` option
iex(19)> [
...(19)> "a^n^",
...(19)> "b~2~",
...(19)> "[[wikilink]]"]
...(19)> |> as_ast(all: true)
{:ok, [
{"p", [], ["a", {"sup", [], ["n"], %{}}, {"br", [], [], %{}}, "b", {"sub", [], ["2"], %{}}, {"br", [], [], %{}}, {"a", [{"href", "wikilink"}], ["wikilink"], %{wikilink: true}}], %{}}
],
[]}
#### Tables
Are supported as long as they are preceded by an empty line.
State | Abbrev | Capital
----: | :----: | -------
Texas | TX | Austin
Maine | ME | Augusta
Tables may have leading and trailing vertical bars on each line
| State | Abbrev | Capital |
| ----: | :----: | ------- |
| Texas | TX | Austin |
| Maine | ME | Augusta |
Tables need not have headers, in which case all column alignments
default to left.
| Texas | TX | Austin |
| Maine | ME | Augusta |
Currently we assume there are always spaces around interior vertical unless
there are exterior bars.
However in order to be more GFM compatible the `gfm_tables: true` option
can be used to interpret only interior vertical bars as a table if a separation
line is given, therefore
Language|Rating
--------|------
Elixir | awesome
is a table (if and only if `gfm_tables: true`) while
Language|Rating
Elixir | awesome
never is.
#### HTML Blocks
HTML is not parsed recursively or detected in all conditions right now, though GFM compliance
is a goal.
But for now the following holds:
A HTML Block defined by a tag starting a line and the same tag starting a different line is parsed
as one HTML AST node, marked with %{verbatim: true}
E.g.
iex(20)> lines = [ "", "some", "
more text" ]
...(20)> EarmarkParser.as_ast(lines)
{:ok, [{"div", [], ["", "some"], %{verbatim: true}}, "more text"], []}
And a line starting with an opening tag and ending with the corresponding closing tag is parsed in similar
fashion
iex(21)> EarmarkParser.as_ast(["spaniel"])
{:ok, [{"span", [{"class", "superspan"}], ["spaniel"], %{verbatim: true}}], []}
What is HTML?
We differ from strict GFM by allowing **all** tags not only HTML5 tags this holds for one liners....
iex(22)> {:ok, ast, []} = EarmarkParser.as_ast(["", "better"])
...(22)> ast
[
{"stupid", [], [], %{verbatim: true}},
{"not", [], ["better"], %{verbatim: true}}]
and for multi line blocks
iex(23)> {:ok, ast, []} = EarmarkParser.as_ast([ "", "world", ""])
...(23)> ast
[{"hello", [], ["world"], %{verbatim: true}}]
#### HTML Comments
Are recognized if they start a line (after ws and are parsed until the next `-->` is found
all text after the next '-->' is ignored
E.g.
iex(24)> EarmarkParser.as_ast(" text -->\nafter")
{:ok, [{:comment, [], [" Comment", "comment line", "comment "], %{comment: true}}, {"p", [], ["after"], %{}}], []}
#### Lists
Lists are pretty much GFM compliant, but some behaviors concerning the interpreation of the markdown inside a List Item's first
paragraph seem not worth to be interpreted, examples are blockquote in a tight [list item](ttps://babelmark.github.io/?text=*+aa%0A++%3E+Second)
which we can only have in a [loose one](https://babelmark.github.io/?text=*+aa%0A++%0A++%3E+Second)
Or a headline in a [tight list item](https://babelmark.github.io/?text=*+bb%0A++%23+Headline) which, again is only available in the
[loose version](https://babelmark.github.io/?text=*+bb%0A%0A++%23+Headline) in EarmarkParser.
furthermore [this example](https://babelmark.github.io/?text=*+aa%0A++%60%60%60%0ASecond%0A++%60%60%60) demonstrates how weird
and definitely not useful GFM's own interpretation can get.
Therefore we stick to a more predictable approach.
iex(25)> markdown = [
...(25)> "* aa",
...(25)> " ```",
...(25)> "Second",
...(25)> " ```" ]
...(25)> as_ast(markdown)
{:ok, [{"ul", [], [{"li", [], ["aa", {"pre", [], [{"code", [], ["Second"], %{}}], %{}}], %{}}], %{}}], []}
Also we do support the immediate style of block content inside lists
iex(26)> as_ast("* > Nota Bene!")
{:ok, [{"ul", [], [{"li", [], [{"blockquote", [], [{"p", [], ["Nota Bene!"], %{}}], %{}}], %{}}], %{}}], []}
or
iex(27)> as_ast("1. # Breaking...")
{:ok, [{"ol", [], [{"li", [], [{"h1", [], ["Breaking..."], %{}}], %{}}], %{}}], []}
### Adding Attributes with the IAL extension
#### To block elements
HTML attributes can be added to any block-level element. We use
the Kramdown syntax: add the line `{:` _attrs_ `}` following the block.
iex(28)> markdown = ["# Headline", "{:.from-next-line}"]
...(28)> as_ast(markdown)
{:ok, [{"h1", [{"class", "from-next-line"}], ["Headline"], %{}}], []}
Headers can also have the IAL string at the end of the line
iex(29)> markdown = ["# Headline{:.from-same-line}"]
...(29)> as_ast(markdown)
{:ok, [{"h1", [{"class", "from-same-line"}], ["Headline"], %{}}], []}
A special use case is headers inside blockquotes which allow for some nifty styling in `ex_doc`*
see [this PR](https://github.com/elixir-lang/ex_doc/pull/1400) if you are interested in the technical
details
iex(30)> markdown = ["> # Headline{:.warning}"]
...(30)> as_ast(markdown)
{:ok, [{"blockquote", [], [{"h1", [{"class", "warning"}], ["Headline"], %{}}], %{}}], []}
This also works for headers inside lists
iex(31)> markdown = ["- # Headline{:.warning}"]
...(31)> as_ast(markdown)
{:ok, [{"ul", [], [{"li", [], [{"h1", [{"class", "warning"}], ["Headline"], %{}}], %{}}], %{}}], []}
It still works for inline code, as it did before
iex(32)> markdown = "`Enum.map`{:lang=elixir}"
...(32)> as_ast(markdown)
{:ok, [{"p", [], [{"code", [{"class", "inline"}, {"lang", "elixir"}], ["Enum.map"], %{line: 1}}], %{}}], []}
_attrs_ can be one or more of:
* `.className`
* `#id`
* name=value, name="value", or name='value'
For example:
# Warning
{: .red}
Do not turn off the engine
if you are at altitude.
{: .boxed #warning spellcheck="true"}
#### To links or images
It is possible to add IAL attributes to generated links or images in the following
format.
iex(33)> markdown = "[link](url) {: .classy}"
...(33)> EarmarkParser.as_ast(markdown)
{ :ok, [{"p", [], [{"a", [{"class", "classy"}, {"href", "url"}], ["link"], %{}}], %{}}], []}
For both cases, malformed attributes are ignored and warnings are issued.
iex(34)> [ "Some text", "{:hello}" ] |> Enum.join("\n") |> EarmarkParser.as_ast()
{:error, [{"p", [], ["Some text"], %{}}], [{:warning, 2,"Illegal attributes [\"hello\"] ignored in IAL"}]}
It is possible to escape the IAL in both forms if necessary
iex(35)> markdown = "[link](url)\\{: .classy}"
...(35)> EarmarkParser.as_ast(markdown)
{:ok, [{"p", [], [{"a", [{"href", "url"}], ["link"], %{}}, "{: .classy}"], %{}}], []}
This of course is not necessary in code blocks or text lines
containing an IAL-like string, as in the following example
iex(36)> markdown = "hello {:world}"
...(36)> EarmarkParser.as_ast(markdown)
{:ok, [{"p", [], ["hello {:world}"], %{}}], []}
## Limitations
* Block-level HTML is correctly handled only if each HTML
tag appears on its own line. So
will work. However. the following won't
hello
* John Gruber's tests contain an ambiguity when it comes to
lines that might be the start of a list inside paragraphs.
One test says that
This is the text
* of a paragraph
that I wrote
is a single paragraph. The "*" is not significant. However, another
test has
* A list item
* an another
and expects this to be a nested list. But, in reality, the second could just
be the continuation of a paragraph.
I've chosen always to use the second interpretation—a line that looks like
a list item will always be a list item.
* Rendering of block and inline elements.
Block or void HTML elements that are at the absolute beginning of a line end
the preceding paragraph.
Thusly
mypara
Becomes
mypara
While
mypara
will be transformed into
mypara
## Annotations
**N.B.** this is an experimental feature from v1.4.16-pre on and might change or be removed again
The idea is that each markdown line can be annotated, as such annotations change the semantics of Markdown
they have to be enabled with the `annotations` option.
If the `annotations` option is set to a string (only one string is supported right now, but a list might
be implemented later on, hence the name), the last occurrence of that string in a line and all text following
it will be added to the line as an annotation.
Depending on how that line will eventually be parsed, this annotation will be added to the meta map (the 4th element
in an AST quadruple) with the key `:annotation`
In the current version the annotation will only be applied to verbatim HTML tags and paragraphs
Let us show some examples now:
### Annotated Paragraphs
iex(37)> as_ast("hello %> annotated", annotations: "%>")
{:ok, [{"p", [], ["hello "], %{annotation: "%> annotated"}}], []}
If we annotate more than one line in a para the first annotation takes precedence
iex(38)> as_ast("hello %> annotated\nworld %> discarded", annotations: "%>")
{:ok, [{"p", [], ["hello \nworld "], %{annotation: "%> annotated"}}], []}
### Annotated HTML elements
In one line
iex(39)> as_ast("One Line // a span", annotations: "//")
{:ok, [{"span", [], ["One Line"], %{annotation: "// a span", verbatim: true}}], []}
or block elements
iex(40)> [
...(40)> " : annotation",
...(40)> " text",
...(40)> "
: discarded"
...(40)> ] |> as_ast(annotations: " : ")
{:ok, [{"div", [], [" text"], %{annotation: " : annotation", verbatim: true}}], []}
### Commenting your Markdown
Although many markdown elements do not support annotations yet, they can be used to comment your markdown, w/o cluttering
the generated AST with comments
iex(41)> [
...(41)> "# Headline --> first line",
...(41)> "- item1 --> a list item",
...(41)> "- item2 --> another list item",
...(41)> "",
...(41)> " --> do not go there"
...(41)> ] |> as_ast(annotations: "-->")
{:ok, [
{"h1", [], ["Headline"], %{}},
{"ul", [], [{"li", [], ["item1 "], %{}}, {"li", [], ["item2 "], %{}}], %{}},
{"p", [], [{"a", [{"href", "http://somewhere/to/go"}], ["http://somewhere/to/go"], %{}}, " "], %{annotation: "--> do not go there"}}
], []
}
"""
alias EarmarkParser.Options
import EarmarkParser.Message, only: [sort_messages: 1]
@doc """
iex(42)> markdown = "My `code` is **best**"
...(42)> {:ok, ast, []} = EarmarkParser.as_ast(markdown)
...(42)> ast
[{"p", [], ["My ", {"code", [{"class", "inline"}], ["code"], %{line: 1}}, " is ", {"strong", [], ["best"], %{}}], %{}}]
iex(43)> markdown = "```elixir\\nIO.puts 42\\n```"
...(43)> {:ok, ast, []} = EarmarkParser.as_ast(markdown, code_class_prefix: "lang-")
...(43)> ast
[{"pre", [], [{"code", [{"class", "elixir lang-elixir"}], ["IO.puts 42"], %{}}], %{}}]
**Rationale**:
The AST is exposed in the spirit of [Floki's](https://hex.pm/packages/floki).
"""
@spec as_ast(binary()|list(binary()), any()) :: t()
def as_ast(lines, options \\ %Options{})
def as_ast(lines, %Options{} = options) do
context = _as_ast(lines, options)
messages = sort_messages(context)
messages1 = Options.add_deprecations(options, messages)
status =
case Enum.any?(messages1, fn {severity, _, _} ->
severity == :error || severity == :warning
end) do
true -> :error
_ -> :ok
end
{status, context.value, messages1}
end
def as_ast(lines, options) when is_list(options) do
as_ast(lines, struct(Options, options))
end
def as_ast(lines, options) when is_map(options) do
as_ast(lines, struct(Options, options |> Map.delete(:__struct__) |> Enum.into([])))
end
def as_ast(_, options) do
raise ArgumentError, "#{inspect options} not a legal options map or keyword list"
end
defp _as_ast(lines, options) do
{blocks, context} = EarmarkParser.Parser.parse_markdown(lines, Options.normalize(options))
EarmarkParser.AstRenderer.render(blocks, context)
end
@doc """
Accesses current hex version of the `EarmarkParser` application. Convenience for
`iex` usage.
"""
def version() do
with {:ok, version} = :application.get_key(:earmark_parser, :vsn),
do: to_string(version)
end
end
# SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/lib/earmark_parser/ 0000775 0000000 0000000 00000000000 14531042673 0020357 5 ustar 00root root 0000000 0000000 earmark_parser-1.4.39/lib/earmark_parser/ast/ 0000775 0000000 0000000 00000000000 14531042673 0021146 5 ustar 00root root 0000000 0000000 earmark_parser-1.4.39/lib/earmark_parser/ast/emitter.ex 0000664 0000000 0000000 00000001523 14531042673 0023156 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser.Ast.Emitter do
@moduledoc false
def emit(tag, content \\ [], atts \\ [], meta \\ %{})
def emit(tag, content, atts, meta) when is_binary(content) or is_tuple(content) do
{tag, _to_atts(atts), [content], meta}
end
def emit(tag, content, atts, meta) do
{tag, _to_atts(atts), content, meta}
end
defp _to_atts(atts)
defp _to_atts(nil), do: []
defp _to_atts(atts) when is_map(atts) do
atts
|> Enum.into([])
|> Enum.map(fn {name, value} -> {to_string(name), _to_string(value)} end)
end
defp _to_atts(atts) do
atts
|> Enum.map(fn {name, value} -> {to_string(name), _to_string(value)} end)
end
defp _to_string(value)
defp _to_string(value) when is_list(value), do: Enum.join(value, " ")
defp _to_string(value), do: to_string(value)
end
# SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/lib/earmark_parser/ast/inline.ex 0000664 0000000 0000000 00000032614 14531042673 0022770 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser.Ast.Inline do
@moduledoc false
alias EarmarkParser.{Context, Message, Parser}
alias EarmarkParser.Helpers.PureLinkHelpers
alias Parser.LinkParser
import EarmarkParser.Ast.Emitter
import EarmarkParser.Ast.Renderer.AstWalker
import EarmarkParser.Helpers
import EarmarkParser.Helpers.AttrParser
import EarmarkParser.Helpers.StringHelpers, only: [behead: 2]
import EarmarkParser.Helpers.AstHelpers
import Context, only: [set_value: 2]
@typep conversion_data :: {String.t(), non_neg_integer(), EarmarkParser.Context.t(), boolean()}
def convert(src, lnb, context)
def convert(list, lnb, context) when is_list(list) do
_convert(Enum.join(list, "\n"), lnb, context, true)
end
def convert(src, lnb, context) do
_convert(src, lnb, context, true)
end
defp _convert(src, current_lnb, context, use_linky?)
defp _convert(src, _, %{options: %{parse_inline: false}} = context, _) do
prepend(context, src)
end
defp _convert("", _, context, _), do: context
defp _convert(src, current_lnb, context, use_linky?) do
{src1, lnb1, context1, use_linky1?} = _convert_next(src, current_lnb, context, use_linky?)
_convert(src1, lnb1, context1, use_linky1?)
end
defp all_converters do
[
converter_for_escape: &converter_for_escape/1,
converter_for_autolink: &converter_for_autolink/1,
# only if use_linky?
converter_for_link_and_image: &converter_for_link_and_image/1,
converter_for_reflink: &converter_for_reflink/1,
converter_for_footnote: &converter_for_footnote/1,
converter_for_nolink: &converter_for_nolink/1,
#
converter_for_strikethrough_gfm: &converter_for_strikethrough_gfm/1,
converter_for_strong: &converter_for_strong/1,
converter_for_em: &converter_for_em/1,
# only for option sub_sup
converter_for_sub: &converter_for_sub/1,
converter_for_sup: &converter_for_sup/1,
#
converter_for_math_display: &converter_for_math_display/1,
converter_for_math_inline: &converter_for_math_inline/1,
#
converter_for_code: &converter_for_code/1,
converter_for_br: &converter_for_br/1,
converter_for_inline_ial: &converter_for_inline_ial/1,
converter_for_pure_link: &converter_for_pure_link/1,
converter_for_text: &converter_for_text/1
]
end
defp _convert_next(src, lnb, context, use_linky?) do
_find_and_execute_converter({src, lnb, context, use_linky?})
end
defp _find_and_execute_converter({src, lnb, context, use_linky?}) do
all_converters()
|> Enum.find_value(fn {_converter_name, converter} ->
converter.({src, lnb, context, use_linky?})
end)
end
######################
#
# Converters
#
######################
@escape_rule ~r{^\\([\\`*\{\}\[\]()\#+\-.!_>$])}
def converter_for_escape({src, lnb, context, use_linky?}) do
if match = Regex.run(@escape_rule, src) do
[match, escaped] = match
{behead(src, match), lnb, prepend(context, escaped), use_linky?}
end
end
@autolink_rgx ~r{^<([^ >]+(@|:\/)[^ >]+)>}
def converter_for_autolink({src, lnb, context, use_linky?}) do
if match = Regex.run(@autolink_rgx, src) do
[match, link, protocol] = match
{href, text} = convert_autolink(link, protocol)
out = render_link(href, text)
{behead(src, match), lnb, prepend(context, out), use_linky?}
end
end
def converter_for_pure_link({src, lnb, context, use_linky?}) do
if context.options.pure_links do
case PureLinkHelpers.convert_pure_link(src) do
{ast, length} -> {behead(src, length), lnb, prepend(context, ast), use_linky?}
_ -> nil
end
end
end
def converter_for_link_and_image({src, lnb, context, use_linky?}) do
if use_linky? do
match = LinkParser.parse_link(src, lnb)
if match do
{match1, text, href, title, link_or_img} = match
out =
case link_or_img do
:link -> output_link(context, text, href, title, lnb)
:wikilink -> maybe_output_wikilink(context, text, href, title, lnb)
:image -> render_image(text, href, title)
end
if out do
{behead(src, match1), lnb, prepend(context, out), use_linky?}
end
end
end
end
@link_text ~S{(?:\[[^]]*\]|[^][]|\])*}
@reflink ~r{^!?\[(#{@link_text})\]\s*\[([^]]*)\]}x
def converter_for_reflink({src, lnb, context, use_linky?}) do
if use_linky? do
if match = Regex.run(@reflink, src) do
{match_, alt_text, id} =
case match do
[match__, id, ""] -> {match__, id, id}
[match__, alt_text, id] -> {match__, alt_text, id}
end
case reference_link(context, match_, alt_text, id, lnb) do
{:ok, out} -> {behead(src, match_), lnb, prepend(context, out), use_linky?}
_ -> nil
end
end
end
end
def converter_for_footnote({src, lnb, context, use_linky?}) do
if use_linky? do
case Regex.run(context.rules.footnote, src) do
[match, id] ->
case footnote_link(context, match, id) do
{:ok, out} ->
{behead(src, match), lnb, _prepend_footnote(context, out, id), use_linky?}
_ ->
converter_for_text(
{src, lnb,
Message.add_message(
context,
{:error, lnb, "footnote #{id} undefined, reference to it ignored"}
), use_linky?}
)
end
_ ->
nil
end
end
end
@nolink ~r{^!?\[((?:\[[^]]*\]|[^][])*)\]}
def converter_for_nolink({src, lnb, context, use_linky?}) do
if use_linky? do
case Regex.run(@nolink, src) do
[match, id] ->
case reference_link(context, match, id, id, lnb) do
{:ok, out} -> {behead(src, match), lnb, prepend(context, out), use_linky?}
_ -> nil
end
_ ->
nil
end
end
end
################################
# Simple Tags: em, strong, del #
################################
@strikethrough_rgx ~r{\A~~(?=\S)([\s\S]*?\S)~~}
def converter_for_strikethrough_gfm({src, _, _, _} = conv_tuple) do
if match = Regex.run(@strikethrough_rgx, src) do
_converter_for_simple_tag(conv_tuple, match, "del")
end
end
@strong_rgx ~r{\A__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)}
def converter_for_strong({src, _, _, _} = conv_tuple) do
if match = Regex.run(@strong_rgx, src) do
_converter_for_simple_tag(conv_tuple, match, "strong")
end
end
@emphasis_rgx ~r{\A\b_((?:__|[\s\S])+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)}
def converter_for_em({src, _, _, _} = conv_tuple) do
if match = Regex.run(@emphasis_rgx, src) do
_converter_for_simple_tag(conv_tuple, match, "em")
end
end
@sub_rgx ~r{\A~(?=\S)(.*?\S)~}
def converter_for_sub({src, _, %{options: %{sub_sup: true}}, _} = conv_tuple) do
if match = Regex.run(@sub_rgx, src) do
_converter_for_simple_tag(conv_tuple, match, "sub")
end
end
def converter_for_sub(_), do: nil
@sup_rgx ~r{\A\^(?=\S)(.*?\S)\^}
def converter_for_sup({src, _, %{options: %{sub_sup: true}}, _} = conv_tuple) do
if match = Regex.run(@sup_rgx, src) do
_converter_for_simple_tag(conv_tuple, match, "sup")
end
end
def converter_for_sup(_), do: nil
@math_inline_rgx ~r{\A\$(?=[^\s$])([\s\S]*?[^\s\\])\$}
def converter_for_math_inline({src, lnb, %{options: %{math: true}} = context, use_linky?}) do
if match = Regex.run(@math_inline_rgx, src) do
[match, content] = match
content = String.trim(content)
out = math_inline(content, lnb)
{behead(src, match), lnb, prepend(context, out), use_linky?}
end
end
def converter_for_math_inline(_), do: nil
@math_display_rgx ~r{\A\$\$([\s\S]+?)\$\$}
def converter_for_math_display({src, lnb, %{options: %{math: true}} = context, use_linky?}) do
if match = Regex.run(@math_display_rgx, src) do
[match, content] = match
content = String.trim(content)
out = math_display(content, lnb)
{behead(src, match), lnb, prepend(context, out), use_linky?}
end
end
def converter_for_math_display(_), do: nil
@squash_ws ~r{\s+}
@code ~r{^
(`+) # $1 = Opening run of `
(.+?) # $2 = The code block
(? String.trim()
|> String.replace(@squash_ws, " ")
out = codespan(content1, lnb)
{behead(src, match), lnb, prepend(context, out), use_linky?}
end
end
@inline_ial ~r<^\s*\{:\s*(.*?)\s*}>
def converter_for_inline_ial({src, lnb, context, use_linky?}) do
if match = Regex.run(@inline_ial, src) do
[match, ial] = match
{context1, ial_attrs} = parse_attrs(context, ial, lnb)
new_tags = augment_tag_with_ial(context.value, ial_attrs)
{behead(src, match), lnb, set_value(context1, new_tags), use_linky?}
end
end
def converter_for_br({src, lnb, context, use_linky?}) do
if match = Regex.run(context.rules.br, src, return: :index) do
[{0, match_len}] = match
{behead(src, match_len), lnb, prepend(context, emit("br")), use_linky?}
end
end
@line_ending ~r{\r\n?|\n}
@spec converter_for_text(conversion_data()) :: conversion_data()
def converter_for_text({src, lnb, context, _}) do
matched =
case Regex.run(context.rules.text, src) do
[match] -> match
end
line_count = matched |> String.split(@line_ending) |> Enum.count()
ast = hard_line_breaks(matched, context.options.gfm)
ast = walk_ast(ast, &gruber_line_breaks/1)
{behead(src, matched), lnb + line_count - 1, prepend(context, ast), true}
end
######################
#
# Helpers
#
######################
defp _converter_for_simple_tag({src, lnb, context, use_linky?}, match, for_tag) do
{match1, content} =
case match do
[m, _, c] -> {m, c}
[m, c] -> {m, c}
end
context1 = _convert(content, lnb, set_value(context, []), use_linky?)
{behead(src, match1), lnb, prepend(context, emit(for_tag, context1.value |> Enum.reverse())),
use_linky?}
end
defp _prepend_footnote(context, out, id) do
context
|> Map.update!(:referenced_footnote_ids, &MapSet.put(&1, id))
|> prepend(out)
end
defp convert_autolink(link, separator)
defp convert_autolink(link, _separator = "@") do
link = if String.at(link, 6) == ":", do: behead(link, 7), else: link
text = link
href = "mailto:" <> text
{href, text}
end
defp convert_autolink(link, _separator) do
{link, link}
end
@gruber_line_break Regex.compile!(" {2,}(?>\n)", "m")
defp gruber_line_breaks(text) do
text
|> String.split(@gruber_line_break)
|> Enum.intersperse(emit("br"))
|> _remove_leading_empty()
end
@gfm_hard_line_break ~r{\\\n}
defp hard_line_breaks(text, gfm)
defp hard_line_breaks(text, false), do: text
defp hard_line_breaks(text, nil), do: text
defp hard_line_breaks(text, _) do
text
|> String.split(@gfm_hard_line_break)
|> Enum.intersperse(emit("br"))
|> _remove_leading_empty()
end
defp output_image_or_link(context, link_or_image, text, href, title, lnb)
defp output_image_or_link(_context, "!" <> _, text, href, title, _lnb) do
render_image(text, href, title)
end
defp output_image_or_link(context, _, text, href, title, lnb) do
output_link(context, text, href, title, lnb)
end
defp output_link(context, text, href, title, lnb) do
context1 = %{context | options: %{context.options | pure_links: false}}
context2 = _convert(text, lnb, set_value(context1, []), String.starts_with?(text, "!"))
if title do
emit("a", Enum.reverse(context2.value), href: href, title: title)
else
emit("a", Enum.reverse(context2.value), href: href)
end
end
defp maybe_output_wikilink(context, text, href, title, lnb) do
if context.options.wikilinks do
{tag, attrs, content, meta} = output_link(context, text, href, title, lnb)
{tag, attrs, content, Map.put(meta, :wikilink, true)}
end
end
defp reference_link(context, match, alt_text, id, lnb) do
id = id |> replace(~r{\s+}, " ") |> String.downcase()
case Map.fetch(context.links, id) do
{:ok, link} ->
{:ok, output_image_or_link(context, match, alt_text, link.url, link.title, lnb)}
_ ->
nil
end
end
defp footnote_link(context, _match, id) do
case Map.fetch(context.footnotes, id) do
{:ok, _} ->
{:ok, render_footnote_link("fn:#{id}", "fnref:#{id}", id)}
_ ->
nil
end
end
defp prepend(%Context{} = context, prep) do
_prepend(context, prep)
end
defp _prepend(context, value)
defp _prepend(context, [bin | rest]) when is_binary(bin) do
_prepend(_prepend(context, bin), rest)
end
defp _prepend(%Context{value: [str | rest]} = context, prep)
when is_binary(str) and is_binary(prep) do
%{context | value: [str <> prep | rest]}
end
defp _prepend(%Context{value: value} = context, prep) when is_list(prep) do
%{context | value: Enum.reverse(prep) ++ value}
end
defp _prepend(%Context{value: value} = context, prep) do
%{context | value: [prep | value]}
end
defp _remove_leading_empty(list)
defp _remove_leading_empty(["" | rest]), do: rest
defp _remove_leading_empty(list), do: list
end
# SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/lib/earmark_parser/ast/renderer/ 0000775 0000000 0000000 00000000000 14531042673 0022754 5 ustar 00root root 0000000 0000000 earmark_parser-1.4.39/lib/earmark_parser/ast/renderer/ast_walker.ex 0000664 0000000 0000000 00000003557 14531042673 0025460 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser.Ast.Renderer.AstWalker do
@moduledoc false
def walk(anything, fun, ignore_map_keys \\ false), do: _walk(anything, fun, ignore_map_keys, false)
def walk_ast(ast, fun), do: _walk_ast(ast, fun, [])
defp _walk(ast, fun, ignore_map_keys, child_of_map)
defp _walk([], _fun, _ignore_map_keys, _child_of_map), do: []
defp _walk(list, fun, ignore_map_keys, _child_of_map) when is_list(list) do
Enum.map(list, &(_walk(&1, fun, ignore_map_keys, false)))
end
defp _walk(map, fun, ignore_map_keys, _child_of_map) when is_map(map) do
map
|> Enum.into(%{}, &(_walk(&1, fun, ignore_map_keys, true)))
end
defp _walk(tuple, fun, ignore_map_keys, child_of_map) when is_tuple(tuple) do
if child_of_map && ignore_map_keys do
_walk_map_element(tuple, fun, ignore_map_keys)
else
tuple
|> Tuple.to_list
|> Enum.map(&(_walk(&1, fun, ignore_map_keys, false)))
|> List.to_tuple
end
end
defp _walk(ele, fun, _ignore_map_keys, _child_of_map), do: fun.(ele)
defp _walk_map_element({key, value}, fun, ignore_map_keys) do
{key, _walk(value, fun, ignore_map_keys, false)}
end
defp _walk_ast(ast, fun, res)
defp _walk_ast([], _fun, res), do: Enum.reverse(res)
defp _walk_ast(stringy, fun, res) when is_binary(stringy), do: _walk_ast([stringy], fun, res)
defp _walk_ast([stringy|rest], fun, res) when is_binary(stringy) do
res1 =
case fun.(stringy) do
[] -> res
[_|_]=trans -> List.flatten([Enum.reverse(trans)|res])
stringy1 -> [stringy1|res]
end
_walk_ast(rest, fun, res1)
end
defp _walk_ast([{tag, atts, content, meta}|rest], fun, res) do
_walk_ast(rest, fun, [{tag, atts, _walk_ast(content, fun, []), meta}|res])
end
defp _walk_ast([list|rest], fun, res) when is_list(list) do
_walk_ast(rest, fun, [_walk_ast(list, fun, [])|res])
end
end
earmark_parser-1.4.39/lib/earmark_parser/ast/renderer/footnote_renderer.ex 0000664 0000000 0000000 00000002624 14531042673 0027041 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser.Ast.Renderer.FootnoteRenderer do
import EarmarkParser.Ast.Emitter
alias EarmarkParser.{AstRenderer, Block, Context, Message}
import Context, only: [clear_value: 1, prepend: 2]
@moduledoc false
@empty_set MapSet.new([])
def render_defined_fns(%Block.FnList{blocks: footnotes}, context) do
{elements, errors} = render_footnote_blocks(footnotes, context)
ast =
emit(
"div",
[
emit("hr"),
emit("ol", elements)
],
class: "footnotes"
)
prepend(context, ast) |> Message.add_messages(errors)
end
defp _render_footnote_def(%Block.FnDef{blocks: blocks, id: id}, {ast, errors, context}=acc) do
if MapSet.member?(context.referenced_footnote_ids, id) do
context1 = AstRenderer.render(blocks, clear_value(context))
a_attrs = %{title: "return to article", class: "reversefootnote", href: "#fnref:#{id}"}
footnote_li_ast =
emit("li", [emit("a", ["↩"], a_attrs) | context1.value],
id: "fn:#{id}")
{[footnote_li_ast|ast], MapSet.union(errors, context1.options.messages), context}
else
acc
end
end
defp render_footnote_blocks(footnotes, context) do
{elements, errors, _} =
footnotes
|> Enum.reduce({[], @empty_set, context}, &_render_footnote_def/2)
{elements|>Enum.reverse, errors}
end
end
# SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/lib/earmark_parser/ast/renderer/html_renderer.ex 0000664 0000000 0000000 00000001655 14531042673 0026153 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser.Ast.Renderer.HtmlRenderer do
import EarmarkParser.Context, only: [prepend: 2]
import EarmarkParser.Helpers.HtmlParser
import EarmarkParser.Helpers.AstHelpers, only: [annotate: 2]
@moduledoc false
# Structural Renderer for html blocks
def render_html_block(lines, context, annotation)
def render_html_block(lines, context, annotation) do
[tag] = parse_html(lines)
tag_ = if annotation, do: annotate(tag, annotation), else: tag
prepend(context, tag_)
end
def render_html_oneline([line|_], context, annotation \\ []) do
[tag|rest] = parse_html([line])
tag_ = if annotation, do: annotate(tag, annotation), else: tag
prepend(context, [tag_|rest])
end
@html_comment_start ~r{\A\s*.*\z}
def render_html_comment_line(line) do
line
|> String.replace(@html_comment_start, "")
|> String.replace(@html_comment_end, "")
end
end
earmark_parser-1.4.39/lib/earmark_parser/ast/renderer/table_renderer.ex 0000664 0000000 0000000 00000002727 14531042673 0026277 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser.Ast.Renderer.TableRenderer do
@moduledoc false
alias EarmarkParser.Ast.Inline
alias EarmarkParser.Context
import EarmarkParser.Ast.Emitter
def render_header(header, lnb, aligns, context) do
{th_ast, context1} =
header
|> Enum.zip(aligns)
|> Enum.map_reduce(context, &_render_col(&1, &2, lnb, "th"))
{emit("thead", emit("tr", th_ast)), context1}
end
def render_rows(rows, lnb, aligns, context) do
{rows1, context1} =
rows
|> Enum.zip(Stream.iterate(lnb, &(&1 + 1)))
|> Enum.map_reduce(context, &_render_row(&1, &2, aligns))
{[emit("tbody", rows1)], context1}
end
defp _render_cols(row, lnb, aligns, context, coltype \\ "td") do
row
|> Enum.zip(aligns)
|> Enum.map_reduce(context, &_render_col(&1, &2, lnb, coltype))
end
defp _render_col({col, align}, context, lnb, coltype) do
context1 = Inline.convert(col, lnb, Context.clear_value(context))
{emit(coltype, context1.value |> Enum.reverse, _align_to_style(align)), context1}
end
defp _render_row({row, lnb}, context, aligns) do
{ast, context1} = _render_cols(row, lnb, aligns, context)
{emit("tr", ast), context1}
end
defp _align_to_style(align)
defp _align_to_style(:left), do: [{"style", "text-align: left;"}]
defp _align_to_style(:right), do: [{"style", "text-align: right;"}]
defp _align_to_style(:center), do: [{"style", "text-align: center;"}]
end
# SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/lib/earmark_parser/ast_renderer.ex 0000664 0000000 0000000 00000015734 14531042673 0023404 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser.AstRenderer do
alias EarmarkParser.Block
alias EarmarkParser.Context
alias EarmarkParser.Options
import Context, only: [clear_value: 1, modify_value: 2, prepend: 2, prepend: 3]
import EarmarkParser.Ast.Emitter
import EarmarkParser.Ast.Inline, only: [convert: 3]
import EarmarkParser.Helpers.AstHelpers
import EarmarkParser.Ast.Renderer.{HtmlRenderer, FootnoteRenderer, TableRenderer}
@moduledoc false
def render(blocks, context = %Context{options: %Options{}}, loose? \\ true) do
_render(blocks, context, loose?)
end
defp _render(blocks, context, loose?)
defp _render([], context, _loose?), do: context
defp _render([block | blocks], context, loose?) do
context1 = render_block(block, clear_value(context), loose?)
_render(blocks, prepend(context1, context), loose?)
end
defp render_block(block, context, loose?)
#############
# Paragraph #
#############
defp render_block(%Block.Para{lnb: lnb, lines: lines, attrs: attrs} = para, context, _loose?) do
context1 = convert(lines, lnb, context)
value = context1.value |> Enum.reverse()
ast =
emit("p", value, attrs)
|> annotate(para)
prepend(context, ast, context1)
end
########
# Html #
########
defp render_block(%Block.Html{annotation: annotation, html: html}, context, _loose?) do
render_html_block(html, context, annotation)
end
defp render_block(%Block.HtmlOneline{annotation: annotation, html: html}, context, _loose?) do
render_html_oneline(html, context, annotation)
end
defp render_block(%Block.HtmlComment{lines: lines}, context, _loose?) do
lines1 = lines |> Enum.map(&render_html_comment_line/1)
prepend(context, emit(:comment, lines1, [], %{comment: true}))
end
#########
# Ruler #
#########
defp render_block(%Block.Ruler{type: "-", attrs: attrs}, context, _loose?) do
prepend(context, emit("hr", [], merge_attrs(attrs, %{"class" => "thin"})))
end
defp render_block(%Block.Ruler{type: "_", attrs: attrs}, context, _loose?) do
prepend(context, emit("hr", [], merge_attrs(attrs, %{"class" => "medium"})))
end
defp render_block(%Block.Ruler{type: "*", attrs: attrs}, context, _loose?) do
prepend(context, emit("hr", [], merge_attrs(attrs, %{"class" => "thick"})))
end
###########
# Heading #
###########
defp render_block(
%Block.Heading{lnb: lnb, level: level, content: content, attrs: attrs},
context,
_loose?
) do
context1 = convert(content, lnb, clear_value(context))
modify_value(
context1,
fn _ ->
[
emit(
"h#{level}",
context1.value |> Enum.reverse(),
attrs)
]
end
)
end
##############
# Blockquote #
##############
defp render_block(%Block.BlockQuote{blocks: blocks, attrs: attrs}, context, _loose?) do
context1 = render(blocks, clear_value(context))
modify_value(context1, fn ast ->
[emit("blockquote", ast, attrs)]
end)
end
#########
# Table #
#########
defp render_block(
%Block.Table{lnb: lnb, header: header, rows: rows, alignments: aligns, attrs: attrs},
context,
_loose?
) do
header_offset =
if header do
2 # 1 line for header text, 1 line for header separator
else
0
end
{rows_ast, context1} = render_rows(rows, lnb + header_offset, aligns, context)
{rows_ast1, context2} =
if header do
{header_ast, context3} = render_header(header, lnb, aligns, context1)
{[header_ast | rows_ast], context3}
else
{rows_ast, context1}
end
prepend(
clear_value(context2),
emit("table", rows_ast1, attrs)
)
end
########
# Code #
########
defp render_block(
%Block.Code{language: language, attrs: attrs} = block,
context = %Context{options: options},
_loose?
) do
classes =
if language && language != "",
do: [code_classes(language, options.code_class_prefix)],
else: []
lines = render_code(block)
prepend(
context,
emit("pre", emit("code", lines, classes), attrs)
)
end
#########
# Lists #
#########
@start_rgx ~r{\A\d+}
defp render_block(
%Block.List{type: type, bullet: bullet, blocks: items, attrs: attrs},
context,
_loose?
) do
context1 = render(items, clear_value(context))
start_map =
case bullet && Regex.run(@start_rgx, bullet) do
nil -> %{}
["1"] -> %{}
[start1] -> %{start: _normalize_start(start1)}
end
prepend(
context,
emit(to_string(type), context1.value, merge_attrs(attrs, start_map)),
context1
)
end
# format a spaced list item
defp render_block(
%Block.ListItem{blocks: blocks, attrs: attrs, loose?: loose?},
context,
_loose?
) do
context1 = render(blocks, clear_value(context), loose?)
prepend(
context,
emit("li", context1.value, attrs),
context1
)
end
########
# Text #
########
defp render_block(%Block.Text{line: line, lnb: lnb}, context, loose?) do
context1 = convert(line, lnb, clear_value(context))
ast = context1.value |> Enum.reverse()
if loose? do
modify_value(context1, fn _ -> [emit("p", ast)] end)
else
modify_value(context1, fn _ -> ast end)
end
end
##################
# Footnote Block #
##################
@empty_set MapSet.new([])
defp render_block(%Block.FnList{}=fn_list, context, _loose?) do
if MapSet.equal?(context.referenced_footnote_ids, @empty_set) do
context
else
render_defined_fns(fn_list, context)
end
end
#######################################
# Isolated IALs are rendered as paras #
#######################################
defp render_block(%Block.Ial{verbatim: verbatim}, context, _loose?) do
prepend(context, emit("p", "{:#{verbatim}}"))
end
####################
# IDDef is ignored #
####################
defp render_block(%Block.IdDef{}, context, _loose?), do: context
# Helpers
# -------
# Seems to be dead code but as GFM list handling is broken maybe we have a bug
# that does not call this correctly, anyhow AST triplets do not exits anymore
# so this code would break if called
# defp _fix_text_lines(ast, loose?)
# defp _fix_text_lines(ast, false), do: Enum.map(ast, &_fix_tight_text_line/1)
# defp _fix_text_lines(ast, true), do: Enum.map(ast, &_fix_loose_text_line/1)
# defp _fix_loose_text_line(node)
# defp _fix_loose_text_line({:text, _, lines}), do: emit("p", lines)
# defp _fix_loose_text_line(node), do: node
# defp _fix_tight_text_line(node)
# defp _fix_tight_text_line({:text, _, lines}), do: lines
# defp _fix_tight_text_line(node), do: node
# INLINE CANDIDATE
defp _normalize_start(start) do
case String.trim_leading(start, "0") do
"" -> "0"
start1 -> start1
end
end
end
# SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/lib/earmark_parser/block/ 0000775 0000000 0000000 00000000000 14531042673 0021451 5 ustar 00root root 0000000 0000000 earmark_parser-1.4.39/lib/earmark_parser/block/block_quote.ex 0000664 0000000 0000000 00000000246 14531042673 0024320 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser.Block.BlockQuote do
@moduledoc false
defstruct lnb: 0, annotation: nil, attrs: nil, blocks: []
end
# SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/lib/earmark_parser/block/code.ex 0000664 0000000 0000000 00000000256 14531042673 0022724 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser.Block.Code do
@moduledoc false
defstruct lnb: 0, annotation: nil, attrs: nil, lines: [], language: nil
end
# SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/lib/earmark_parser/block/fn_def.ex 0000664 0000000 0000000 00000000267 14531042673 0023235 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser.Block.FnDef do
@moduledoc false
defstruct lnb: 0, annotation: nil, attrs: nil, id: nil, number: nil, blocks: []
end
# SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/lib/earmark_parser/block/fn_list.ex 0000664 0000000 0000000 00000000253 14531042673 0023445 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser.Block.FnList do
@moduledoc false
defstruct lnb: 0, annotation: nil, attrs: ".footnotes", blocks: []
end
# SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/lib/earmark_parser/block/heading.ex 0000664 0000000 0000000 00000000261 14531042673 0023405 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser.Block.Heading do
@moduledoc false
defstruct lnb: 0, annotation: nil, attrs: nil, content: nil, level: nil
end
# SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/lib/earmark_parser/block/html.ex 0000664 0000000 0000000 00000000250 14531042673 0022750 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser.Block.Html do
@moduledoc false
defstruct lnb: 0, annotation: nil, attrs: nil, html: [], tag: nil
end
# SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/lib/earmark_parser/block/html_comment.ex 0000664 0000000 0000000 00000000246 14531042673 0024477 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser.Block.HtmlComment do
@moduledoc false
defstruct lnb: 0, annotation: nil, attrs: nil, lines: []
end
# SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/lib/earmark_parser/block/html_oneline.ex 0000664 0000000 0000000 00000000245 14531042673 0024465 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser.Block.HtmlOneline do
@moduledoc false
defstruct lnb: 0, annotation: nil, attrs: nil, html: ""
end
# SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/lib/earmark_parser/block/ial.ex 0000664 0000000 0000000 00000000257 14531042673 0022560 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser.Block.Ial do
@moduledoc false
defstruct lnb: 0, annotation: nil, attrs: nil, content: nil, verbatim: ""
end
# SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/lib/earmark_parser/block/id_def.ex 0000664 0000000 0000000 00000000264 14531042673 0023223 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser.Block.IdDef do
@moduledoc false
defstruct lnb: 0, annotation: nil, attrs: nil, id: nil, url: nil, title: nil
end
# SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/lib/earmark_parser/block/list.ex 0000664 0000000 0000000 00000000620 14531042673 0022760 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser.Block.List do
@moduledoc false
defstruct annotation: nil,
attrs: nil,
blocks: [],
lines: [],
bullet: "-",
indent: 0,
lnb: 0,
loose?: false,
pending: {nil, 0},
spaced?: false,
start: "",
type: :ul
end
# SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/lib/earmark_parser/block/list_item.ex 0000664 0000000 0000000 00000000455 14531042673 0024004 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser.Block.ListItem do
@moduledoc false
defstruct attrs: nil,
blocks: [],
bullet: "",
lnb: 0,
annotation: nil,
loose?: false,
spaced?: true,
type: :ul
end
# SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/lib/earmark_parser/block/para.ex 0000664 0000000 0000000 00000000237 14531042673 0022734 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser.Block.Para do
@moduledoc false
defstruct lnb: 0, annotation: nil, attrs: nil, lines: []
end
# SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/lib/earmark_parser/block/ruler.ex 0000664 0000000 0000000 00000000240 14531042673 0023134 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser.Block.Ruler do
@moduledoc false
defstruct lnb: 0, annotation: nil, attrs: nil, type: nil
end
# SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/lib/earmark_parser/block/table.ex 0000664 0000000 0000000 00000000434 14531042673 0023077 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser.Block.Table do
@moduledoc false
defstruct lnb: 0, annotation: nil, attrs: nil, rows: [], header: nil, alignments: []
def new_for_columns(n) do
%__MODULE__{alignments: Elixir.List.duplicate(:left, n)}
end
end
# SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/lib/earmark_parser/block/text.ex 0000664 0000000 0000000 00000000236 14531042673 0022774 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser.Block.Text do
@moduledoc false
defstruct attrs: nil, lnb: 0, annotation: nil, line: ""
end
# SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/lib/earmark_parser/context.ex 0000664 0000000 0000000 00000007770 14531042673 0022414 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser.Context do
@moduledoc false
alias EarmarkParser.Options
@type t :: %__MODULE__{
options: EarmarkParser.Options.t(),
links: map(),
footnotes: map(),
referenced_footnote_ids: MapSet.t(String.t()),
value: String.t() | [String.t()]
}
defstruct options: %EarmarkParser.Options{},
links: Map.new(),
rules: nil,
footnotes: Map.new(),
referenced_footnote_ids: MapSet.new([]),
value: []
##############################################################################
# Handle adding option specific rules and processors #
##############################################################################
@doc false
def modify_value(%__MODULE__{value: value} = context, fun) do
nv = fun.(value)
%{context | value: nv}
end
@doc false
def prepend(context1, ast_or_context, context2_or_nil \\ nil)
def prepend(%__MODULE__{} = context1, %__MODULE__{} = context2, nil) do
context1
|> _merge_contexts(context2)
|> _prepend(context2.value)
end
def prepend(%__MODULE__{} = context1, ast, nil) do
context1
|> _prepend(ast)
end
def prepend(%__MODULE__{} = context1, ast, %__MODULE__{} = context2) do
context1
|> _merge_contexts(context2)
|> _prepend(ast)
end
defp _merge_contexts(
%__MODULE__{referenced_footnote_ids: orig} = context1,
%__MODULE__{referenced_footnote_ids: new} = context2
) do
context_ = _merge_messages(context1, context2)
%{context_| referenced_footnote_ids: MapSet.union(orig, new)}
end
defp _merge_messages(context, context_or_messages)
defp _merge_messages(context, %__MODULE__{options: %Options{messages: messages}}) do
_merge_messages(context, messages)
end
defp _merge_messages(context, messages) do
%{context | options: %{context.options|messages: MapSet.union(context.options.messages, messages)}}
end
defp _prepend(ctxt, []), do: ctxt
defp _prepend(%{value: value} = ctxt, {:comment, _, _, _} = ct),
do: %{ctxt | value: [ct | value]}
defp _prepend(%{value: value} = ctxt, tuple) when is_tuple(tuple) do
%{ctxt | value: [tuple | value] |> List.flatten()}
end
defp _prepend(%{value: value} = ctxt, list) when is_list(list),
do: %{ctxt | value: List.flatten(list ++ value)}
@doc """
Convenience method to prepend to the value list
"""
def set_value(%__MODULE__{} = ctx, value) do
%{ctx | value: value}
end
def clear_value(%__MODULE__{} = ctx), do: %{ctx | value: []}
# this is called by the command line processor to update
# the inline-specific rules in light of any options
def update_context(context = %EarmarkParser.Context{options: options}) do
%{context | rules: rules_for(options)}
end
# ( "[" .*? "]"n or anything w/o {"[", "]"}* or "]" ) *
@link_text ~S{(?:\[[^]]*\]|[^][]|\])*}
# "
# @href ~S{\s*(.*?)>?(?:\s+['"](.*?)['"])?\s*}
defp basic_rules do
[
br: ~r<^ {2,}\n(?!\s*$)>,
text: ~r<^[\s\S]+?(?=[\\
]
end
defp rules_for(options) do
subsup =
if options.sub_sup do
"~^"
else
""
end
math =
if options.math do
"$"
else
""
end
rule_updates =
if options.gfm do
rules = [
text: ~r{^[\s\S]+?(?=~~|[\\ Enum.into(%{})
end
end
# SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/lib/earmark_parser/enum/ 0000775 0000000 0000000 00000000000 14531042673 0021323 5 ustar 00root root 0000000 0000000 earmark_parser-1.4.39/lib/earmark_parser/enum/ext.ex 0000664 0000000 0000000 00000003731 14531042673 0022465 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser.Enum.Ext do
@moduledoc ~S"""
Some extensions of Enum functions
"""
@doc ~S"""
`reduce_with_end` is like `Enum.reduce` for lists, but the reducer function is called for
each element of the list with the tuple `{:element, element}` and the accumulator and once
more at the end with `:end` and the accumulator
iex(1)> reducer =
...(1)> fn {:element, nil}, {partial, result} -> {[], [Enum.sum(partial)|result]}
...(1)> {:element, val}, {partial, result} -> {[val|partial], result}
...(1)> :end, {partial, result} -> [Enum.sum(partial)|result] |> Enum.reverse
...(1)> end
...(1)> [1, 2, nil, 4, 1, 0, nil, 3, 2, 2]
...(1)> |> reduce_with_end({[], []}, reducer)
[3, 5, 7]
**N.B.** that in the treatment of `:end` we can change the shape of the accumulator w/o any
penalty concerning the complexity of the reducer function
"""
def reduce_with_end(collection, initial_acc, reducer_fn)
def reduce_with_end([], acc, reducer_fn) do
reducer_fn.(:end, acc)
end
def reduce_with_end([ele|rest], acc, reducer_fn) do
reduce_with_end(rest, reducer_fn.({:element, ele}, acc), reducer_fn)
end
@doc ~S"""
Like map_reduce but reversing the list
iex(2)> replace_nil_and_count = fn ele, acc ->
...(2)> if ele, do: {ele, acc}, else: {"", acc + 1}
...(2)> end
...(2)> ["y", nil, "u", nil, nil, "a", nil] |> reverse_map_reduce(0, replace_nil_and_count)
{ ["", "a", "", "", "u", "", "y"], 4 }
"""
def reverse_map_reduce(list, initial, fun) do
_reverse_map_reduce(list, initial, [], fun)
end
# Helpers {{{
defp _reverse_map_reduce(list, acc, result, fun)
defp _reverse_map_reduce([], acc, result, _fun), do: {result, acc}
defp _reverse_map_reduce([fst|rst], acc, result, fun) do
{new_ele, new_acc} = fun.(fst, acc)
_reverse_map_reduce(rst, new_acc, [new_ele|result], fun)
end
# }}}
end
# SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/lib/earmark_parser/helpers.ex 0000664 0000000 0000000 00000004542 14531042673 0022364 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser.Helpers do
@moduledoc false
@doc """
Expand tabs to multiples of 4 columns
"""
def expand_tabs(line) do
Regex.replace(~r{(.*?)\t}, line, &expander/2)
end
@trailing_ial_rgx ~r< (?x
@doc ~S"""
Returns a tuple containing a potentially present IAL and the line w/o the IAL
iex(1)> extract_ial("# A headline")
{nil, "# A headline"}
iex(2)> extract_ial("# A classy headline{:.classy}")
{".classy", "# A classy headline"}
An IAL line, remains an IAL line though
iex(3)> extract_ial("{:.line-ial}")
{nil, "{:.line-ial}"}
"""
def extract_ial(line) do
case Regex.split(@trailing_ial_rgx, line, include_captures: true, parts: 2, on: [:ial]) do
[_] -> {nil, line}
[line_, "{:" <> ial, _] ->
ial_ =
ial
|> String.trim_trailing("}")
|> String.trim()
{ial_, String.trim_trailing(line_)}
end
end
defp expander(_, leader) do
extra = 4 - rem(String.length(leader), 4)
leader <> pad(extra)
end
@doc """
Remove newlines at end of line and optionally annotations
"""
# def remove_line_ending(line, annotation \\ nil)
def remove_line_ending(line, nil) do
_trim_line({line, nil})
end
def remove_line_ending(line, annotation) do
case Regex.run(annotation, line) do
nil -> _trim_line({line, nil})
match -> match |> tl() |> List.to_tuple |> _trim_line()
end
end
defp _trim_line({line, annot}), do: {line |> String.trim_trailing("\n") |> String.trim_trailing("\r"), annot}
defp pad(1), do: " "
defp pad(2), do: " "
defp pad(3), do: " "
defp pad(4), do: " "
@doc """
`Regex.replace` with the arguments in the correct order
"""
def replace(text, regex, replacement, options \\ []) do
Regex.replace(regex, text, replacement, options)
end
@doc """
Replace <, >, and quotes with the corresponding entities. If
`encode` is true, convert ampersands, too, otherwise only
convert non-entity ampersands.
"""
@amp_rgx ~r{&(?!#?\w+;)}
def escape(html), do: _escape(Regex.replace(@amp_rgx, html, "&"))
defp _escape(html) do
html
|> String.replace("<", "<")
|> String.replace(">", ">")
|> String.replace("\"", """)
|> String.replace("'", "'")
end
end
# SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/lib/earmark_parser/helpers/ 0000775 0000000 0000000 00000000000 14531042673 0022021 5 ustar 00root root 0000000 0000000 earmark_parser-1.4.39/lib/earmark_parser/helpers/ast_helpers.ex 0000664 0000000 0000000 00000006307 14531042673 0024676 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser.Helpers.AstHelpers do
@moduledoc false
import EarmarkParser.Ast.Emitter
import EarmarkParser.Helpers
alias EarmarkParser.Block
@doc false
def annotate(node, from_block)
def annotate(node, %{annotation: nil}), do: node
def annotate({tag, atts, children, meta}, %{annotation: annotation}),
do: {tag, atts, children, Map.put(meta, :annotation, annotation)}
def annotate({tag, atts, children, meta}, annotation),
do: {tag, atts, children, Map.put(meta, :annotation, annotation)}
@doc false
def attrs_to_string_keys(key_value_pair)
def attrs_to_string_keys({k, vs}) when is_list(vs) do
{to_string(k), Enum.join(vs, " ")}
end
def attrs_to_string_keys({k, vs}) do
{to_string(k),to_string(vs)}
end
@doc false
def augment_tag_with_ial(tags, ial)
def augment_tag_with_ial([{t, a, c, m}|tags], atts) do
[{t, merge_attrs(a, atts), c, m}|tags]
end
def augment_tag_with_ial([], _atts) do
[]
end
@doc false
def code_classes(language, prefix) do
classes =
["" | String.split(prefix || "")]
|> Enum.map(fn pfx -> "#{pfx}#{language}" end)
{"class", classes |> Enum.join(" ")}
end
@doc false
def math_inline(text, lnb) do
emit("code", text, [class: "math-inline"], %{line: lnb})
end
def math_display(text, lnb) do
emit("code", text, [class: "math-display"], %{line: lnb})
end
@doc false
def codespan(text, lnb) do
emit("code", text, [class: "inline"], %{line: lnb})
end
@doc false
def render_footnote_link(ref, backref, number) do
emit("a", to_string(number), href: "##{ref}", id: backref, class: "footnote", title: "see footnote")
end
@doc false
def render_code(%Block.Code{lines: lines}) do
lines |> Enum.join("\n")
end
@remove_escapes ~r{ \\ (?! \\ ) }x
@doc false
def render_image(text, href, title) do
alt = text |> escape() |> String.replace(@remove_escapes, "")
if title do
emit("img", [], src: href, alt: alt, title: title)
else
emit("img", [], src: href, alt: alt)
end
end
@doc false
def render_link(url, text) do
emit("a", text, href: _encode(url))
end
##############################################
# add attributes to the outer tag in a block #
##############################################
@verbatims ~r<%[\da-f]{2}>i
defp _encode(url) do
url
|> String.split(@verbatims, include_captures: true)
|> Enum.chunk_every(2)
|> Enum.map(&_encode_chunk/1)
|> IO.chardata_to_string
end
defp _encode_chunk([encodable, verbatim]), do: [URI.encode(encodable), verbatim]
defp _encode_chunk([encodable]), do: URI.encode(encodable)
@doc false
def merge_attrs(maybe_atts, new_atts)
def merge_attrs(nil, new_atts), do: new_atts
def merge_attrs(atts, new) when is_list(atts) do
atts
|> Enum.into(%{})
|> merge_attrs(new)
end
def merge_attrs(atts, new) do
atts
|> Map.merge(new, &_value_merger/3)
|> Enum.into([])
|> Enum.map(&attrs_to_string_keys/1)
end
defp _value_merger(key, val1, val2)
defp _value_merger(_, val1, val2) when is_list(val1) do
val1 ++ [val2]
end
defp _value_merger(_, val1, val2) do
[val1, val2]
end
end
# SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/lib/earmark_parser/helpers/attr_parser.ex 0000664 0000000 0000000 00000004142 14531042673 0024706 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser.Helpers.AttrParser do
@moduledoc false
import EarmarkParser.Helpers.StringHelpers, only: [ behead: 2 ]
import EarmarkParser.Message, only: [add_message: 2]
@type errorlist :: list(String.t)
def parse_attrs(context, attrs, lnb) do
{ attrs, errors } = _parse_attrs(%{}, attrs, [], lnb)
{ add_errors(context, errors, lnb), attrs }
end
defp _parse_attrs(dict, attrs, errors, lnb) do
cond do
Regex.match?(~r{^\s*$}, attrs) -> {dict, errors}
match = Regex.run(~r{^\.(\S+)\s*}, attrs) ->
[ leader, class ] = match
Map.update(dict, "class", [ class ], &[ class | &1])
|> _parse_attrs(behead(attrs, leader), errors, lnb)
match = Regex.run(~r{^\#(\S+)\s*}, attrs) ->
[ leader, id ] = match
Map.update(dict, "id", [ id ], &[ id | &1])
|> _parse_attrs(behead(attrs, leader), errors, lnb)
# Might we being running into escape issues here too?
match = Regex.run(~r{^(\S+)=\'([^\']*)'\s*}, attrs) -> #'
[ leader, name, value ] = match
Map.update(dict, name, [ value ], &[ value | &1])
|> _parse_attrs(behead(attrs, leader), errors, lnb)
# Might we being running into escape issues here too?
match = Regex.run(~r{^(\S+)=\"([^\"]*)"\s*}, attrs) -> #"
[ leader, name, value ] = match
Map.update(dict, name, [ value ], &[ value | &1])
|> _parse_attrs(behead(attrs, leader), errors, lnb)
match = Regex.run(~r{^(\S+)=(\S+)\s*}, attrs) ->
[ leader, name, value ] = match
Map.update(dict, name, [ value ], &[ value | &1])
|> _parse_attrs(behead(attrs, leader), errors, lnb)
match = Regex.run(~r{^(\S+)\s*(.*)}, attrs) ->
[ _, incorrect, rest ] = match
_parse_attrs(dict, rest, [ incorrect | errors ], lnb)
:otherwise ->
{dict, [attrs | errors ]}
end
end
defp add_errors(context, [], _lnb), do: context
defp add_errors(context, errors, lnb), do: add_message(context, {:warning, lnb, "Illegal attributes #{inspect errors} ignored in IAL"})
end
# SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/lib/earmark_parser/helpers/html_parser.ex 0000664 0000000 0000000 00000005350 14531042673 0024702 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser.Helpers.HtmlParser do
@moduledoc false
import EarmarkParser.Helpers.StringHelpers, only: [behead: 2]
import EarmarkParser.LineScanner, only: [void_tag?: 1]
def parse_html(lines)
def parse_html([tag_line|rest]) do
case _parse_tag(tag_line) do
{ :ok, tag, "" } -> [_parse_rest(rest, tag, [])]
{ :ok, tag, suffix } -> [_parse_rest(rest, tag, [suffix])]
{ :ext, tag, "" } -> [_parse_rest(rest, tag, [])]
{ :ext, tag, suffix } -> [_parse_rest(rest, tag, []), [suffix]]
end
end
# Parse One Tag
# -------------
@quoted_attr ~r{\A ([-\w]+) \s* = \s* (["']) (.*?) \2 \s*}x
@unquoted_attr ~r{\A ([-\w]+) (?: \s* = \s* ([^&\s>]*))? \s*}x
defp _parse_atts(string, tag, atts) do
case Regex.run(@quoted_attr, string) do
[all, name, _delim, value] -> _parse_atts(behead(string, all), tag, [{name, value}|atts])
_ -> case Regex.run(@unquoted_attr, string) do
[all, name, value] -> _parse_atts(behead(string, all), tag, [{name, value}|atts])
[all, name] -> _parse_atts(behead(string, all), tag, [{name, name}|atts])
_ -> _parse_tag_tail(string, tag, atts)
end
end
end
# Are leading and trailing "-"s ok?
@tag_head ~r{\A \s* <([-\w]+) \s*}x
defp _parse_tag(string) do
case Regex.run(@tag_head, string) do
[all, tag] -> _parse_atts(behead(string, all), tag, [])
end
end
@tag_tail ~r{\A .*? (/?)> \s* (.*) \z}x
defp _parse_tag_tail(string, tag, atts) do
case Regex.run(@tag_tail, string) do
[_, closing, suffix] ->
suffix1 = String.replace(suffix, ~r{\s*#{tag}>.*}, "")
_close_tag_tail(tag, atts, closing != "", suffix1)
end
end
defp _close_tag_tail(tag, atts, closing?, suffix) do
if closing? || void_tag?(tag) do
{:ext, {tag, Enum.reverse(atts)}, suffix }
else
{:ok, {tag, Enum.reverse(atts)}, suffix }
end
end
# Iterate over lines inside a tag
# -------------------------------
@verbatim %{verbatim: true}
defp _parse_rest(rest, tag_tpl, lines)
defp _parse_rest([], tag_tpl, lines) do
tag_tpl |> Tuple.append(Enum.reverse(lines)) |> Tuple.append(@verbatim)
end
defp _parse_rest([last_line], {tag, _}=tag_tpl, lines) do
case Regex.run(~r{\A\s*#{tag}>\s*(.*)}, last_line) do
nil -> tag_tpl |> Tuple.append(Enum.reverse([last_line|lines])) |> Tuple.append(@verbatim)
[_, ""] -> tag_tpl |> Tuple.append(Enum.reverse(lines)) |> Tuple.append(@verbatim)
[_, suffix] -> [tag_tpl |> Tuple.append(Enum.reverse(lines)) |> Tuple.append(@verbatim), suffix]
end
end
defp _parse_rest([inner_line|rest], tag_tpl, lines) do
_parse_rest(rest, tag_tpl, [inner_line|lines])
end
end
earmark_parser-1.4.39/lib/earmark_parser/helpers/leex_helpers.ex 0000664 0000000 0000000 00000001400 14531042673 0025031 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser.Helpers.LeexHelpers do
@moduledoc false
@doc """
Allows to lex an Elixir string with a leex lexer and returns
the tokens as needed for a yecc parser.
"""
def lex text, with: lexer do
case text
|> String.to_charlist()
|> lexer.string() do
{:ok, tokens, _} -> tokens
end
end
def tokenize line, with: lexer do
{:ok, tokens, _} =
line
|> to_charlist()
|> lexer.string()
elixirize_tokens(tokens,[])
|> Enum.reverse()
end
defp elixirize_tokens(tokens, rest)
defp elixirize_tokens([], result), do: result
defp elixirize_tokens([{token, _, text}|rest], result), do: elixirize_tokens(rest, [{token,to_string(text)}|result])
end
# SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/lib/earmark_parser/helpers/line_helpers.ex 0000664 0000000 0000000 00000001710 14531042673 0025027 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser.Helpers.LineHelpers do
@moduledoc false
alias EarmarkParser.Line
def blank?(%Line.Blank{}), do: true
def blank?(_), do: false
def blockquote_or_text?(%Line.BlockQuote{}), do: true
def blockquote_or_text?(struct), do: text?(struct)
def indent_or_blank?(%Line.Indent{}), do: true
def indent_or_blank?(line), do: blank?(line)
# Gruber's tests have
#
# para text...
# * and more para text
#
# So list markers inside paragraphs are ignored. But he also has
#
# * line
# * line
#
# And expects it to be a nested list. These seem to be in conflict
#
# I think the second is a better interpretation, so I commented
# out the 2nd match below.
def text?(%Line.Text{}), do: true
def text?(%Line.TableLine{}), do: true
# def text?(%Line.ListItem{}), do: true
def text?(_), do: false
end
# SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/lib/earmark_parser/helpers/lookahead_helpers.ex 0000664 0000000 0000000 00000005014 14531042673 0026030 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser.Helpers.LookaheadHelpers do
@moduledoc false
import EarmarkParser.Helpers.LeexHelpers
@doc """
Indicates if the _numbered_line_ passed in leaves an inline code block open.
If so returns a tuple whre the first element is the opening sequence of backticks,
and the second the linenumber of the _numbered_line_
Otherwise `{nil, 0}` is returned
"""
def opens_inline_code(%{line: line, lnb: lnb}) do
case tokenize(line, with: :earmark_parser_string_lexer) |> has_still_opening_backtix(nil) do
nil -> {nil, 0}
{_, btx} -> {btx, lnb}
end
end
@doc """
returns false if and only if the line closes a pending inline code
*without* opening a new one.
The opening backtix are passed in as second parameter.
If the function does not return false it returns the (new or original)
opening backtix
"""
# (#{},{_,_}) -> {_,_}
def still_inline_code(%{line: line, lnb: lnb}, old = {pending, _pending_lnb}) do
case tokenize(line, with: :earmark_parser_string_lexer) |> has_still_opening_backtix({:old, pending}) do
nil -> {nil, 0}
{:new, btx} -> {btx, lnb}
{:old, _} -> old
end
end
# A tokenized line {:verabtim, text} | {:backtix, ['``+]} is analyzed for
# if it is closed (-> nil), not closed (-> {:old, btx}) or reopened (-> {:new, btx})
# concerning backtix
defp has_still_opening_backtix(tokens, opened_so_far)
# Empty, done, but take care of tangeling escape (\)
defp has_still_opening_backtix([], :force_outside), do: nil
defp has_still_opening_backtix([], open), do: open
# Outside state, represented by nil
defp has_still_opening_backtix([{:other, _} | rest], nil),
do: has_still_opening_backtix(rest, nil)
defp has_still_opening_backtix([{:backtix, btx} | rest], nil),
do: has_still_opening_backtix(rest, {:new, btx})
defp has_still_opening_backtix([{:escape, _} | rest], nil),
do: has_still_opening_backtix(rest, :force_outside)
# Next state forced outside, represented by :force_outside
defp has_still_opening_backtix([_ | rest], :force_outside),
do: has_still_opening_backtix(rest, nil)
# Inside state, represented by { :old | :new, btx }
defp has_still_opening_backtix([{:backtix, btx} | rest], open = {_, openedbtx}) do
if btx == openedbtx do
has_still_opening_backtix(rest, nil)
else
has_still_opening_backtix(rest, open)
end
end
defp has_still_opening_backtix([_ | rest], open = {_, _}),
do: has_still_opening_backtix(rest, open)
end
# SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/lib/earmark_parser/helpers/pure_link_helpers.ex 0000664 0000000 0000000 00000004744 14531042673 0026102 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser.Helpers.PureLinkHelpers do
@moduledoc false
import EarmarkParser.Helpers.AstHelpers, only: [render_link: 2]
@pure_link_rgx ~r{
\A
(\s*)
(
(?:https?://|www\.)
[^\s<>]*
[^\s<>?!.,:*_~]
)
}ux
def convert_pure_link(src) do
case Regex.run(@pure_link_rgx, src) do
[_match, spaces, link_text] ->
if String.ends_with?(link_text, ")") do
remove_trailing_closing_parens(spaces, link_text)
else
make_result(spaces, link_text)
end
_ ->
nil
end
end
@split_at_ending_parens ~r{ (.*?) (\)*) \z}x
defp remove_trailing_closing_parens(leading_spaces, link_text) do
[_, link_text, trailing_parens] = Regex.run(@split_at_ending_parens, link_text)
trailing_paren_count = String.length(trailing_parens)
# try to balance parens from the rhs
unbalanced_count = balance_parens(String.reverse(link_text), trailing_paren_count)
balanced_parens = String.slice(trailing_parens, 0, trailing_paren_count - unbalanced_count)
make_result(leading_spaces, link_text <> balanced_parens)
end
defp make_result(leading_spaces, link_text) do
link =
if String.starts_with?(link_text, "www.") do
render_link("http://" <> link_text, link_text)
else
render_link(link_text, link_text)
end
if leading_spaces == "" do
{link, String.length(link_text)}
else
{[leading_spaces, link], String.length(leading_spaces) + String.length(link_text)}
end
end
# balance parens and return unbalanced *trailing* paren count
defp balance_parens(reverse_text, trailing_count, non_trailing_count \\ 0)
defp balance_parens(<<>>, trailing_paren_count, _non_trailing_count), do: trailing_paren_count
defp balance_parens(_reverse_text, 0, _non_trailing_count), do: 0
defp balance_parens(")" <> rest, trailing_paren_count, non_trailing_count) do
balance_parens(rest, trailing_paren_count, non_trailing_count + 1)
end
defp balance_parens("(" <> rest, trailing_paren_count, non_trailing_count) do
# non-trailing paren must be balanced before trailing paren
if non_trailing_count > 0 do
balance_parens(rest, trailing_paren_count, non_trailing_count - 1)
else
balance_parens(rest, trailing_paren_count - 1, non_trailing_count)
end
end
defp balance_parens(<<_::utf8,rest::binary>>, trailing_paren_count, non_trailing_count) do
balance_parens(rest, trailing_paren_count, non_trailing_count)
end
end
earmark_parser-1.4.39/lib/earmark_parser/helpers/reparse_helpers.ex 0000664 0000000 0000000 00000001353 14531042673 0025544 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser.Helpers.ReparseHelpers do
@moduledoc false
alias EarmarkParser.Line
@doc """
Extract the verbatim text of `%EarmarkParser.Line.t` elements with less alignment so that
it can be reparsed (as elements of footnotes or indented code)
"""
# Add additional spaces for any indentation past level 1
def properly_indent(%Line.Indent{level: level, content: content}, target_level)
when level == target_level do
content
end
def properly_indent(%Line.Indent{level: level, content: content}, target_level)
when level > target_level do
String.duplicate(" ", level-target_level) <> content
end
def properly_indent(line, _) do
line.content
end
end
# SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/lib/earmark_parser/helpers/string_helpers.ex 0000664 0000000 0000000 00000000567 14531042673 0025417 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser.Helpers.StringHelpers do
@moduledoc false
@doc """
Remove the leading part of a string
"""
def behead(str, ignore) when is_integer(ignore) do
{_pre, post} = String.split_at(str, ignore)
post
end
def behead(str, leading_string) do
behead(str, String.length(leading_string))
end
end
# SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/lib/earmark_parser/helpers/yecc_helpers.ex 0000664 0000000 0000000 00000000746 14531042673 0025033 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser.Helpers.YeccHelpers do
@moduledoc false
import EarmarkParser.Helpers.LeexHelpers, only: [lex: 2]
def parse!( text, lexer: lexer, parser: parser ) do
case parse(text, lexer: lexer, parser: parser) do
{:ok, ast} -> ast
{:error, _} -> nil
end
end
def parse( text, lexer: lexer, parser: parser ) do
with tokens <- lex(text, with: lexer) do
parser.parse(tokens)
end
end
end
# SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/lib/earmark_parser/line.ex 0000664 0000000 0000000 00000005025 14531042673 0021646 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser.Line do
@moduledoc false
defmodule Blank do
@moduledoc false
defstruct(annotation: nil, lnb: 0, line: "", indent: -1, content: "")
end
defmodule Ruler do
@moduledoc false
defstruct(annotation: nil, lnb: 0, line: "", indent: -1, type: "- or * or _")
end
defmodule Heading do
@moduledoc false
defstruct(annotation: nil, ial: nil, lnb: 0, line: "", indent: -1, level: 1, content: "inline text")
end
defmodule BlockQuote do
@moduledoc false
defstruct(annotation: nil, ial: nil, lnb: 0, line: "", indent: -1, content: "text")
end
defmodule Indent do
@moduledoc false
defstruct(annotation: nil, lnb: 0, line: "", indent: -1, level: 0, content: "text")
end
defmodule Fence do
@moduledoc false
defstruct(annotation: nil, lnb: 0, line: "", indent: -1, delimiter: "~ or `", language: nil)
end
defmodule HtmlOpenTag do
@moduledoc false
defstruct(annotation: nil, lnb: 0, line: "", indent: -1, tag: "", content: "")
end
defmodule HtmlCloseTag do
@moduledoc false
defstruct(annotation: nil, lnb: 0, line: "", indent: -1, tag: "<... to eol")
end
defmodule HtmlComment do
@moduledoc false
defstruct(annotation: nil, lnb: 0, line: "", indent: -1, complete: true)
end
defmodule HtmlOneLine do
@moduledoc false
defstruct(annotation: nil, lnb: 0, line: "", indent: -1, tag: "", content: "")
end
defmodule IdDef do
@moduledoc false
defstruct(annotation: nil, lnb: 0, line: "", indent: -1, id: nil, url: nil, title: nil)
end
defmodule FnDef do
@moduledoc false
defstruct(annotation: nil, lnb: 0, line: "", indent: -1, id: nil, content: "text")
end
defmodule ListItem do
@moduledoc false
defstruct(
annotation: nil,
ial: nil,
lnb: 0,
type: :ul,
line: "",
indent: -1,
bullet: "* or -",
content: "text",
initial_indent: 0,
list_indent: 0
)
end
defmodule SetextUnderlineHeading do
@moduledoc false
defstruct(annotation: nil, lnb: 0, line: "", indent: -1, level: 1)
end
defmodule TableLine do
@moduledoc false
defstruct(annotation: nil, lnb: 0, line: "", indent: -1, content: "", columns: 0, is_header: false, needs_header: false)
end
defmodule Ial do
@moduledoc false
defstruct(annotation: nil, ial: nil, lnb: 0, line: "", indent: -1, attrs: "", verbatim: "")
end
defmodule Text do
@moduledoc false
defstruct(annotation: nil, lnb: 0, line: "", indent: -1, content: "")
end
end
# SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/lib/earmark_parser/line_scanner.ex 0000664 0000000 0000000 00000022757 14531042673 0023372 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser.LineScanner do
@moduledoc false
alias EarmarkParser.{Helpers, Line, Options}
# This is the re that matches the ridiculous "[id]: url title" syntax
@id_title_part ~S"""
(?|
" (.*) " # in quotes
| ' (.*) ' #
| \( (.*) \) # in parens
)
"""
@id_re ~r'''
^\[([^^].*?)\]: # [someid]:
\s+
(?|
< (\S+) > # url in <>s
| (\S+) # or without
)
(?:
\s+ # optional title
#{@id_title_part}
)?
\s*
$
'''x
@indent_re ~r'''
\A ( (?: \s{4})+ ) (\s*) # 4 or more leading spaces
(.*) # the rest
'''x
@void_tags ~w{area br hr img wbr}
@void_tag_rgx ~r'''
^<( #{Enum.join(@void_tags, "|")} )
.*?
>
'''x
@doc false
def void_tag?(tag), do: Regex.match?(@void_tag_rgx, "<#{tag}>")
def scan_lines(lines, options, recursive) do
_lines_with_count(lines, options.line - 1)
|> _with_lookahead(options, recursive)
end
def type_of(line, recursive)
when is_boolean(recursive),
do: type_of(line, %Options{}, recursive)
def type_of({line, lnb}, options = %Options{annotations: annotations}, recursive) when is_binary(line) do
{line1, annotation} = line |> Helpers.expand_tabs() |> Helpers.remove_line_ending(annotations)
%{_type_of(line1, options, recursive) | annotation: annotation, lnb: lnb}
end
def type_of({line, lnb}, _, _) do
raise ArgumentError, "line number #{lnb} #{inspect line} is not a binary"
end
defp _type_of(line, options = %Options{}, recursive) do
{ial, stripped_line} = Helpers.extract_ial(line)
{content, indent} = _count_indent(line, 0)
lt_four? = indent < 4
cond do
content == "" ->
_create_text(line, content, indent)
lt_four? && !recursive && Regex.run(~r/\A \z/x, content) ->
%Line.HtmlComment{complete: true, indent: indent, line: line}
lt_four? && !recursive && Regex.run(~r/\A /u, options, recursive)]
other ->
[other | _with_lookahead(lines, options, recursive)]
end
end
defp _with_lookahead([], _options, _recursive), do: []
defp _lookahead_until_match([], _, _, _), do: []
defp _lookahead_until_match([{line, lnb} | lines], regex, options, recursive) do
if line =~ regex do
[type_of({line, lnb}, options, recursive) | _with_lookahead(lines, options, recursive)]
else
[
%{_create_text(line) | lnb: lnb}
| _lookahead_until_match(lines, regex, options, recursive)
]
end
end
@column_rgx ~r{\A[\s|:-]+\z}
defp _determine_if_header(columns) do
columns
|> Enum.all?(fn col -> Regex.run(@column_rgx, col) end)
end
defp _split_table_columns(line) do
line
|> String.split(~r{(? Enum.map(&String.trim/1)
|> Enum.map(fn col -> Regex.replace(~r{\\\|}, col, "|") end)
end
end
# SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/lib/earmark_parser/message.ex 0000664 0000000 0000000 00000001732 14531042673 0022344 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser.Message do
@moduledoc false
alias EarmarkParser.Context
alias EarmarkParser.Options
@type message_type :: :error | :warning
@type t :: {message_type, number, binary}
@type ts :: list(t)
@type container_type :: Options.t() | Context.t()
def add_messages(container, messages),
do: Enum.reduce(messages, container, &add_message(&2, &1))
def add_message(container, message)
def add_message(options = %Options{}, message) do
%{options | messages: MapSet.put(options.messages, message)}
end
def add_message(context = %Context{}, message) do
%{context | options: add_message(context.options, message)}
end
def get_messages(container)
def get_messages(%Context{options: %{messages: messages}}), do: messages
@doc """
For final output
"""
def sort_messages(container) do
container
|> get_messages()
|> Enum.sort(fn {_, l, _}, {_, r, _} -> r >= l end)
end
end
# SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/lib/earmark_parser/options.ex 0000664 0000000 0000000 00000010405 14531042673 0022410 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser.Options do
# What we use to render
defstruct renderer: EarmarkParser.HtmlRenderer,
# Inline style options
all: false,
gfm: true,
gfm_tables: false,
breaks: false,
footnotes: false,
footnote_offset: 1,
wikilinks: false,
parse_inline: true,
# allow for annotations
annotations: nil,
# additional prefies for class of code blocks
code_class_prefix: nil,
# Filename and initial line number of the markdown block passed in
# for meaningful error messages
file: "",
line: 1,
# [{:error|:warning, lnb, text},...]
messages: MapSet.new([]),
pure_links: true,
sub_sup: false,
math: false,
# deprecated
pedantic: false,
smartypants: false,
timeout: nil
@type t :: %__MODULE__{
all: boolean(),
gfm: boolean(),
gfm_tables: boolean(),
breaks: boolean(),
footnotes: boolean(),
footnote_offset: non_neg_integer(),
wikilinks: boolean(),
parse_inline: boolean(),
# allow for annotations
annotations: nil | binary(),
# additional prefies for class of code blocks
code_class_prefix: nil | binary(),
# Filename and initial line number of the markdown block passed in
# for meaningful error messages
file: binary(),
line: number(),
# [{:error|:warning, lnb, text},...]
messages: MapSet.t,
pure_links: boolean(),
sub_sup: boolean(),
# deprecated
pedantic: boolean(),
smartypants: boolean(),
timeout: nil | non_neg_integer()
}
@doc false
def add_deprecations(options, messages)
def add_deprecations(%__MODULE__{smartypants: true} = options, messages) do
add_deprecations(
%{options | smartypants: false},
[
{:deprecated, 0,
"The smartypants option has no effect anymore and will be removed in EarmarkParser 1.5"}
| messages
]
)
end
def add_deprecations(%__MODULE__{timeout: timeout} = options, messages) when timeout != nil do
add_deprecations(
%{options | timeout: nil},
[
{:deprecated, 0,
"The timeout option has no effect anymore and will be removed in EarmarkParser 1.5"}
| messages
]
)
end
def add_deprecations(%__MODULE__{pedantic: true} = options, messages) do
add_deprecations(
%{options | pedantic: false},
[
{:deprecated, 0,
"The pedantic option has no effect anymore and will be removed in EarmarkParser 1.5"}
| messages
]
)
end
def add_deprecations(_options, messages), do: messages
@doc ~S"""
Use normalize before passing it into any API function
iex(1)> options = normalize(annotations: "%%")
...(1)> options.annotations
~r{\A(.*)(%%.*)}
"""
def normalize(options)
def normalize(%__MODULE__{} = options) do
case options.annotations do
%Regex{} ->
options
nil ->
options
_ ->
%{
options
| annotations: Regex.compile!("\\A(.*)(#{Regex.escape(options.annotations)}.*)")
}
end
|> _set_all_if_applicable()
|> _deprecate_old_messages()
end
def normalize(options), do: struct(__MODULE__, options) |> normalize()
defp _deprecate_old_messages(opitons)
defp _deprecate_old_messages(%__MODULE__{messages: %MapSet{}} = options), do: options
defp _deprecate_old_messages(%__MODULE__{} = options) do
%{
options
| messages:
MapSet.new([
{:deprecated, 0,
"messages is an internal option that is ignored and will be removed from the API in v1.5"}
])
}
end
defp _set_all_if_applicable(options)
defp _set_all_if_applicable(%{all: true} = options) do
%{options | breaks: true, footnotes: true, gfm_tables: true, sub_sup: true, wikilinks: true}
end
defp _set_all_if_applicable(options), do: options
end
# SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/lib/earmark_parser/parser.ex 0000664 0000000 0000000 00000046736 14531042673 0022231 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser.Parser do
@moduledoc false
alias EarmarkParser.{Block, Line, LineScanner, Options}
import EarmarkParser.Helpers.{AttrParser, LineHelpers, ReparseHelpers}
import EarmarkParser.Helpers.LookaheadHelpers,
only: [opens_inline_code: 1, still_inline_code: 2]
import EarmarkParser.Message, only: [add_message: 2, add_messages: 2]
import EarmarkParser.Parser.FootnoteParser, only: [parse_fn_defs: 3]
import EarmarkParser.Parser.ListParser, only: [parse_list: 3]
@doc """
Given a markdown document (as either a list of lines or
a string containing newlines), return a parse tree and
the context necessary to render the tree.
The options are a `%EarmarkParser.Options{}` structure. See `as_html!`
for more details.
"""
def parse_markdown(lines, options)
def parse_markdown(lines, options = %Options{}) when is_list(lines) do
{blocks, links, footnotes, options1} = parse(lines, options, false)
context =
%EarmarkParser.Context{options: options1, links: links}
|> EarmarkParser.Context.update_context()
context = put_in(context.footnotes, footnotes)
context = put_in(context.options, options1)
{blocks, context}
end
def parse_markdown(lines, options) when is_binary(lines) do
lines
|> String.split(~r{\r\n?|\n})
|> parse_markdown(options)
end
def parse_markdown(lines, _) do
raise ArgumentError, "#{inspect lines} not a binary, nor a list of binaries"
end
def parse(text_lines, options = %Options{}, recursive) do
["" | text_lines ++ [""]]
|> LineScanner.scan_lines(options, recursive)
|> parse_lines(options, recursive)
end
@doc false
# Given a list of `Line.xxx` structs, group them into related blocks.
# Then extract any id definitions, and build a map from them. Not
# for external consumption.
def parse_lines(lines, options, recursive) do
{blocks, footnotes, options} =
lines |> remove_trailing_blank_lines() |> lines_to_blocks(options, recursive)
links = links_from_blocks(blocks)
{blocks, links, footnotes, options}
end
defp lines_to_blocks(lines, options, recursive) do
{blocks, footnotes, options1} = _parse(lines, [], options, recursive)
{blocks |> assign_attributes_to_blocks([]), footnotes, options1}
end
defp _parse(input, result, options, recursive)
defp _parse([], result, options, _recursive), do: {result, %{}, options}
###################
# setext headings #
###################
# 1 step
defp _parse(
[
%Line.Blank{},
%Line.Text{content: heading, lnb: lnb},
%Line.SetextUnderlineHeading{annotation: annotation, level: level}
| rest
],
result,
options,
recursive
) do
_parse(
rest,
[%Block.Heading{annotation: annotation, content: heading, level: level, lnb: lnb} | result],
options,
recursive
)
end
# 1 step
defp _parse(
[
%Line.Blank{},
%Line.Text{content: heading, lnb: lnb},
%Line.Ruler{type: "-"}
| rest
],
result,
options,
recursive
) do
_parse(
rest,
[%Block.Heading{content: heading, level: 2, lnb: lnb} | result],
options,
recursive
)
end
#################
# Other heading #
#################
# 1 step
defp _parse(
[%Line.Heading{content: content, ial: ial, level: level, lnb: lnb} | rest],
result,
options,
recursive
) do
{options1, result1} =
prepend_ial(
options,
ial,
lnb,
[%Block.Heading{content: content, level: level, lnb: lnb} | result]
)
_parse(rest, result1, options1, recursive)
end
#########
# Ruler #
#########
# 1 step
defp _parse([%Line.Ruler{type: type, lnb: lnb} | rest], result, options, recursive) do
_parse(rest, [%Block.Ruler{type: type, lnb: lnb} | result], options, recursive)
end
###############
# Block Quote #
###############
# split and parse
defp _parse(lines = [%Line.BlockQuote{lnb: lnb} | _], result, options, recursive) do
{quote_lines, rest} = Enum.split_while(lines, &blockquote_or_text?/1)
lines = for line <- quote_lines, do: line.content
{blocks, _, _, options1} = parse(lines, %{options | line: lnb}, true)
_parse(rest, [%Block.BlockQuote{blocks: blocks, lnb: lnb} | result], options1, recursive)
end
#########
# Table #
#########
# read and add verbatim
defp _parse(
lines = [
%Line.TableLine{columns: cols1, lnb: lnb1, needs_header: false},
%Line.TableLine{columns: cols2}
| _rest
],
result,
options,
recursive
)
when length(cols1) == length(cols2) do
columns = length(cols1)
{table, rest} = read_table(lines, columns, [])
table1 = %{table | lnb: lnb1}
_parse(rest, [table1 | result], options, recursive)
end
defp _parse(
lines = [
%Line.TableLine{columns: cols1, lnb: lnb1, needs_header: true},
%Line.TableLine{columns: cols2, is_header: true}
| _rest
],
result,
options,
recursive
)
when length(cols1) == length(cols2) do
columns = length(cols1)
{table, rest} = read_table(lines, columns, [])
table1 = %{table | lnb: lnb1}
_parse(rest, [table1 | result], options, recursive)
end
#############
# Paragraph #
#############
# split and add verbatim
defp _parse(lines = [%Line.TableLine{lnb: lnb} | _], result, options, recursive) do
{para_lines, rest} = Enum.split_while(lines, &text?/1)
line_text = for line <- para_lines, do: line.line
_parse(rest, [%Block.Para{lines: line_text, lnb: lnb + 1} | result], options, recursive)
end
# read and parse
defp _parse(lines = [%Line.Text{lnb: lnb} | _], result, options, recursive) do
{reversed_para_lines, rest, pending, annotation} = consolidate_para(lines)
options1 =
case pending do
{nil, _} ->
options
{pending, lnb1} ->
add_message(
options,
{:warning, lnb1, "Closing unclosed backquotes #{pending} at end of input"}
)
end
line_text = for line <- reversed_para_lines |> Enum.reverse(), do: line.line
if recursive == :list do
_parse(rest, [%Block.Text{line: line_text, lnb: lnb} | result], options1, recursive)
else
_parse(
rest,
[%Block.Para{annotation: annotation, lines: line_text, lnb: lnb} | result],
options1,
recursive
)
end
end
defp _parse(
[%Line.SetextUnderlineHeading{line: line, lnb: lnb, level: 2} | rest],
result,
options,
recursive
) do
_parse([%Line.Text{line: line, lnb: lnb} | rest], result, options, recursive)
end
#########
# Lists #
#########
# We handle lists in two passes. In the first, we build list items,
# in the second we combine adjacent items into lists. This is pass one
defp _parse([%Line.ListItem{} | _] = input, result, options, recursive) do
{with_prepended_lists, rest, options1} = parse_list(input, result, options)
_parse([%Line.Blank{lnb: 0} | rest], with_prepended_lists, options1, recursive)
end
#################
# Indented code #
#################
defp _parse(list = [%Line.Indent{lnb: lnb} | _], result, options, recursive) do
{code_lines, rest} = Enum.split_while(list, &indent_or_blank?/1)
code_lines = remove_trailing_blank_lines(code_lines)
code = for line <- code_lines, do: properly_indent(line, 1)
_parse([%Line.Blank{}|rest], [%Block.Code{lines: code, lnb: lnb} | result], options, recursive)
end
###############
# Fenced code #
###############
defp _parse(
[%Line.Fence{delimiter: delimiter, language: language, lnb: lnb} | rest],
result,
options,
recursive
) do
{code_lines, rest} =
Enum.split_while(rest, fn line ->
!match?(%Line.Fence{delimiter: ^delimiter, language: _}, line)
end)
{rest1, options1} = _check_closing_fence(rest, lnb, delimiter, options)
code = for line <- code_lines, do: line.line
_parse(
rest1,
[%Block.Code{lines: code, language: language, lnb: lnb} | result],
options1,
recursive
)
end
##############
# HTML block #
##############
defp _parse(
[opener = %Line.HtmlOpenTag{annotation: annotation, tag: tag, lnb: lnb} | rest],
result,
options,
recursive
) do
{html_lines, rest1, unclosed, annotation} = _html_match_to_closing(opener, rest, annotation)
options1 =
add_messages(
options,
unclosed
|> Enum.map(fn %{lnb: lnb1, tag: tag} ->
{:warning, lnb1, "Failed to find closing <#{tag}>"}
end)
)
html = Enum.reverse(html_lines)
_parse(
rest1,
[%Block.Html{tag: tag, html: html, lnb: lnb, annotation: annotation} | result],
options1,
recursive
)
end
####################
# HTML on one line #
####################
defp _parse(
[%Line.HtmlOneLine{annotation: annotation, line: line, lnb: lnb} | rest],
result,
options,
recursive
) do
_parse(
rest,
[%Block.HtmlOneline{annotation: annotation, html: [line], lnb: lnb} | result],
options,
recursive
)
end
################
# HTML Comment #
################
defp _parse(
[line = %Line.HtmlComment{complete: true, lnb: lnb} | rest],
result,
options,
recursive
) do
_parse(rest, [%Block.HtmlComment{lines: [line.line], lnb: lnb} | result], options, recursive)
end
defp _parse(
lines = [%Line.HtmlComment{complete: false, lnb: lnb} | _],
result,
options,
recursive
) do
{html_lines, rest} =
Enum.split_while(lines, fn line ->
!(line.line =~ ~r/-->/)
end)
{html_lines, rest} =
if rest == [] do
{html_lines, rest}
else
{html_lines ++ [hd(rest)], tl(rest)}
end
html = for line <- html_lines, do: line.line
_parse(rest, [%Block.HtmlComment{lines: html, lnb: lnb} | result], options, recursive)
end
#################
# ID definition #
#################
defp _parse([defn = %Line.IdDef{lnb: lnb} | rest], result, options, recursive) do
_parse(
rest,
[%Block.IdDef{id: defn.id, url: defn.url, title: defn.title, lnb: lnb} | result],
options,
recursive
)
end
#######################
# Footnote Definition #
#######################
# Starting from 1.5.0 Footnote Definitions are always at the end of the document (GFM) meaning that the
# `_parse` iteration can now end and we will trigger `_parse_fn_defs`
# this has the advantage that we can make the assumption that the top of the `result`
# list contains a `Block.FnList` element
defp _parse([%Line.FnDef{} | _] = input, result, options, _recursive) do
parse_fn_defs(input, result, options)
end
####################
# IAL (attributes) #
####################
defp _parse(
[%Line.Ial{attrs: attrs, lnb: lnb, verbatim: verbatim} | rest],
result,
options,
recursive
) do
{options1, attributes} = parse_attrs(options, attrs, lnb)
_parse(
rest,
[%Block.Ial{attrs: attributes, content: attrs, lnb: lnb, verbatim: verbatim} | result],
options1,
recursive
)
end
###############
# Blank Lines #
###############
# We've reached the point where empty lines are no longer significant
defp _parse([%Line.Blank{} | rest], result, options, recursive) do
_parse(rest, result, options, recursive)
end
##############################################################
# Anything else... we warn, then treat it as if it were text #
##############################################################
defp _parse([anything = %{lnb: lnb} | rest], result, options, recursive) do
_parse(
[%Line.Text{content: anything.line, lnb: lnb} | rest],
result,
add_message(options, {:warning, anything.lnb, "Unexpected line #{anything.line}"}),
recursive
)
end
#######################################################
# Assign attributes that follow a block to that block #
#######################################################
defp assign_attributes_to_blocks([], result), do: result
defp assign_attributes_to_blocks([%Block.Ial{attrs: attrs}, block | rest], result) do
assign_attributes_to_blocks(rest, [%{block | attrs: attrs} | result])
end
defp assign_attributes_to_blocks([block | rest], result) do
assign_attributes_to_blocks(rest, [block | result])
end
defp _check_closing_fence(rest, lnb, delimiter, options)
defp _check_closing_fence([], lnb, delimiter, options) do
{[], add_message(options, {:error, lnb, "Fenced Code Block opened with #{delimiter} not closed at end of input"})}
end
defp _check_closing_fence([_|rest], _lnb, _delimiter, options) do
{rest, options}
end
############################################################
# Consolidate multiline inline code blocks into an element #
############################################################
@not_pending {nil, 0}
# ([#{},...]) -> {[#{}],[#{}],{'nil' | binary(),number()}}
# @spec consolidate_para( ts ) :: { ts, ts, {nil | String.t, number} }
defp consolidate_para(lines), do: _consolidate_para(lines, [], @not_pending, nil)
defp _consolidate_para([], result, pending, annotation) do
{result, [], pending, annotation}
end
defp _consolidate_para([line | rest] = lines, result, pending, annotation) do
case _inline_or_text?(line, pending) do
%{pending: still_pending, continue: true} ->
_consolidate_para(rest, [line | result], still_pending, annotation || line.annotation)
_ ->
{result, lines, @not_pending, annotation}
end
end
##################################################
# Read in a table (consecutive TableLines with
# the same number of columns)
defp read_table(lines, col_count, rows)
defp read_table(
[%Line.TableLine{columns: cols} | rest],
col_count,
rows
)
when length(cols) == col_count do
read_table(rest, col_count, [cols | rows])
end
defp read_table(rest, col_count, rows) do
rows = Enum.reverse(rows)
table = Block.Table.new_for_columns(col_count)
table =
case look_for_alignments(rows) do
nil -> %Block.Table{table | rows: rows}
aligns -> %Block.Table{table | alignments: aligns, header: hd(rows), rows: tl(tl(rows))}
end
{table, [%Line.Blank{lnb: 0} | rest]}
end
defp look_for_alignments([_first, second | _rest]) do
if Enum.all?(second, fn row -> row =~ ~r{^:?-+:?$} end) do
second
|> Enum.map(fn row -> Regex.replace(~r/-+/, row, "-") end)
|> Enum.map(fn row ->
case row do
":-:" -> :center
":-" -> :left
"-" -> :left
"-:" -> :right
end
end)
else
nil
end
end
#####################################################
# Traverse the block list and build a list of links #
#####################################################
defp links_from_blocks(blocks) do
visit(blocks, Map.new(), &link_extractor/2)
end
defp link_extractor(item = %Block.IdDef{id: id}, result) do
Map.put(result, String.downcase(id), item)
end
defp link_extractor(_, result), do: result
##################################
# Visitor pattern for each block #
##################################
defp visit([], result, _func), do: result
# Structural node BlockQuote -> descend
defp visit([item = %Block.BlockQuote{blocks: blocks} | rest], result, func) do
result = func.(item, result)
result = visit(blocks, result, func)
visit(rest, result, func)
end
# Structural node List -> descend
defp visit([item = %Block.List{blocks: blocks} | rest], result, func) do
result = func.(item, result)
result = visit(blocks, result, func)
visit(rest, result, func)
end
# Structural node ListItem -> descend
defp visit([item = %Block.ListItem{blocks: blocks} | rest], result, func) do
result = func.(item, result)
result = visit(blocks, result, func)
visit(rest, result, func)
end
# Leaf, leaf it alone
defp visit([item | rest], result, func) do
result = func.(item, result)
visit(rest, result, func)
end
###################################################################
# Consume HTML, taking care of nesting. Assumes one tag per line. #
###################################################################
defp _html_match_to_closing(opener, rest, annotation),
do: _find_closing_tags([opener], rest, [opener.line], [], annotation)
defp _find_closing_tags(needed, input, html_lines, text_lines, annotation)
# No more open tags, happy case
defp _find_closing_tags([], rest, html_lines, [], annotation),
do: {html_lines, rest, [], annotation}
# run out of input, unhappy case
defp _find_closing_tags(needed, [], html_lines, text_lines, annotation),
do: {_add_text_lines(html_lines, text_lines), [], needed, annotation}
# still more lines, still needed closing
defp _find_closing_tags(
needed = [needed_hd | needed_tl],
[rest_hd | rest_tl],
html_lines,
text_lines,
annotation
) do
cond do
_closes_tag?(rest_hd, needed_hd) ->
_find_closing_tags(
needed_tl,
rest_tl,
[rest_hd.line | _add_text_lines(html_lines, text_lines)],
[],
_override_annotation(annotation, rest_hd)
)
_opens_tag?(rest_hd) ->
_find_closing_tags(
[rest_hd | needed],
rest_tl,
[rest_hd.line | _add_text_lines(html_lines, text_lines)],
[],
annotation
)
true ->
_find_closing_tags(needed, rest_tl, html_lines, [rest_hd.line | text_lines], annotation)
end
end
defp _add_text_lines(html_lines, []),
do: html_lines
defp _add_text_lines(html_lines, text_lines),
do: [text_lines |> Enum.reverse() |> Enum.join("\n") | html_lines]
###########
# Helpers #
###########
defp _closes_tag?(%Line.HtmlCloseTag{tag: ctag}, %Line.HtmlOpenTag{tag: otag}) do
ctag == otag
end
defp _closes_tag?(_, _), do: false
defp _opens_tag?(%Line.HtmlOpenTag{}), do: true
defp _opens_tag?(_), do: false
defp _inline_or_text?(line, pending)
defp _inline_or_text?(line = %Line.Text{}, @not_pending) do
pending = opens_inline_code(line)
%{pending: pending, continue: true}
end
defp _inline_or_text?(line = %Line.TableLine{}, @not_pending) do
pending = opens_inline_code(line)
%{pending: pending, continue: true}
end
defp _inline_or_text?(_line, @not_pending), do: %{pending: @not_pending, continue: false}
defp _inline_or_text?(line, pending) do
pending = still_inline_code(line, pending)
%{pending: pending, continue: true}
end
defp _override_annotation(annotation, line), do: annotation || line.annotation
defp remove_trailing_blank_lines(lines) do
lines
|> Enum.reverse()
|> Enum.drop_while(&blank?/1)
|> Enum.reverse()
end
def prepend_ial(context, maybeatts, lnb, result)
def prepend_ial(context, nil, _lnb, result), do: {context, result}
def prepend_ial(context, ial, lnb, result) do
{context1, attributes} = parse_attrs(context, ial, lnb)
{context1, [%Block.Ial{attrs: attributes, content: ial, lnb: lnb, verbatim: ial} | result]}
end
end
# SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/lib/earmark_parser/parser/ 0000775 0000000 0000000 00000000000 14531042673 0021653 5 ustar 00root root 0000000 0000000 earmark_parser-1.4.39/lib/earmark_parser/parser/footnote_parser.ex 0000664 0000000 0000000 00000003575 14531042673 0025434 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser.Parser.FootnoteParser do
alias EarmarkParser.{Block, Enum.Ext, Line}
@moduledoc false
def parse_fn_defs([fn_def | rest], result, options) do
acc =
{[fn_def.content], [%Block.FnList{blocks: [_block_fn_def(fn_def)]} | result], %{}, options}
rest
|> Ext.reduce_with_end(acc, &_parse_fn_def_reduce/2)
end
defp _parse_fn_def_reduce(ele_or_end, acc)
defp _parse_fn_def_reduce({:element, %Line.FnDef{content: content}=fn_def}, acc) do
{result1, footnotes, options1} = _complete_fn_def_block(acc, fn_def)
{[content], result1, footnotes, options1}
end
defp _parse_fn_def_reduce({:element, %{line: line}}, acc) do
_prepend_to_first_in4(line, acc)
end
defp _parse_fn_def_reduce(:end, acc) do
{[fn_list | rest], footnotes, options} = _complete_fn_def_block(acc)
{[%{fn_list | blocks: Enum.reverse(fn_list.blocks)} | rest], footnotes, options}
end
defp _prepend_to_first_in4(element, {a, b, c, d}) do
{[element | a], b, c, d}
end
defp _block_fn_def(%Line.FnDef{} = fn_def) do
%Block.FnDef{id: fn_def.id, lnb: fn_def.lnb}
end
defp _complete_fn_def_block(
{input, [%Block.FnList{blocks: [open_fn | closed_fns]} | rest], footnotes, options},
new_fn_def \\ nil
) do
# `_footnotes1` should be empty but let us not change the shape of parse depending
# on options or the value of recursive?
{inner_blocks, _links, _footnotes1, options1} = EarmarkParser.Parser.parse(Enum.reverse(input), options, true)
closed_fn = %{open_fn | blocks: inner_blocks}
footnotes1 = Map.put(footnotes, closed_fn.id, closed_fn)
fn_blocks =
if new_fn_def do
[_block_fn_def(new_fn_def), closed_fn | closed_fns]
else
[closed_fn | closed_fns]
end
{[%Block.FnList{blocks: fn_blocks} | rest], footnotes1, options1}
end
end
# SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/lib/earmark_parser/parser/link_parser.ex 0000664 0000000 0000000 00000011110 14531042673 0024514 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser.Parser.LinkParser do
@moduledoc false
import EarmarkParser.Helpers.LeexHelpers, only: [tokenize: 2]
import EarmarkParser.Helpers.YeccHelpers, only: [parse!: 2]
import EarmarkParser.Helpers.StringHelpers, only: [behead: 2]
# Hopefully this will go away in v1.3
# **********************************
#
# Right now it needs to parse the url part of strings according to the following grammar
#
# url -> ( inner_url )
# url -> ( inner_url title )
#
# inner_url -> ( inner_url )
# inner_url -> [ inner_url ]
# inner_url -> url_char*
#
# url_char -> . - quote - ( - ) - [ - ]
#
# title -> quote .* quote ;; not LALR-k here
#
# quote -> "
# quote -> ' ;; yep allowing '...." for now
#
# non_quote -> . - quote
@doc false
def parse_link(src, lnb) do
case parse!(src, lexer: :earmark_parser_link_text_lexer, parser: :earmark_parser_link_text_parser) do
{link_or_img, link_text, parsed_text} ->
beheaded = behead(src, to_string(parsed_text))
tokens = tokenize(beheaded, with: :earmark_parser_link_text_lexer)
p_url(tokens, lnb) |> make_result(to_string(link_text), to_string(parsed_text), link_or_img)
_ -> nil
end
end
defp p_url([{:open_paren, _} | ts], lnb), do: url(ts, {[], [], nil}, [:close_paren], lnb)
defp p_url(_, _), do: nil
# push one level
defp url([{:open_paren, text} | ts], result, needed, lnb),
do: url(ts, add(result, text), [:close_paren | needed], lnb)
# pop last level
defp url([{:close_paren, _} | _], result, [:close_paren], _lnb), do: result
# pop inner level
defp url([{:close_paren, text} | ts], result, [:close_paren | needed], lnb),
do: url(ts, add(result, text), needed, lnb)
# A quote on level 0 -> bailing out if there is a matching quote
defp url(ts_all = [{:open_title, text} | ts], result, [:close_paren], lnb) do
case bail_out_to_title(ts_all, result) do
nil -> url(ts, add(result, text), [:close_paren], lnb)
res -> res
end
end
# All these are just added to the url
defp url([{:open_bracket, text} | ts], result, needed, lnb),
do: url(ts, add(result, text), needed, lnb)
defp url([{:close_bracket, text} | ts], result, needed, lnb),
do: url(ts, add(result, text), needed, lnb)
defp url([{:any_quote, text} | ts], result, needed, lnb),
do: url(ts, add(result, text), needed, lnb)
defp url([{:verbatim, text} | ts], result, needed, lnb),
do: url(ts, add(result, text), needed, lnb)
defp url([{:ws, text} | ts], result, needed, lnb),
do: url(ts, add(result, text), needed, lnb)
defp url([{:escaped, text} | ts], result, needed, lnb),
do: url(ts, add(result, text), needed, lnb)
# That is not good, actually this is not a legal url part of a link
defp url(_, _, _, _), do: nil
defp bail_out_to_title(ts, result) do
with remaining_text <- ts |> Enum.map(&text_of_token/1) |> Enum.join("") do
case title(remaining_text) do
nil -> nil
{title_text, inner_title} ->
add_title(result, {title_text, inner_title})
end
end
end
defp text_of_token(token)
defp text_of_token({:escaped, text}), do: "\\#{text}"
defp text_of_token({_, text}) do
text
end
# sic!!! Greedy and not context aware, matching '..." and "...' for backward comp
@title_rgx ~r{\A\s+(['"])(.*?)\1(?=\))}
defp title(remaining_text) do
case Regex.run(@title_rgx, remaining_text) do
nil -> nil
[parsed, _, inner] -> {parsed, inner}
end
end
@wikilink_rgx ~r{\A\[\[([^\]\|]+)(?:\|([^\]]+))?\]\]\Z}
defp make_result(nil, _, parsed_text, :link) do
case Regex.run(@wikilink_rgx, parsed_text) do
nil -> nil
[_, wikilink] -> make_wikilink(parsed_text, wikilink, wikilink)
[_, wikilink, link_text] -> make_wikilink(parsed_text, wikilink, link_text)
end
end
defp make_result(nil, _, _, _), do: nil
defp make_result({parsed, url, title}, link_text, parsed_text, link_or_img) do
{"#{parsed_text}(#{list_to_text(parsed)})", link_text, list_to_text(url), title, link_or_img}
end
defp add({parsed_text, url_text, nil}, text), do: {[text | parsed_text], [text | url_text], nil}
defp add_title({parsed_text, url_text, _}, {parsed, inner}),
do: {[parsed | parsed_text], url_text, inner}
defp make_wikilink(parsed_text, target, link_text) do
{parsed_text, String.trim(link_text), String.trim(target), nil, :wikilink}
end
defp list_to_text(lst), do: lst |> Enum.reverse() |> Enum.join("")
end
# SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/lib/earmark_parser/parser/list_info.ex 0000664 0000000 0000000 00000002315 14531042673 0024200 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser.Parser.ListInfo do
import EarmarkParser.Helpers.LookaheadHelpers, only: [opens_inline_code: 1, still_inline_code: 2]
@moduledoc false
@not_pending {nil, 0}
defstruct(
indent: 0,
lines: [],
loose?: false,
pending: @not_pending,
options: %EarmarkParser.Options{},
width: 0
)
def new(%EarmarkParser.Line.ListItem{initial_indent: ii, list_indent: width}=item, options) do
pending = opens_inline_code(item)
%__MODULE__{indent: ii, lines: [item.content], options: options, pending: pending, width: width}
end
def update_list_info(list_info, line, pending_line, loose? \\ false) do
prepend_line(list_info, line) |> _update_rest(pending_line, loose?)
end
def prepend_line(%__MODULE__{lines: lines}=list_info, line) do
%{list_info|lines: [line|lines]}
end
defp _update_rest(%{pending: @not_pending}=list_info, line, loose?) do
pending = opens_inline_code(line)
%{list_info | pending: pending, loose?: loose?}
end
defp _update_rest(%{pending: pending}=list_info, line, loose?) do
pending1 = still_inline_code(line, pending)
%{list_info | pending: pending1, loose?: loose?}
end
end
# SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/lib/earmark_parser/parser/list_parser.ex 0000664 0000000 0000000 00000016657 14531042673 0024557 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser.Parser.ListParser do
alias EarmarkParser.{Block, Line, Options}
alias EarmarkParser.Parser.ListInfo
import EarmarkParser.Helpers.StringHelpers, only: [behead: 2]
import EarmarkParser.Message, only: [add_message: 2]
import ListInfo
@moduledoc false
@not_pending {nil, 0}
def parse_list(lines, result, options \\ %Options{}) do
{items, rest, options1} = _parse_list_items_init(lines, [], options)
list = _make_list(items, _empty_list(items) )
{[list|result], rest, options1}
end
defp _parse_list_items_init([item|rest], list_items, options) do
options1 = %{options|line: item.lnb}
_parse_list_items_start(rest, _make_and_prepend_list_item(item, list_items), new(item, options1))
end
defp _parse_list_items_spaced(input, items, list_info)
defp _parse_list_items_spaced(input, items, %{pending: @not_pending}=list_info) do
_parse_list_items_spaced_np(input, items, list_info)
end
defp _parse_list_items_spaced(input, items, list_info) do
_parse_list_items_spaced_pdg(input, items, list_info)
end
defp _parse_list_items_spaced_np([%Line.Blank{}|rest], items, list_info) do
list_info1 = %{list_info|lines: [""|list_info.lines], options: %{list_info.options|line: list_info.options.line + 1}}
_parse_list_items_spaced_np(rest, items, list_info1)
end
defp _parse_list_items_spaced_np([%Line.Ruler{}|_]=lines, items, list_info) do
_finish_list_items(lines, items, false, list_info)
end
defp _parse_list_items_spaced_np([%Line.ListItem{indent: ii}=item|_]=input, list_items, %{width: w}=list_info)
when ii < w do
if _starts_list?(item, list_items) do
_finish_list_items(input, list_items, false, list_info)
else
{items1, options1} = _finish_list_item(list_items, false, _loose(list_info))
_parse_list_items_init(input, items1, options1)
end
end
defp _parse_list_items_spaced_np([%Line.Indent{indent: ii}=item|rest], list_items, %{width: w}=list_info)
when ii >= w do
indented = _behead_spaces(item.line, w)
_parse_list_items_spaced(rest, list_items, update_list_info(list_info, indented, item, true))
end
defp _parse_list_items_spaced_np([%Line.ListItem{}=line|rest], items, list_info) do
indented = _behead_spaces(line.line, list_info.width)
_parse_list_items_start(rest, items, update_list_info(list_info, indented, line))
end
# BUG: Still do not know how much to indent here???
defp _parse_list_items_spaced_np([%{indent: indent, line: str_line}=line|rest], items, %{width: width}=list_info) when
indent >= width
do
_parse_list_items_spaced(rest, items, update_list_info(list_info, behead(str_line, width), line, true))
end
defp _parse_list_items_spaced_np(input, items, list_info) do
_finish_list_items(input ,items, false, list_info)
end
defp _parse_list_items_spaced_pdg(input, items, list_info)
defp _parse_list_items_spaced_pdg([], items, %{pending: {pending, lnb}}=list_info) do
options1 =
add_message(list_info.options, {:warning, lnb, "Closing unclosed backquotes #{pending} at end of input"})
_finish_list_items([], items, false, %{list_info| options: options1})
end
defp _parse_list_items_spaced_pdg([line|rest], items, list_info) do
indented = _behead_spaces(line.line, list_info.width)
_parse_list_items_spaced(rest, items, update_list_info(list_info, indented, line, true))
end
defp _parse_list_items_start(input, list_items, list_info)
defp _parse_list_items_start(input, list_items, %{pending: @not_pending}=list_info) do
_parse_list_items_start_np(input, list_items, list_info)
end
defp _parse_list_items_start(input, list_items, list_info) do
_parse_list_items_start_pdg(input, list_items, list_info)
end
defp _parse_list_items_start_np(input, list_items, list_info)
defp _parse_list_items_start_np([%Line.Blank{}|input], items, list_info) do
_parse_list_items_spaced(input, items, prepend_line(list_info, ""))
end
defp _parse_list_items_start_np([], list_items, list_info) do
_finish_list_items([], list_items, true, list_info)
end
defp _parse_list_items_start_np([%Line.Ruler{}|_]=input, list_items, list_info) do
_finish_list_items(input, list_items, true, list_info)
end
defp _parse_list_items_start_np([%Line.Heading{}|_]=input, list_items, list_info) do
_finish_list_items(input, list_items, true, list_info)
end
defp _parse_list_items_start_np([%Line.ListItem{indent: ii}=item|_]=input, list_items, %{width: w}=list_info)
when ii < w do
if _starts_list?(item, list_items) do
_finish_list_items(input, list_items, true, list_info)
else
{items1, options1} = _finish_list_item(list_items, true, list_info)
_parse_list_items_init(input, items1, options1)
end
end
# Slurp in everything else before a first blank line
defp _parse_list_items_start_np([%{line: str_line}=line|rest], items, list_info) do
indented = _behead_spaces(str_line, list_info.width)
_parse_list_items_start(rest, items, update_list_info(list_info, indented, line))
end
defp _parse_list_items_start_pdg(input, items, list_info)
defp _parse_list_items_start_pdg([], items, list_info) do
_finish_list_items([], items, true, list_info)
end
defp _parse_list_items_start_pdg([%{line: str_line}=line|rest], items, list_info) do
indented = _behead_spaces(str_line, list_info.width)
_parse_list_items_start(rest, items, update_list_info(list_info, indented, line))
end
defp _behead_spaces(str, len) do
Regex.replace(~r/\A\s{1,#{len}}/, str, "")
end
# INLINE CANDIDATE
defp _empty_list([%Block.ListItem{loose?: loose?, type: type}|_]) do
%Block.List{loose?: loose?, type: type}
end
@start_number_rgx ~r{\A0*(\d+)\.}
defp _extract_start(%{bullet: bullet}) do
case Regex.run(@start_number_rgx, bullet) do
nil -> ""
[_, "1"] -> ""
[_, start] -> ~s{ start="#{start}"}
end
end
defp _finish_list_item([%Block.ListItem{}=item|items], _at_start?, list_info) do
{blocks, _, _, options1} = list_info.lines
|> Enum.reverse
|> EarmarkParser.Parser.parse(%{list_info.options|line: item.lnb}, :list)
loose1? = _already_loose?(items) || list_info.loose?
{[%{item | blocks: blocks, loose?: loose1?}|items], options1}
end
defp _finish_list_items(input, items, at_start?, list_info) do
{items1, options1} = _finish_list_item(items, at_start?, list_info)
{items1, input, options1}
end
defp _make_and_prepend_list_item(%Line.ListItem{bullet: bullet, lnb: lnb, type: type}, list_items) do
[%Block.ListItem{bullet: bullet, lnb: lnb, spaced?: false, type: type}|list_items]
end
defp _make_list(items, list)
defp _make_list([%Block.ListItem{bullet: bullet, lnb: lnb}=item], %Block.List{loose?: loose?}=list) do
%{list | blocks: [%{item | loose?: loose?}|list.blocks],
bullet: bullet,
lnb: lnb,
start: _extract_start(item)}
end
defp _make_list([%Block.ListItem{}=item|rest], %Block.List{loose?: loose?}=list) do
_make_list(rest, %{list | blocks: [%{item | loose?: loose?}|list.blocks]})
end
defp _already_loose?(items)
defp _already_loose?([]), do: false
defp _already_loose?([%{loose?: loose?}|_]), do: loose?
defp _loose(list_info), do: %{list_info|loose?: true}
defp _starts_list?(%{bullet: bullet1}, [%Block.ListItem{bullet: bullet2}|_]) do
String.last(bullet1) != String.last(bullet2)
end
end
# SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/mix.exs 0000664 0000000 0000000 00000004336 14531042673 0016137 0 ustar 00root root 0000000 0000000 defmodule EarmarkParser.MixProject do
use Mix.Project
@version "1.4.39"
@url "https://github.com/RobertDober/earmark_parser"
@deps [
{:dialyxir, "~> 1.4.1", only: [:dev]},
{:earmark_ast_dsl, "~> 0.3.7", only: [:test]},
{:excoveralls, "~> 0.14.4", only: [:test]},
{:extractly, "~> 0.5.3", only: [:dev]},
{:floki, "~> 0.32", only: [:dev, :test]}
]
def project do
[
app: :earmark_parser,
version: @version,
compilers: [:leex, :yecc] ++ Mix.compilers(),
elixir: "~> 1.11",
elixirc_paths: elixirc_paths(Mix.env()),
deps: @deps,
description: "AST parser and generator for Markdown",
package: package(),
preferred_cli_env: [
coveralls: :test,
"coveralls.detail": :test,
"coveralls.post": :test,
"coveralls.html": :test
],
test_coverage: [tool: ExCoveralls],
aliases: [docs: &build_docs/1]
]
end
defp package do
[
files: [
"lib",
"src/*.xrl",
"src/*.yrl",
"mix.exs",
"README.md",
"RELEASE.md",
"LICENSE"
],
maintainers: [
"Robert Dober "
],
licenses: [
"Apache-2.0"
],
links: %{
"Changelog" => "#{@url}/blob/master/RELEASE.md",
"GitHub" => @url
}
]
end
defp elixirc_paths(:test), do: ["lib", "test/support", "dev"]
defp elixirc_paths(:dev), do: ["lib", "bench", "dev"]
defp elixirc_paths(_), do: ["lib"]
@module "EarmarkParser"
defp build_docs(_) do
Mix.Task.run("compile")
ex_doc = Path.join(Mix.path_for(:escripts), "ex_doc")
Mix.shell().info("Using escript: #{ex_doc} to build the docs")
unless File.exists?(ex_doc) do
raise "cannot build docs because escript for ex_doc is not installed, " <>
"make sure to run `mix escript.install hex ex_doc` before"
end
args = [@module, @version, Mix.Project.compile_path()]
opts = ~w[--main #{@module} --source-ref v#{@version} --source-url #{@url}]
Mix.shell().info("Running: #{ex_doc} #{inspect(args ++ opts)}")
System.cmd(ex_doc, args ++ opts)
Mix.shell().info("Docs built successfully")
end
end
# SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/mix.lock 0000664 0000000 0000000 00000010553 14531042673 0016266 0 ustar 00root root 0000000 0000000 %{
"certifi": {:hex, :certifi, "2.8.0", "d4fb0a6bb20b7c9c3643e22507e42f356ac090a1dcea9ab99e27e0376d695eba", [:rebar3], [], "hexpm", "6ac7efc1c6f8600b08d625292d4bbf584e14847ce1b6b5c44d983d273e1097ea"},
"dialyxir": {:hex, :dialyxir, "1.4.1", "a22ed1e7bd3a3e3f197b68d806ef66acb61ee8f57b3ac85fc5d57354c5482a93", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "84b795d6d7796297cca5a3118444b80c7d94f7ce247d49886e7c291e1ae49801"},
"earmark_ast_dsl": {:hex, :earmark_ast_dsl, "0.3.7", "5b8cacc7169c9404237a98e1232fdfd8e1ea6e54c8cc2a0497b1fd52f9c6b7c3", [:mix], [], "hexpm", "12d26fa74797e2facfc53f1660efe67218e3c5c87cd7de7a5f377c26091020b1"},
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
"excoveralls": {:hex, :excoveralls, "0.14.4", "295498f1ae47bdc6dce59af9a585c381e1aefc63298d48172efaaa90c3d251db", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "e3ab02f2df4c1c7a519728a6f0a747e71d7d6e846020aae338173619217931c1"},
"extractly": {:hex, :extractly, "0.5.3", "ca6afc3430e63cc9017d50eb03bdf1a3499c7e888037d2279b0ec0ea393af390", [:mix], [], "hexpm", "858f7a285ffb767937e75bb8866f03fecb3b7e5b42728a9677d2ebb6ea885503"},
"floki": {:hex, :floki, "0.32.0", "f915dc15258bc997d49be1f5ef7d3992f8834d6f5695270acad17b41f5bcc8e2", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "1c5a91cae1fd8931c26a4826b5e2372c284813904c8bacb468b5de39c7ececbd"},
"hackney": {:hex, :hackney, "1.18.0", "c4443d960bb9fba6d01161d01cd81173089686717d9490e5d3606644c48d121f", [:rebar3], [{:certifi, "~>2.8.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "9afcda620704d720db8c6a3123e9848d09c87586dc1c10479c42627b905b5c5e"},
"html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"},
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
"jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
"parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"},
"traverse": {:hex, :traverse, "1.0.1", "5e9b04428e1a82a7de8857ffeecdc37afc94cd702140690c4de2a5e68cbc483d", [:mix], [], "hexpm", "0cf9ec9a974caf36d2d70476af88f6fda469c5d1c08e6341c5cf517b02e01c71"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
}
earmark_parser-1.4.39/src/ 0000775 0000000 0000000 00000000000 14531042673 0015402 5 ustar 00root root 0000000 0000000 earmark_parser-1.4.39/src/earmark_parser_link_text_lexer.xrl 0000664 0000000 0000000 00000002107 14531042673 0024407 0 ustar 00root root 0000000 0000000 Definitions.
ESCAPED = \\.
ESCAPE = \\
EXCLAMATION_MARK = [!]
OPEN_PAREN = \(
CLOSE_PAREN = \)
OPEN_BRACKET = \[
CLOSE_BRACKET = \]
OPEN_TITLE = \s+['"]
ANY_QUOTE = ['"]
WS = \s+
ANY = [^]\\"'()[\s]+
Rules.
{ESCAPED} : {token, {escaped, TokenLine, dismiss_backslash(TokenChars)}}.
{EXCLAMATION_MARK} : {token, {exclamation_mark, TokenLine, TokenChars}}.
{OPEN_PAREN} : {token, {open_paren, TokenLine, TokenChars}}.
{CLOSE_PAREN} : {token, {close_paren, TokenLine, TokenChars}}.
{OPEN_BRACKET} : {token, {open_bracket, TokenLine, TokenChars}}.
{CLOSE_BRACKET} : {token, {close_bracket, TokenLine, TokenChars}}.
{OPEN_TITLE} : {token, {open_title, TokenLine, TokenChars}}.
{ANY_QUOTE} : {token, {any_quote, TokenLine, TokenChars}}.
{ESCAPE} : {token, {verbatim, TokenLine, TokenChars}}.
{WS} : {token, {ws, TokenLine, TokenChars}}.
{ANY} : {token, {verbatim, TokenLine, TokenChars}}.
Erlang code.
dismiss_backslash([$\\|Chars]) -> Chars.
earmark_parser-1.4.39/src/earmark_parser_link_text_parser.yrl 0000664 0000000 0000000 00000004751 14531042673 0024574 0 ustar 00root root 0000000 0000000 Nonterminals link_or_image
link rest
inside_brackets inside_brackets_part anything.
Terminals any_quote open_bracket open_title close_bracket open_paren close_paren verbatim escaped exclamation_mark ws.
Rootsymbol link_or_image.
link_or_image -> exclamation_mark link : make_image_tuple('$2').
link_or_image -> link : '$1'.
link -> open_bracket close_bracket : {link, "", "[]"}.
link -> open_bracket close_bracket rest : {link, "", "[]"}.
link -> open_bracket inside_brackets close_bracket : title_tuple('$2').
link -> open_bracket inside_brackets close_bracket rest : title_tuple('$2').
inside_brackets -> inside_brackets_part : '$1'.
inside_brackets -> inside_brackets_part inside_brackets : concat_tuple('$1', '$2').
inside_brackets_part -> exclamation_mark : extract_token('$1').
inside_brackets_part -> verbatim : extract_token('$1').
inside_brackets_part -> ws : extract_token('$1').
inside_brackets_part -> open_title : extract_token('$1').
inside_brackets_part -> open_paren : {"(", "("}.
inside_brackets_part -> close_paren : {")", ")"}.
inside_brackets_part -> any_quote : extract_token('$1').
inside_brackets_part -> escaped : escaped_token('$1').
inside_brackets_part -> open_bracket close_bracket : {"[]", "[]"}.
inside_brackets_part -> open_bracket inside_brackets close_bracket : concat_3t("[", '$2', "]").
rest -> anything.
rest -> anything rest.
anything -> exclamation_mark.
anything -> ws.
anything -> verbatim.
anything -> open_paren.
anything -> close_paren.
anything -> open_bracket.
anything -> close_bracket.
anything -> any_quote.
anything -> escaped.
anything -> open_title.
Erlang code.
concat_tuple({LT, LP}, {RT, RP}) -> {string:concat(LT, RT), string:concat(LP, RP)}.
concat_3t(L, {MT, MP}, R) -> {string:join([L, MT, R], ""), string:join([ L, MP, R ], "")}.
escaped_token({_Token, _Line, Value}) -> {string:concat("\\", Value), string:concat("\\", Value)}.
extract_token({_Token, _Line, Value}) -> {Value, Value}.
make_image_tuple({_Link, L, R}) -> {image, L, string:concat("!", R)}.
title_tuple({Title, Parsed}) -> {link, Title, string:join(["[", Parsed, "]"], "")}.
%% SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/src/earmark_parser_string_lexer.xrl 0000664 0000000 0000000 00000000377 14531042673 0023723 0 ustar 00root root 0000000 0000000 Definitions.
OTHER = [^`\\]+
ESCAPE = \\
BACKTIX = `+
Rules.
{OTHER} : {token, {other, TokenLine, TokenChars}}.
{ESCAPE} : {token, {escape, TokenLine, TokenChars}}.
{BACKTIX} : {token, {backtix, TokenLine, TokenChars}}.
Erlang code.
earmark_parser-1.4.39/test/ 0000775 0000000 0000000 00000000000 14531042673 0015572 5 ustar 00root root 0000000 0000000 earmark_parser-1.4.39/test/acceptance/ 0000775 0000000 0000000 00000000000 14531042673 0017660 5 ustar 00root root 0000000 0000000 earmark_parser-1.4.39/test/acceptance/ast/ 0000775 0000000 0000000 00000000000 14531042673 0020447 5 ustar 00root root 0000000 0000000 earmark_parser-1.4.39/test/acceptance/ast/atx_headers_test.exs 0000664 0000000 0000000 00000006030 14531042673 0024515 0 ustar 00root root 0000000 0000000 defmodule Acceptance.Ast.AtxHeadersTest do
use ExUnit.Case, async: true
import Support.Helpers, only: [as_ast: 1]
import EarmarkAstDsl
describe "ATX headers" do
test "from one to six" do
markdown = "# foo\n## foo\n### foo\n#### foo\n##### foo\n###### foo\n"
ast = (1..6) |> Enum.map(&tag("h#{&1}", "foo"))
messages = []
assert as_ast(markdown) == {:ok, ast, messages}
end
test "from one to six with closing" do
markdown = "# foo #\n## foo ##\n### foo ###\n#### foo #####\n##### foo #####\n###### foo ######\n"
ast = (1..6) |> Enum.map(&tag("h#{&1}", "foo"))
messages = []
assert as_ast(markdown) == {:ok, ast, messages}
end
test "from one to six with closing and trailing ws" do
markdown = "# foo # \n## foo ## \n### foo ### \n#### foo ##### \n##### foo ##### \n###### foo ###### \n"
ast = (1..6) |> Enum.map(&tag("h#{&1}", "foo"))
messages = []
assert as_ast(markdown) == {:ok, ast, messages}
end
test "seven? kidding, right?" do
markdown = "####### foo\n"
ast = p("####### foo")
messages = []
assert as_ast(markdown) == {:ok, [ast], messages}
end
test "sticky (better than to have no glue)" do
markdown = "#5 bolt\n\n#foobar\n"
ast = [ p("#5 bolt"), p("#foobar") ]
messages = []
assert as_ast(markdown) == {:ok, ast, messages}
end
test "close escape" do
markdown = "\\## foo\n"
ast = p("## foo")
messages = []
assert as_ast(markdown) == {:ok, [ast], messages}
end
test "position is so important" do
markdown = "# foo *bar* \\*baz\\*\n"
ast = tag("h1", ["foo ", tag("em", "bar"), " *baz*"])
messages = []
assert as_ast(markdown) == {:ok, [ast], messages}
end
test "spacy" do
markdown = "# foo \n"
ast = tag("h1", "foo")
messages = []
assert as_ast(markdown) == {:ok, [ast], messages}
end
test "code comes first" do
markdown = " # foo\nnext"
ast = [pre_code("# foo"), p("next")]
messages = []
assert as_ast(markdown) == {:ok, ast, messages}
end
test "some prefer to close their headers" do
markdown = "# foo#\n"
ast = tag("h1", "foo")
messages = []
assert as_ast(markdown) == {:ok, [ast], messages}
end
test "closing headers can get creative" do
markdown = "### foo######\n"
ast = tag("h3", "foo")
messages = []
assert as_ast(markdown) == {:ok, [ast], messages}
end
test "hash can still be used in a header" do
markdown = "# C# #\n"
ast = tag("h1", "C#")
messages = []
assert as_ast(markdown) == {:ok, [ast], messages}
end
test "closing header with hash" do
markdown = "# C# (language)#\n"
ast = tag("h1", "C# (language)")
messages = []
assert as_ast(markdown) == {:ok, [ast], messages}
end
end
end
# SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/test/acceptance/ast/block_ial_test.exs 0000664 0000000 0000000 00000004765 14531042673 0024162 0 ustar 00root root 0000000 0000000 defmodule Acceptance.Ast.BlockIalTest do
use ExUnit.Case, async: true
import Support.Helpers, only: [as_ast: 1]
import EarmarkAstDsl
describe "IAL" do
test "Not associated" do
markdown = "{:hello=world}"
ast = p("{:hello=world}")
messages = []
assert as_ast(markdown) == {:ok, [ast], messages}
end
test "Not associated means verbatim" do
markdown = "{: hello=world }"
ast = p("{: hello=world }")
messages = []
assert as_ast(markdown) == {:ok, [ast], messages}
end
test "Not associated and incorrect" do
markdown = "{:hello}"
ast = tag("p", "{:hello}")
messages = [{:warning, 1, "Illegal attributes [\"hello\"] ignored in IAL" }]
assert as_ast(markdown) == {:error, [ast], messages}
end
test "Associated" do
markdown = "Before\n{:hello=world}"
ast = tag("p", "Before", hello: "world")
messages = []
assert as_ast(markdown) == {:ok, [ast], messages}
end
test "Associated in between" do
markdown = "Before\n{:hello=world}\nAfter"
ast = [p("Before", hello: "world"), p("After")]
messages = []
assert as_ast(markdown) == {:ok, ast, messages}
end
test "Associated and incorrect" do
markdown = "Before\n{:hello}"
ast = tag("p", "Before")
messages = [{:warning, 2, "Illegal attributes [\"hello\"] ignored in IAL" }]
assert as_ast(markdown) == {:error, [ast], messages}
end
test "Associated and partly incorrect" do
markdown = "Before\n{:hello title=world}"
ast = p("Before", title: "world")
messages = [{:warning, 2, "Illegal attributes [\"hello\"] ignored in IAL" }]
assert as_ast(markdown) == {:error, [ast], messages}
end
test "Associated and partly incorrect and shortcuts" do
markdown = "Before\n{:#hello .alpha hello title=world .beta class=\"gamma\" title='class'}"
ast = p("Before", class: "gamma beta alpha", id: "hello", title: "class world")
messages = [{:warning, 2, "Illegal attributes [\"hello\"] ignored in IAL" }]
assert as_ast(markdown) == {:error, [ast], messages}
end
# https://github.com/pragdave/earmark/issues/367
@tag :wip
test "In tight lists?" do
markdown = "- Before\n{:.alpha .beta}"
ast = tag("ul", tag("li", "Before", class: "beta alpha"))
messages = []
assert as_ast(markdown) == {:ok, [ast], messages}
end
end
end
# SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/test/acceptance/ast/block_quotes_test.exs 0000664 0000000 0000000 00000007603 14531042673 0024727 0 ustar 00root root 0000000 0000000 defmodule Acceptance.Ast.BlockQuotesTest do
use ExUnit.Case, async: true
import Support.Helpers, only: [as_ast: 1, as_ast: 2]
import EarmarkAstDsl
@moduletag :ast
describe "with breaks: true" do
test "acceptance test 490 with breaks" do
markdown = "> bar\nbaz\n> foo\n"
ast = bq(p(brlist(~w[bar baz foo])))
messages = []
assert as_ast(markdown, breaks: true) == {:ok, [ast], messages}
end
test "acceptance test 580 with breaks" do
markdown = "1. foo\nbar"
ast = tag("ol", [tag("li", brlist(~w[foo bar]))])
messages = []
assert as_ast(markdown, breaks: true) == {:ok, [ast], messages}
end
test "acceptance test 581 with breaks" do
markdown = "* a\n b\nc"
ast = tag("ul", tag("li", brlist(~w[a b c])))
messages = []
assert as_ast(markdown, breaks: true) == {:ok, [ast], messages}
end
test "acceptance test 582 with breaks" do
markdown = "* x\n a\n| A | B |"
ast = tag("ul", tag("li", brlist(["x", "a", "| A | B |"])))
messages = []
assert as_ast(markdown, breaks: true) == {:ok, [ast], messages}
end
test "acceptance test 583 with breaks" do
markdown = "* x\n | A | B |"
ast = tag("ul", tag("li", brlist(["x", "| A | B |"])))
messages = []
assert as_ast(markdown, breaks: true) == {:ok, [ast], messages}
end
test "acceptance test 630 with breaks" do
markdown = "\\*not emphasized\\*\n\\[not a link](/foo)\n\\`not code\\`\n1\\. not a list\n\\* not a list\n\\# not a header\n\\[foo]: /url \"not a reference\"\n"
ast = p(brlist([
"*not emphasized*",
"[not a link](/foo)",
"`not code`",
"1. not a list",
"* not a list",
"# not a header",
"[foo]: /url \"not a reference\""]))
messages = []
assert as_ast(markdown, breaks: true) == {:ok, [ast], messages}
end
test "more" do
markdown = "> # Foo\n> bar\n> baz\n"
ast = bq([tag("h1", "Foo"), p(brlist(~w[bar baz]))])
messages = []
assert as_ast(markdown, breaks: true) == {:ok, [ast], messages}
end
end
describe "Block Quotes" do
test "quote my block" do
markdown = "> Foo"
ast = bq(p("Foo"))
messages = []
assert as_ast(markdown) == {:ok, [ast], messages}
end
test "and block my quotes" do
markdown = "> # Foo\n> bar\n> baz\n"
ast = bq([tag("h1", "Foo"), p("bar\nbaz")])
messages = []
assert as_ast(markdown) == {:ok, [ast], messages}
end
test "linient we are" do
markdown = "> bar\nbaz\n> foo\n"
ast = bq(p("bar\nbaz\nfoo"))
messages = []
assert as_ast(markdown) == {:ok, [ast], messages}
end
test "lists in blockquotes? Coming up Sir" do
markdown = "> - foo\n- bar\n"
ast = [bq(tag("ul", tag("li", "foo"))), tag("ul", tag("li", "bar"))]
messages = []
assert as_ast(markdown) == {:ok, ast, messages}
end
test "indented case" do
markdown = " > - foo\n- bar\n"
ast = [bq(tag("ul", tag("li", "foo"))), tag("ul", tag("li", "bar"))]
messages = []
assert as_ast(markdown) == {:ok, ast, messages}
end
end
describe "Nested blockquotes" do
test "just two blocks" do
markdown = ">>foo\n> bar\n"
ast = bq(bq(p("foo\nbar")))
messages = []
assert as_ast(markdown) == {:ok, [ast], messages}
end
test "attached list" do
markdown = " >- foo\n- bar\n"
ast = [bq(tag("ul", tag("li", "foo"))), tag("ul", tag("li", "bar"))]
messages = []
assert as_ast(markdown) == {:ok, ast, messages}
end
end
defp bq(content, atts \\ []) do
tag("blockquote", content, atts)
end
defp br, do: void_tag("br")
defp brlist(elements) do
elements
|> Enum.intersperse(br())
end
end
# SPDX-License-Identifier: Apache-2.0
earmark_parser-1.4.39/test/acceptance/ast/comment_test.exs 0000664 0000000 0000000 00000002627 14531042673 0023700 0 ustar 00root root 0000000 0000000 defmodule Acceptance.Ast.CommentTest do
use ExUnit.Case, async: true
import Support.Helpers, only: [as_ast: 1]
describe "HTML Comments" do
test "one line" do
markdown = ""
ast = {:comment, [], [" Hello "], %{comment: true}}
messages = []
assert as_ast(markdown) == {:ok, [ast], messages}
end
test "more lines" do
markdown = ""
ast = {:comment, [], [" Hello", " World"], %{comment: true}}
messages = []
assert as_ast(markdown) == {:ok, [ast], messages}
end
test "what about the closing" do
markdown = "garbish"
ast = {:comment, [], [" Hello", " World "], %{comment: true}}
messages = []
assert as_ast(markdown) == {:ok, [ast], messages}
end
test "what about the fenced closing" do
markdown = """
garbish
"""
ast = {:comment, [], [" Hello", "```elixir", " World "], %{comment: true}}
messages = []
assert as_ast(markdown) == {:ok, [ast], messages}
end
test "fence at the end" do
markdown = """
", %Line.HtmlComment{complete: true}, nil},
{"", %Line.HtmlComment{complete: true}, "does it make sense?"},
{"