Coder Social home page Coder Social logo

commonmark.jl's Introduction

CommonMark

A CommonMark-compliant parser for Julia.

CI Codecov

Interface

using CommonMark

Create a markdown parser with the default CommonMark settings and then add footnote syntax to our parser.

parser = Parser()
enable!(parser, FootnoteRule())

Parse some text to an abstract syntax tree from a String:

ast = parser("Hello *world*")

Parse the contents of a source file:

ast = open(parser, "document.md")

Write ast to a string.

body = html(ast)
content = "<head></head><body>$body</body>"

Write to a file.

open("file.tex", "w") do file
    latex(file, ast)
    println(file, "rest of document...")
end

Or write to a buffer, such as stdout.

term(stdout, ast)

Output Formats

Supported output formats are currently:

  • html
  • latex
  • term: colourised and Unicode-formatted for display in a terminal.
  • markdown
  • notebook: Jupyter notebooks.

Extensions

Extensions can be enabled using the enable! function and disabled using disable!.

Typography

Convert ASCII dashes, ellipses, and quotes to their Unicode equivalents.

enable!(parser, TypographyRule())

Keyword arguments available for TypographyRule are

  • double_quotes
  • single_quotes
  • ellipses
  • dashes

which all default to true.

Admonitions

enable!(parser, AdmonitionRule())

Front matter

Fenced blocks at the start of a file containing structured data.

+++
[heading]
content = "..."
+++

The rest of the file...

The block must start on the first line of the file. Supported blocks are:

  • ;;; for JSON
  • +++ for TOML
  • --- for YAML

To enable provide the FrontMatterRule with your choice of parsers for the formats:

using JSON
enable!(parser, FrontMatterRule(json=JSON.Parser.parse))

You can access front matter from a parsed file using frontmatter. As follows.

ast = open(parser, "document.md")
meta = frontmatter(ast)

Footnotes

enable!(parser, FootnoteRule())

Math

Julia-style inline and display maths:

Some ``\LaTeX`` math:

```math
f(a) = \frac{1}{2\pi}\int_{0}^{2\pi} (\alpha+R\cos(\theta))d\theta
```

Enabled with:

enable!(parser, MathRule())

Dollar-style inline and display math is also available using

enable!(parser, DollarMathRule())

Supported syntax:

  • single dollar signs surrounding inline math,
  • double dollars surrounding a single line paragraph for display math.

For more complex math, such as multiline display math, use the literal block syntax available with MathRule().

Tables

Pipe-style tables, similar to GitHub's tables. Literal | characters that are not wrapped in other syntax such as * must be escaped with a backslash. The number of columns in the table is specified by the second line.

| Column One | Column Two | Column Three |
|:---------- | ---------- |:------------:|
| Row `1`    | Column `2` |              |
| *Row* 2    | **Row** 2  | Column ``|`` |

Rows with more cells than specified have the trailing cells discarded, and rows with less cells are topped up with empty cells.

Enabled with:

enable!(parser, TableRule())

Raw Content

Overload literal syntax to support passing through any type of raw content.

enable!(parser, RawContentRule())

By default RawContentRule will handle inline and block content in HTML and LaTeX formats.

This is raw HTML: `<img src="myimage.jpg">`{=html}.

And here's an HTML block:

```{=html}
<div id="main">
 <div class="article">
```
```{=latex}
\begin{tikzpicture}
\draw[gray, thick] (-1,2) -- (2,-4);
\draw[gray, thick] (-1,-1) -- (2,2);
\filldraw[black] (0,0) circle (2pt) node[anchor=west] {Intersection point};
\end{tikzpicture}
```

This can be used to pass through different complex content that can't be easily handled by CommonMark natively without any loss of expressiveness.

Custom raw content handlers can also be passed through when enabling the rule. The naming scheme is <format>_inline or <format>_block.

enable!(p, RawContentRule(rst_inline=RstInline))

The last example would require the definition of a custom RstInline struct and associated display methods for all supported output types, namely: html, latex, and term. When passing your own keywords to RawContentRule the defaults are not included and must be enabled individually.

Attributes

Block and inline nodes can be tagged with arbitrary metadata in the form of key/value pairs using the AttributeRule extension.

enable!(p, AttributeRule())

Block attributes appear directly above the node that they target:

{#my_id color="red"}
# Heading

This will attach the metadata id="my_id" and color="red" to # Heading.

Inline attributes appear directly after the node that they target:

*Some styled text*{background="green"}.

Which will attach metadata background="green" to the emphasised text Some styled text.

CSS-style shorthand syntax #<name> and .<name> are available to use in place of id="<name>" and class="name". Multiple classes may be specified sequentially.

AttributeRule does not handle writing metadata to particular formats such as HTML or LaTeX. It is up to the implementation of a particular writer format to make use of available metadata itself. The built-in html and latex outputs make use of included attributes. html will include all provided attributes in the output, while latex makes use of only the #<id> attribute.

Citations

Use the following to enable in-text citations and reference list generation:

enable!(p, CitationRule())

Syntax for citations is similar to what is offered by Pandoc. Citations start with @.

Citations can either appear in square brackets [@id], or they can be written as
part of the text like @id. Bracketed citations can contain more than one
citation; separated by semi-colons [@one; @two; and @three].

{#refs}
# References

A reference section that will be populated with a list of all references can be marked using a {#refs} attribute from AttributeRule at the toplevel of the document. The list will be inserted after the node, in this case # References.

Citations and reference lists are formatted following the Chicago Manual of Style. Styling will, in future versions, be customisable using Citation Style Language styles.

The reference data used for citations must be provided in a format matching CSL JSON. Pass this data to CommonMark.jl when writing an AST to a output format.

html(ast, Dict{String,Any}("references" => JSON.parsefile("references.json")))

CSL JSON can be exported easily from reference management software such as Zotero or generated via pandoc-citeproc --bib2json or similar. The references data can be provided by the front matter section of a document so long as the FrontMatterRule has been enabled, though this does require writing your CSL data manually.

Note that the text format of the reference list is not important, and does not have to be JSON data. So long as the shape of the data matches CSL JSON it is valid. Below we use YAML references embedded in the document's front matter:

---
references:
- id: abelson1996
  author:
    - family: Abelson
      given: Harold
    - family: Sussman
      given: Gerald Jay
  edition: 2nd Editon
  event-place: Cambridge
  ISBN: 0-262-01153-0
  issued:
    date-parts:
      - - 1996
  publisher: MIT Press/McGraw-Hill
  publisher-place: Cambridge
  title: Structure and interpretation of computer programs
  type: book
---

Here's a citation [@abelson1996].

{#refs}
# References

Auto Identifiers

Headings within a document can be assigned ids automatically using

enable!(p, AutoIdentifierRule())

Identifiers are determined with CommonMark.slugify, which is based on the algorithm used by Pandoc. Non-unique identifiers are suffixed with a numeric counter and so cannot be considered stable. If you need stable identifiers then you should use AttributeRule to assign stable ids manually.

CommonMark Defaults

Block rules enabled by default in Parser objects:

  • AtxHeadingRule()
  • BlockQuoteRule()
  • FencedCodeBlockRule()
  • HtmlBlockRule()
  • IndentedCodeBlockRule()
  • ListItemRule()
  • SetextHeadingRule()
  • ThematicBreakRule()

Inline rules enabled by default in Parser objects:

  • AsteriskEmphasisRule()
  • AutolinkRule()
  • HtmlEntityRule()
  • HtmlInlineRule()
  • ImageRule()
  • InlineCodeRule()
  • LinkRule()
  • UnderscoreEmphasisRule()

These can all be disabled using disable!. Note that disabling some parser rules may result in unexpected results. It is recommended to be conservative in what is disabled.

Note

Until version 1.0.0 the rules listed above are subject to change and should be considered unstable regardless of whether they are exported or not.

Writer Configuration

When writing to an output format configuration data can be provided by:

  • passing a Dict{String,Any} to the writer method,
  • front matter in the source document using the FrontMatterRule extension.

Front matter takes precedence over the passed Dict.

Notable Variables

Values used to determine template behaviour:

  • template-engine::Function Used to render standalone document templates.

    No default is provided by this package. The template-engine function should follow the interface provided by Mustache.render. It is recommended to use Mustache.jl to provide this functionalilty.

    Syntax for opening and closing tags used by CommonMark.jl is ${...}. See the templates in src/writers/templates for usage examples.

  • <format>.template.file::String Custom template file to use for standalone <format>.

  • <format>.template.string::String Custom template string to use for standalone <format>.

Generic variables that can be included in templates to customise documents:

  • abstract::String Summary of the document.

  • authors::Vector{String} Vector of author names.

  • date::String Date of file generation.

  • keywords::Vector{String} Vector of keywords to be included in the document metadata.

  • lang::String Language of the document.

  • title::String Title of the document.

  • subtitle::String Subtitle of the document.

Format-specific variables that should be used only in a particular format's template. They are namespaced to avoid collision with other variables.

  • html

    • html.css::Vector{String} Vector of CSS files to include in document.

    • html.js::Vector{String} Vector of JavaScript files to include in document.

    • html.header::String String content to add at end of <head>.

    • html.footer::String String content to add at end of <body>.

  • latex

    • latex.documentclass::String Class file to use for document. Default is article.

    • latex.preamble::String String content to add directly before \begin{document}.

The following are automatically available in document templates.

  • body::String Main content of the page.

  • curdir::String Current directory.

  • outputfile::String Name of file that is being written to. When writing to an in-memory buffer this variable is not defined.

commonmark.jl's People

Contributors

chriselrod avatar dependabot[bot] avatar fonsp avatar github-actions[bot] avatar haberdashpi avatar michaelhatherly avatar mortenpi avatar rratic avatar tfiers avatar timholy avatar tlienart avatar vdayanand avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

commonmark.jl's Issues

Table parsing fail for some unicode identifiers

julia> using CommonMark

julia> p=Parser(); enable!(p, TableRule());

julia> md=p("""
       | 标 | 题 | 们 |
       | --- | --- | --- |
       | d | 文 字 | g |
       """); html(md)
"<table><thead><tr><th align=\"left\">标</th><th align=\"left\">题  们</th><th align=\"left\"></th></tr></thead><tbody><tr><td align=\"left\">d</td><td align=\"left\">文 字  g</td><td align=\"left\"></td></tr></tbody></table>"

intended:

d 文 字 g

actual:

题 们
d文 字 g

Error handling multi unicode text

( found these while building docs

p=Parser(); enable!(p, AdmonitionRule());

julia> p("""
       !!! note "Ju 的文字"
           Ju
       """)
ERROR: StringIndexError: invalid index [18], valid nearby indices [17]=>'', [20]=>''
...

julia> raw="在上一篇[向量化编程与广播(1):引言](20220515.md)中我们介绍了 `meshgrid` 这个函数的由来,以及表明广播的存在使得它在大多数时候已经成为一个不 必要的函数了。在这一篇中我们将更具体地介绍广播的基本规则,从而在实际编程中可以能够更自如地使用这一概念。"; p(raw)
Error showing value of type CommonMark.Node:
ERROR: StringIndexError: invalid index [84], valid nearby indices [83]=>'', [86]=>''
Stacktrace:
  [1] string_index_err(s::String, i::Int64)
    @ Base .\strings\string.jl:12
  [2] findprev(testf::CommonMark.var"#35#36", s::String, i::Int64)
    @ Base .\strings\search.jl:408
  [3] print_literal_part(r::CommonMark.Writer{CommonMark.Term, IOContext{Base.TTY}}, lit::String, rec::Int64)
...

HTML injection: block vs inline

Hello!

I'm finally about to do some actual implementations to replace Franklin's use of Julia's Markdown module by CommonMark.jl. In doing so, I'm also considering refactoring a bit how Franklin produces stuff (details here but probably not relevant for my question). Overall the gist would be to have the following workflow:

input (md) --> Franklin --> inter1 (md) --> CommonMark --> inter2 (html) --> Franklin --> output (html)

the relevant part for the question is the first one: taking "Franklin"-Markdown and converting it into CommonMark markdow (note that, in case you're wondering, I don't think the whole lot can be done in CommonMark as some parsing patterns are quite different).
Franklin could basically read what it considers "special-blocks" from the input, convert those into HTML itself, then form a CommonMark text with those weaved in, so basically inter1 (md) would look like

Some standard markdown 

```{=html}
a special block converted into html by Franklin
```

Some more markdown

which CommonMark.jl could then parse and generate HTML for with the RawContentRule()

Question

(apologies for the long intro)

in CM.jl there's a distinction between inline-html and block-html; and, as far as I can tell you can only use inline HTML in nested environments, things like list items or table cells; on the other hand inline-html cannot have \n in it

this is problematic for me because I cannot generally assume that the resolution of a "special block" by Franklin will be single or multi-line.
Could there be a way to not have this distinction? (possibly build a specific rule?) I think what I want is to be able to just inject blocks of raw html anywhere (including in list items, table cells, ...) without assuming that there will be a single or multiple lines.

Thanks!

Nesting in ordered list

Is this meant to be?


julia> a = """
       1. abc
       2. def
         1. ghi
       """
"1. abc\n2. def\n  1. ghi\n"

julia> p(a)
  1. abc
  
  2. def
  
  3. ghi


julia> a = """
       * abc
       * def
         * ghi
       """
"* abc\n* def\n  * ghi\n"

julia> p(a)
  ● abc
  
  ● def
    
     ○ ghi

or must sub-levels be indicated differently for ordered lists?

Keep reference links as references

From what I gather from

if !isempty(reflabel)
# Lookup rawlabel in refmap.
link = get(parser.refmap, normalize_reference(reflabel), nothing)
if link !== nothing
dest, title = link
matched = true
end
end
end
if matched
node = Node(is_image ? Image() : Link())
node.t.destination = dest

is that link labels get expanded to full links during parsing. Would it be possible / reasonable to somehow have a parser option that would keep the link labels and link reference definitions intact in the AST?

The use case I have in mind is to write a small script to automatically verify and possibly update such reference links and their definitions (specifically in the Documenter CHANGELOG).

Scoping for Franklin

Hello Michael,

I'm interested in slowly moving over to CommonMark.jl as it's likely to solve a bunch of sneaky issues in Franklin. You mentioned on Discourse you'd be happy to discuss inclusion of extensions that would be relevant to franklin (thanks!). At a high level the parsing works as follows:

  1. go over source page, find tokens
  2. associate tokens into blocks from an opening token to a closing token
  3. resolve the blocks recursively

The resolution of "plain text" blocks is passed to Julia's Markdown for things like links, lists, ... other blocks that are processed first by Franklin include "command blocks", "div blocks", math blocks, code blocks etc. I don't think it would make a lot of sense to use CommonMark.jl for those yet.

The first obvious step is to use CommonMark.jl for the "level 0" instead of Julia's Markdown. I had already looked into using GithubMarkdown.jl (which wraps around pandoc) for this but there is one key thing I must be able to do: be able to ignore the conversion of indented blocks. (I want users to be able to use indentation for other stuff like making their div blocks clearer). Currently I capture the result of Markdown.parse, and hack them back into a paragraph block so that they're not converted. How could I do something like it in with CommonMark.jl?

The key small function is https://github.com/tlienart/Franklin.jl/blob/772dde56872b8834bf52ec73c05b4760a1844ddb/src/converter/markdown/utils.jl#L9-L30

which, at a high level, takes the level 0 stuff ("plain markdown") and converts it to HTML. Note that there are two tricks:

  1. (key thing) indented code blocks are forced back into paragraphs
  2. (minor thing) {{...}} are converted by the Markdown module into html entities, I don't want that to happen, is that something that would happen with CommonMark.jl ?

Assuming I could do both of these things easily with CommonMark.jl, it should be mostly straightforward to make Franklin use it (and I'd be very happy about it, finally proper lists etc...!)

Thanks and apologies for the long winded message

Discoverability on JuliaHub: should be easier to find when searching for "Markdown"

Currently the description of this repo is:

A CommonMark-compliant parser for Julia.

AFAIK JuliaHub adopts the repo description as the package description, updating the entry whenever you make a release. AFAIK the package description influences the JuliaHub package search results.

I think adding "Markdown" into your repo description would help the package be more discoverable via JuliaHub, so I propose this repo description:

A CommonMark-compliant Markdown parser for Julia.

Markdown printing adds unwanted newlines

ref domluna/JuliaFormatter.jl#603

Wondering if there's a way to disable this behaviour

julia> s = """
       \"\"\"
           solve_knapsack(
               optimizer,
               data_filename::String,
               config::_AbstractConfiguration,
           )

       Solve the knapsack problem and return the optimal primal solution

       ## Arguments

         - `optimizer`: an object that can be passed to `JuMP.Model` to construct a new
           JuMP model.
         - `data_filename`: the filename of a JSON file containing the data for the
           problem.
         - `config`: an object to control the type of knapsack model constructed.
           Valid options are:
             + `BinaryKnapsackConfig()`
             + `IntegerKnapsackConfig()`

       ## Returns
       \"\"\"
       foo() = nothing
       """
"\"\"\"\n    solve_knapsack(\n        optimizer,\n        data_filename::String,\n        config::_AbstractConfiguration,\n    )\n\nSolve the knapsack problem and return the optimal primal solution\n\n## Arguments\n\n  - `optimizer`: an object that can be passed to `JuMP.Model` to construct a new\n    JuMP model.\n  - `data_filename`: the filename of a JSON file containing the data for the\n    problem.\n  - `config`: an object to control the type of knapsack model constructed.\n    Valid options are:\n      + `BinaryKnapsackConfig()`\n      + `IntegerKnapsackConfig()`\n\n## Returns\n\"\"\"\nfoo() = nothing\n"

julia> parser = Parser()
Parser(Node(CommonMark.Document))

julia> ast = parser(s)
 """ solve_knapsack( optimizer, data_filename::String, config::_AbstractConfiguration, )

 Solve the knapsack problem and return the optimal primal solution

 ## Arguments

  ● optimizer: an object that can be passed to JuMP.Model to construct a new JuMP model.

  ● data_filename: the filename of a JSON file containing the data for the problem.

  ● config: an object to control the type of knapsack model constructed. Valid options are:

     ○ BinaryKnapsackConfig()

     ○ IntegerKnapsackConfig()

 ## Returns

 """ foo() = nothing


julia> markdown(ast) |> print
"""
solve_knapsack(
optimizer,
data_filename::String,
config::_AbstractConfiguration,
)

Solve the knapsack problem and return the optimal primal solution

## Arguments

  - `optimizer`: an object that can be passed to `JuMP.Model` to construct a new
    JuMP model.
  - `data_filename`: the filename of a JSON file containing the data for the
    problem.
  - `config`: an object to control the type of knapsack model constructed.
    Valid options are:

      + `BinaryKnapsackConfig()`
      + `IntegerKnapsackConfig()`

## Returns

"""
foo() = nothing

julia>

If I assign the result of markdown printing to another variable and try it again it produces another newline

julia> markdown(ast) |> print
"""
solve_knapsack(
optimizer,
data_filename::String,
config::_AbstractConfiguration,
)

Solve the knapsack problem and return the optimal primal solution

## Arguments

  - `optimizer`: an object that can be passed to `JuMP.Model` to construct a new
    JuMP model.

  - `data_filename`: the filename of a JSON file containing the data for the
    problem.
  - `config`: an object to control the type of knapsack model constructed.
    Valid options are:

      + `BinaryKnapsackConfig()`
      + `IntegerKnapsackConfig()`

## Returns

"""
foo() = nothing

At this point no new newlines are added.

Bug in MathRule?

using CommonMark

parser = Parser()
enable!(parser, MathRule())
parser(
"""
Interpolate using `\\\$`
"""
)

I get

BoundsError: attempt to access String
  at index [23]
checkbounds at basic.jl:194 [inlined]
codeunit at string.jl:89 [inlined]
getindex at string.jl:210 [inlined]
peek at parsers.jl:14 [inlined]
read(::CommonMark.InlineParser, ::Type{Char}) at parsers.jl:20
parse_inline(::CommonMark.InlineParser, ::CommonMark.Node) at inlines.jl:108
parse_inlines(::CommonMark.InlineParser, ::CommonMark.Node) at inlines.jl:121
parse at inlines.jl:129 [inlined]
process_inlines(::Parser, ::CommonMark.Node) at blocks.jl:416
parse(::Parser, ::Base.GenericIOBuffer{Array{UInt8,1}}; kws::Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}) at blocks.jl:446
parse at blocks.jl:427 [inlined]
#_#25 at blocks.jl:451 [inlined]
Parser at blocks.jl:451 [inlined]
#_#24 at blocks.jl:450 [inlined]
(::Parser)(::String) at blocks.jl:450
top-level scope at test.jl:5

I'm not 100% sure this is a bug, but if it's not, maybe there could be a nicer error? This came from one of the JuliaFormatter tests

Duplicate rules when enabling/disabling rules

The enable! / disable! functions seem to duplicate the rules. I presume this is not intended?

If you disable! a rule, it does remove it, but it seems to duplicate every other rule:

julia> using CommonMark

julia> p = Parser()
Parser(Node(CommonMark.Document))

julia> disable!(p, LinkRule())
Parser(Node(CommonMark.Document))

julia> p.rules
36-element Vector{Any}:
 BlockQuoteRule()                    <-------------------------
 AtxHeadingRule()
 FencedCodeBlockRule()
 HtmlBlockRule()
 SetextHeadingRule()
 ThematicBreakRule()
 ListItemRule()
 IndentedCodeBlockRule()
 AutolinkRule()
 InlineCodeRule()
 AsteriskEmphasisRule()
 UnderscoreEmphasisRule()
 CommonMark.BackslashEscapeRule()
 HtmlInlineRule()
 HtmlEntityRule()
 ImageRule()
 CommonMark.TextRule()
 CommonMark.NewlineRule()
 BlockQuoteRule()                    <-------------------------
 AtxHeadingRule()
 FencedCodeBlockRule()
 HtmlBlockRule()
 SetextHeadingRule()
 ThematicBreakRule()
 ListItemRule()
 IndentedCodeBlockRule()
 AutolinkRule()
 InlineCodeRule()
 AsteriskEmphasisRule()
 UnderscoreEmphasisRule()
 CommonMark.BackslashEscapeRule()
 HtmlInlineRule()
 HtmlEntityRule()
 ImageRule()
 CommonMark.TextRule()
 CommonMark.NewlineRule()

Similarly, enable!-ing a rule that is already enabled, will duplicate it in the list, rather than overriding it:

julia> using CommonMark

julia> p = Parser()
Parser(Node(CommonMark.Document))

julia> enable!(p, LinkRule())
Parser(Node(CommonMark.Document))

julia> p.rules
20-element Vector{Any}:
 BlockQuoteRule()
 AtxHeadingRule()
 FencedCodeBlockRule()
 HtmlBlockRule()
 SetextHeadingRule()
 ThematicBreakRule()
 ListItemRule()
 IndentedCodeBlockRule()
 AutolinkRule()
 InlineCodeRule()
 AsteriskEmphasisRule()
 UnderscoreEmphasisRule()
 CommonMark.BackslashEscapeRule()
 HtmlInlineRule()
 HtmlEntityRule()
 LinkRule()                    <------------------
 ImageRule()
 CommonMark.TextRule()
 CommonMark.NewlineRule()
 LinkRule()                    <------------------

For context: I am working on an approach for implementing the invalid reference link callback that would work via an option to LinkRule, so I want to replace the vanilla LinkRule with one that has the callback as a field. Right now, if I enable! it, the original LinkRule still exists and takes precedence, if I understand correctly what is going on.

Lists (again), conversion to LaTeX

Input string:

s = """
**Unordered**

* item a
* item b
* item c

**Ordered**

1. item a
1. item b
1. item c

**Nested**

* item a
  * subitem a.a
    * subsubitem a.a
  * subitem a.b
* item b

**Mixed nesting**

* item a
  * subitem a.a
    1. subsubitem a.a
    1. subsubitem a.b
  * subitem a.b
* item b
"""

LaTeX output

CommonMark.latex(p(s))
\textbf{Unordered}\par
\begin{itemize}
\setlength{\itemsep}{0pt}
\setlength{\parskip}{0pt}
\item
item a\par
\item
item b\par
\item
item c\par
\end{itemize}
\textbf{Ordered}\par
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{1}
\setlength{\itemsep}{0pt}
\setlength{\parskip}{0pt}
\item
item a\par
\item
item b\par
\item
item c\par
\end{enumerate}
\textbf{Nested} (nesting must be sufficiently indented)\par
\begin{itemize}
\setlength{\itemsep}{0pt}
\setlength{\parskip}{0pt}
\item
item a\par
\begin{itemize}
\setlength{\itemsep}{0pt}
\setlength{\parskip}{0pt}
\item
subitem a.a\par
\begin{itemize}
\setlength{\itemsep}{0pt}
\setlength{\parskip}{0pt}
\item
subsubitem a.a\par
\end{itemize}
\item
subitem a.b\par
\end{itemize}
\item
item b\par
\end{itemize}
\textbf{Mixed nesting}\par
\begin{itemize}
\setlength{\itemsep}{0pt}
\setlength{\parskip}{0pt}
\item
item a\par
\begin{itemize}
\setlength{\itemsep}{0pt}
\setlength{\parskip}{0pt}
\item
subitem a.a\par
\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{1}
\setlength{\itemsep}{0pt}
\setlength{\parskip}{0pt}
\item
subsubitem a.a\par
\item
subsubitem a.b\par
\end{enumerate}
\item
subitem a.b\par
\end{itemize}
\item
item b\par
\end{itemize}

Here's a screenshot using lualatex:

Screenshot 2021-07-14 at 22 41 25

Edit: long story short, the counter is wrong. It should be 0 (I think), PR #24

Thanks!

HypertextLiteral.jl + CommonMark.jl = 🤯

I made a combination of HypertextLiteral.jl by @clarkevans and CommonMark.jl, and I think it is really cool!!

DEMO NOTEBOOK

Screenshots

Schermafbeelding 2021-12-16 om 13 17 09

Schermafbeelding 2021-12-16 om 13 15 48

Features

The list of features is really simple to explain: it is everything that CommonMark gives, plus everything that HypertextLiteral gives! This includes:

  • CommonMark! Markdown but less glitchy!
  • Really flexible interpolation support with infinite nesting and syntax highlighting (since it is a @md(""" macro instead of md""")
  • Interpolate Julia objects into <script> to automatically convert to JS literals
  • Context-aware HTML escaping
  • Automatic quote wrapping for HTML attributes
  • Use a Dict or NamedTuple for the style attribute inside an HTML tag

Implementation

Also cool: the code is extremely short!

macro md(expr)
	cm_parser = CommonMark.Parser()
	quote
		result = @htl($expr)
		htl_output = repr(MIME"text/html"(), result)

		$(cm_parser)(htl_output)
	end
end

It is essentially the @htl macro for HypertextLiteral.jl, but the result is passed through a CommonMark parser. This works, because:

  • CommonMark allows raw HTML
  • HypertextLiteral leaves literal content unchanged, so hello *world* appears exactly

Let me know what you think! Perhaps it can be added to this package as a new macro, or we can make a new package.

please bump the [compat] for JSON

The current version of JSON.jl is 1.
Using an old version in [compat] can cause ERROR: Unsatisfiable requirements when there are other packages added that do not list the old version in thier [compat].
You can specify multiple versions, if there is reason to do so.

Separate package for Markdown AST

I flouted this idea a long time ago, but I would now like to open this up for more specific discussion and feedback.

In short, I think it would be useful to have some sort of an interface package for representing the Markdown AST (basically, the Node type, the AbstractContainer abstract types, and the specific container types; + APIs). At the same time decoupling it from the parser and writer details, to keep it stable and lightweight. I also imagine it would provide clean, documented APIs to then work with that AST (e.g. returning all children, accessing the node data; as far as I can tell, CommonMark currently doesn't really provide that many official APIs for this kind of stuff).

It would allow parsers and consumers (e.g. writers, Documenter) to pass Markdown AST back and forth via a standard interface. In principle, it could all also live here and achieve the same thing, but the AST would then be versioned together with changes to e.g. parsers and writers. I feel that having those things versioned separately would be good (the AST package would be very conservative, but CommonMark could still make rapid changes).

The design of the AST here (a single Node type that handles the structure of the AST), when compared to the Markdown standard library AST, feels much better (provides type stability, but also easily allows a lot of general operations on the whole tree without having to special case for each node type). As such, I think lifting the AST from here to a separate package and then sprinkling some APIs and documentation around it could largely be all that is needed. CommonMark, I imagine, would then depend on the AST package and use the AST types from there.

If this seems like a reasonable idea, I am volunteering to do the legwork. This would be a part of some refactoring and cleaning up of Documenter I would like to do anyway.


A few other thoughts:

  • I do think that the AST package should be minimal. Just the AST, no parsers or writers, which should stay here. The only writer that should probably be moved to the AST package is the terminal one, as pretty a representation of the AST in the terminal is probably useful for the users of the interface package.

  • I think it should also explicitly support implementing new nodes defined by the user (technically, just a new AbstractContainer, which can already be done). In particular, I would like to switch the internal representation of Documenter documents over to such a standardized AST. But then in the early parsing steps Documenter would replace e.g. the at-code-blocks with its own nodes.

  • Related to the both previous points: I would suggest that the set of node types in the external package would be the union of standard CommonMark nodes + Julia Markdown nodes. Any additional extensions would stay here.

  • The CommonMark Node type seems to contain some parser-specific information, which could be out of place in the interface package. So I am wondering if that would create some friction for using the interface package here directly. Generally, I can imagine three different ways to implement the interface package:

    1. As said above, simply lift all the relevant types from here to a separate package, and maybe create some API for attaching custom metadata to a node that CommonMark could use?
    2. The interface package could have its own Node implementation, but there would be some conversion functionality between the ASTs.
    3. Finally, and I am not sure if this is even doable, but perhaps the interface package could just provide and abstract node type and define APIs, and users could then implement their own node types. Consumers would then just rely on the API.
  • Just as a quick side project, I might revive CMark.jl, which links against the libcmark and libcmark-gfm C parsers, and have it also produce this standardized AST. I don't think users should use the C parsers if they're parsing Markdown (CommonMark.jl is preferred), but this could be useful for testing.

Newline `\n` is not preserved in latex output for codeblocks

I am currently trying to move from LaTeX to Markdown for writing an article which contains some code. For code blocks which get put into verbatim environment I found that \n is not preserved and as well _ is being escaped with \, whereas in html output such things does not happen. MWE:

using CommonMark
markdown = Parser()
ast = markdown("""```
x_i = 1
y = 2
```""") 

html(ast) gives "<pre><code>x_i = 1\ny = 2\n</code></pre>\n" whereas latex(ast) is "\\begin{verbatim}\nx\\_i = 1y = 2\n\\end{verbatim}\n" .

Interpolate a vector of items

The @md_str from Markdown, and the @htl macro from https://github.com/MechanicalRabbit/HypertextLiteral.jl will render items one-by-one if you interpolate a vector or generator:

Schermafbeelding 2021-12-16 om 12 37 31

worlds = [HTML("<div>world $i</div>") for i in 1:3]

The @cm_str macro renders the vector itself. Is this intentional? Is there another way to render all items? (My solution right now is to wrap the array in @htl("$(worlds)") to turn it into a single HTML renderable.

Indentation within HTML block with empty lines

The following input:

asdf

<blockquote class="banner">
    <p>Hello world.</p>
</blockquote>

produces this output:

<p>asdf</p>

<blockquote class="banner">
    <p>Hello world.</p>
</blockquote>

but if I add an empty line inside the block, the contents get escaped: 😥

asdf

<blockquote class="banner">

    <p>Hello world.</p>
</blockquote>
<p>asdf</p>
<blockquote class="banner">
<pre><code>&lt;p&gt;Hello world.&lt;/p&gt;
</code></pre>
</blockquote>

`Raw/Noop` extension

Could there be an extension that doesn't alter the input at all? This is required to properly format docstrings for domluna/JuliaFormatter.jl#231. The use case is using the markdown package to find Julia code in the docstrings, format it, and then print the output. Aside from what is formatted nothing else should be altered.

Here's an example

        str = """
        \"""
        \\\\
        \"""
        x"""

Currently using Markdown or CommonMark with RawContentRule the output is not the same as the input.

using Markdown
using CommonMark

Markdown.plain(Markdown.parse(str)) == str # false

parser = Parser()
enable!(parser, RawContentRule())
markdown(parser(str)) == str # false

Implement Interpolation Extension

I can imagine there is a bit of a catch-22 situation here...libraries currently using Markdown implicitly rely upon the interpolation offered by the lib, so the absence of this feature limits drop-in adoption

Yeah, pretty much that. We'll have to implement it prior to consideration for stdlib otherwise it doesn't have feature parity with Markdown which I consider a requirement.

Looking forward to seeing how this lib develops, feel free to tag me in any beginner level issues that may come up in this project

Not exactly a beginner level issue, but getting the interpolation syntax working is the only major outstanding thing. If you ever feel like looking into that one the best place to start is look at how the $ math syntax is implemented and adapting that:

#
# Dollar math
#
struct DollarMathRule end
function parse_block_dollar_math(p::Parser, node::Node)
if node.t isa Paragraph
left = match(r"^(\$+)", node.literal)
left === nothing && return nothing
right = match(r"(\$+)$", rstrip(node.literal))
right === nothing && return nothing
if length(left[1]) == length(right[1]) == 2
node.literal = strip(c -> isspace(c) || c === '$', node.literal)
node.t = DisplayMath()
end
end
return nothing
end
block_modifier(::DollarMathRule) = Rule(parse_block_dollar_math, 0)
const reDollarsHere = r"^\$+"
const reDollars = r"\$+"
function parse_inline_dollar_math(p::InlineParser, node::Node)
dollars = match(reDollarsHere, p)
if dollars === nothing || length(dollars.match) > 1
return false
end
consume(p, dollars)
after_opener, count = position(p), length(dollars.match)
while true
matched = consume(p, match(reDollars, p))
matched === nothing && break
if length(matched.match) === count
before_closer = position(p) - count - 1
raw = String(bytes(p, after_opener, before_closer))
child = Node(Math())
child.literal = strip(replace(raw, r"\s+" => ' '))
append_child(node, child)
return true
end
end
# We didn't match a balanced closing sequence.
seek(p, after_opener)
append_child(node, text(dollars.match))
return true
end
inline_rule(::DollarMathRule) = Rule(parse_inline_dollar_math, 0, "\$")

Originally posted by @MichaelHatherly in #14 (comment)

Clash between TableRule and IndentedCodeBlockRule

Example

using CommonMark
CM = CommonMark

cm_parser = CM.Parser()

enable!(cm_parser, CM.TableRule())
disable!(cm_parser, CM.IndentedCodeBlockRule())

s = """
    tables:

    | abc | def |
    | --- | ----|
    | 0 | 1|

    end
    """
cm_parser(s)

Stacktrace

ERROR: KeyError: key '\0' not found
Stacktrace:
 [1] getindex
   @ ./dict.jl:481 [inlined]
 [2] incorporate_line(parser::Parser, ln::String)
   @ CommonMark ~/.julia/packages/CommonMark/VdZT9/src/parsers/blocks.jl:322
 [3] parse(parser::Parser, my_input::IOBuffer; kws::Base.Pairs{Symbol, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
   @ CommonMark ~/.julia/packages/CommonMark/VdZT9/src/parsers/blocks.jl:440
 [4] parse
   @ ~/.julia/packages/CommonMark/VdZT9/src/parsers/blocks.jl:427 [inlined]
 [5] #_#21
   @ ~/.julia/packages/CommonMark/VdZT9/src/parsers/blocks.jl:451 [inlined]
 [6] Parser
   @ ~/.julia/packages/CommonMark/VdZT9/src/parsers/blocks.jl:451 [inlined]
 [7] #_#20
   @ ~/.julia/packages/CommonMark/VdZT9/src/parsers/blocks.jl:450 [inlined]
 [8] (::Parser)(text::String)
   @ CommonMark ~/.julia/packages/CommonMark/VdZT9/src/parsers/blocks.jl:450
 [9] top-level scope
   @ none:1

removing the disable! with IndentedCodeBlockRule avoids this but I do want to disable that rule (as per #1 (comment)). Any suggestions?

Thanks

TagBot trigger issue

This issue is used to trigger TagBot; feel free to unsubscribe.

If you haven't already, you should update your TagBot.yml to include issue comment triggers.
Please see this post on Discourse for instructions and more details.

If you'd like for me to do this for you, comment TagBot fix on this issue.
I'll open a PR within a few hours, please be patient!

Bug in `TableRule`

Here's the error output when I just enable TableRule and run the tests. I'll see if I can't find a simpler reproducer.

docs: Error During Test at /home/brandon/.julia/dev/JuliaFormatter/test/default_style.jl:1375
  Got exception outside of a @test
  UndefRefError: access to undefined reference
  Stacktrace:
   [1] getproperty at ./Base.jl:33 [inlined]
   [2] (::CommonMark.var"#49#51"{CommonMark.TableRule})(::CommonMark.InlineParser, ::CommonMark.Node) at /home/brandon/.julia/dev/CommonMark/src/extensions/tables.jl:111
   [3] parse_inlines(::CommonMark.InlineParser, ::CommonMark.Node) at /home/brandon/.julia/dev/CommonMark/src/parsers/inlines.jl:125
   [4] parse at /home/brandon/.julia/dev/CommonMark/src/parsers/inlines.jl:129 [inlined]
   [5] process_inlines(::CommonMark.Parser, ::CommonMark.Node) at /home/brandon/.julia/dev/CommonMark/src/parsers/blocks.jl:416
   [6] parse(::CommonMark.Parser, ::Base.GenericIOBuffer{Array{UInt8,1}}; kws::Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}) at /home/brandon/.julia/dev/CommonMark/src/parsers/blocks.jl:446
   [7] parse at /home/brandon/.julia/dev/CommonMark/src/parsers/blocks.jl:427 [inlined]
   [8] #_#21 at /home/brandon/.julia/dev/CommonMark/src/parsers/blocks.jl:451 [inlined]
   [9] Parser at /home/brandon/.julia/dev/CommonMark/src/parsers/blocks.jl:451 [inlined]
   [10] #_#20 at /home/brandon/.julia/dev/CommonMark/src/parsers/blocks.jl:450 [inlined]
   [11] Parser at /home/brandon/.julia/dev/CommonMark/src/parsers/blocks.jl:450 [inlined]
   [12] #p_literal#45 at /home/brandon/.julia/dev/JuliaFormatter/src/pretty.jl:344 [inlined]
   [13] p_macrocall(::DefaultStyle, ::CSTParser.EXPR, ::JuliaFormatter.State) at /home/brandon/.julia/dev/JuliaFormatter/src/pretty.jl:458
   [14] pretty(::DefaultStyle, ::CSTParser.EXPR, ::JuliaFormatter.State; kwargs::Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}) at /home/brandon/.julia/dev/JuliaFormatter/src/pretty.jl:71
   [15] pretty(::DefaultStyle, ::CSTParser.EXPR, ::JuliaFormatter.State) at /home/brandon/.julia/dev/JuliaFormatter/src/pretty.jl:3
   [16] p_fileh(::DefaultStyle, ::CSTParser.EXPR, ::JuliaFormatter.State) at /home/brandon/.julia/dev/JuliaFormatter/src/pretty.jl:149
   [17] pretty(::DefaultStyle, ::CSTParser.EXPR, ::JuliaFormatter.State; kwargs::Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}) at /home/brandon/.julia/dev/JuliaFormatter/src/pretty.jl:125
   [18] pretty at /home/brandon/.julia/dev/JuliaFormatter/src/pretty.jl:3 [inlined]
   [19] run_pretty(::String, ::Int64; opts::Options, style::DefaultStyle) at /home/brandon/.julia/dev/JuliaFormatter/test/runtests.jl:27
   [20] run_pretty(::String, ::Int64) at /home/brandon/.julia/dev/JuliaFormatter/test/runtests.jl:24
   [21] top-level scope at /home/brandon/.julia/dev/JuliaFormatter/test/default_style.jl:1383
   [22] top-level scope at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.4/Test/src/Test.jl:1113
   [23] top-level scope at /home/brandon/.julia/dev/JuliaFormatter/test/default_style.jl:1376
   [24] top-level scope at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.4/Test/src/Test.jl:1113
   [25] top-level scope at /home/brandon/.julia/dev/JuliaFormatter/test/default_style.jl:2
   [26] include(::String) at ./client.jl:439
   [27] top-level scope at /home/brandon/.julia/dev/JuliaFormatter/test/runtests.jl:43
   [28] include(::String) at ./client.jl:439
   [29] top-level scope at none:6
   [30] eval(::Module, ::Any) at ./boot.jl:331
   [31] exec_options(::Base.JLOptions) at ./client.jl:264
   [32] _start() at ./client.jl:484
  
strings: Error During Test at /home/brandon/.julia/dev/JuliaFormatter/test/default_style.jl:1519
  Test threw exception
  Expression: fmt(str) == str
  UndefRefError: access to undefined reference
  Stacktrace:
   [1] getproperty at ./Base.jl:33 [inlined]
   [2] (::CommonMark.var"#49#51"{CommonMark.TableRule})(::CommonMark.InlineParser, ::CommonMark.Node) at /home/brandon/.julia/dev/CommonMark/src/extensions/tables.jl:111
   [3] parse_inlines(::CommonMark.InlineParser, ::CommonMark.Node) at /home/brandon/.julia/dev/CommonMark/src/parsers/inlines.jl:125
   [4] parse at /home/brandon/.julia/dev/CommonMark/src/parsers/inlines.jl:129 [inlined]
   [5] process_inlines(::CommonMark.Parser, ::CommonMark.Node) at /home/brandon/.julia/dev/CommonMark/src/parsers/blocks.jl:416
   [6] parse(::CommonMark.Parser, ::Base.GenericIOBuffer{Array{UInt8,1}}; kws::Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}) at /home/brandon/.julia/dev/CommonMark/src/parsers/blocks.jl:446
   [7] parse at /home/brandon/.julia/dev/CommonMark/src/parsers/blocks.jl:427 [inlined]
   [8] #_#21 at /home/brandon/.julia/dev/CommonMark/src/parsers/blocks.jl:451 [inlined]
   [9] Parser at /home/brandon/.julia/dev/CommonMark/src/parsers/blocks.jl:451 [inlined]
   [10] #_#20 at /home/brandon/.julia/dev/CommonMark/src/parsers/blocks.jl:450 [inlined]
   [11] Parser at /home/brandon/.julia/dev/CommonMark/src/parsers/blocks.jl:450 [inlined]
   [12] #p_literal#45 at /home/brandon/.julia/dev/JuliaFormatter/src/pretty.jl:344 [inlined]
   [13] p_macrocall(::DefaultStyle, ::CSTParser.EXPR, ::JuliaFormatter.State) at /home/brandon/.julia/dev/JuliaFormatter/src/pretty.jl:458
   [14] pretty(::DefaultStyle, ::CSTParser.EXPR, ::JuliaFormatter.State; kwargs::Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}) at /home/brandon/.julia/dev/JuliaFormatter/src/pretty.jl:71
   [15] pretty(::DefaultStyle, ::CSTParser.EXPR, ::JuliaFormatter.State) at /home/brandon/.julia/dev/JuliaFormatter/src/pretty.jl:3
   [16] p_fileh(::DefaultStyle, ::CSTParser.EXPR, ::JuliaFormatter.State) at /home/brandon/.julia/dev/JuliaFormatter/src/pretty.jl:149
   [17] pretty(::DefaultStyle, ::CSTParser.EXPR, ::JuliaFormatter.State; kwargs::Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}) at /home/brandon/.julia/dev/JuliaFormatter/src/pretty.jl:125
   [18] pretty(::DefaultStyle, ::CSTParser.EXPR, ::JuliaFormatter.State) at /home/brandon/.julia/dev/JuliaFormatter/src/pretty.jl:3
   [19] _format_text(::DefaultStyle, ::JuliaFormatter.State, ::CSTParser.EXPR) at /home/brandon/.julia/dev/JuliaFormatter/src/JuliaFormatter.jl:270
   [20] format_text(::String; indent::Int64, margin::Int64, style::DefaultStyle, always_for_in::Bool, whitespace_typedefs::Bool, whitespace_ops_in_indices::Bool, remove_extra_newlines::Bool, import_to_using::Bool, pipe_to_function_call::Bool, short_to_long_function_def::Bool, always_use_return::Bool, whitespace_in_kwargs::Bool, annotate_untyped_fields_with_any::Bool) at /home/brandon/.julia/dev/JuliaFormatter/src/JuliaFormatter.jl:258
   [21] #fmt1#3 at /home/brandon/.julia/dev/JuliaFormatter/test/runtests.jl:6 [inlined]
   [22] #fmt#5 at /home/brandon/.julia/dev/JuliaFormatter/test/runtests.jl:13 [inlined]
   [23] fmt(::String) at /home/brandon/.julia/dev/JuliaFormatter/test/runtests.jl:13
   [24] top-level scope at /home/brandon/.julia/dev/JuliaFormatter/test/default_style.jl:1519
   [25] top-level scope at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.4/Test/src/Test.jl:1113
   [26] top-level scope at /home/brandon/.julia/dev/JuliaFormatter/test/default_style.jl:1514
   [27] top-level scope at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.4/Test/src/Test.jl:1113
   [28] top-level scope at /home/brandon/.julia/dev/JuliaFormatter/test/default_style.jl:2
  
strings: Error During Test at /home/brandon/.julia/dev/JuliaFormatter/test/default_style.jl:1529
  Test threw exception
  Expression: fmt(str) == str
  UndefRefError: access to undefined reference
  Stacktrace:
   [1] getproperty at ./Base.jl:33 [inlined]
   [2] (::CommonMark.var"#49#51"{CommonMark.TableRule})(::CommonMark.InlineParser, ::CommonMark.Node) at /home/brandon/.julia/dev/CommonMark/src/extensions/tables.jl:111
   [3] parse_inlines(::CommonMark.InlineParser, ::CommonMark.Node) at /home/brandon/.julia/dev/CommonMark/src/parsers/inlines.jl:125
   [4] parse at /home/brandon/.julia/dev/CommonMark/src/parsers/inlines.jl:129 [inlined]
   [5] process_inlines(::CommonMark.Parser, ::CommonMark.Node) at /home/brandon/.julia/dev/CommonMark/src/parsers/blocks.jl:416
   [6] parse(::CommonMark.Parser, ::Base.GenericIOBuffer{Array{UInt8,1}}; kws::Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}) at /home/brandon/.julia/dev/CommonMark/src/parsers/blocks.jl:446
   [7] parse at /home/brandon/.julia/dev/CommonMark/src/parsers/blocks.jl:427 [inlined]
   [8] #_#21 at /home/brandon/.julia/dev/CommonMark/src/parsers/blocks.jl:451 [inlined]
   [9] Parser at /home/brandon/.julia/dev/CommonMark/src/parsers/blocks.jl:451 [inlined]
   [10] #_#20 at /home/brandon/.julia/dev/CommonMark/src/parsers/blocks.jl:450 [inlined]
   [11] Parser at /home/brandon/.julia/dev/CommonMark/src/parsers/blocks.jl:450 [inlined]
   [12] #p_literal#45 at /home/brandon/.julia/dev/JuliaFormatter/src/pretty.jl:344 [inlined]
   [13] p_macrocall(::DefaultStyle, ::CSTParser.EXPR, ::JuliaFormatter.State) at /home/brandon/.julia/dev/JuliaFormatter/src/pretty.jl:458
   [14] pretty(::DefaultStyle, ::CSTParser.EXPR, ::JuliaFormatter.State; kwargs::Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}) at /home/brandon/.julia/dev/JuliaFormatter/src/pretty.jl:71
   [15] pretty(::DefaultStyle, ::CSTParser.EXPR, ::JuliaFormatter.State) at /home/brandon/.julia/dev/JuliaFormatter/src/pretty.jl:3
   [16] p_fileh(::DefaultStyle, ::CSTParser.EXPR, ::JuliaFormatter.State) at /home/brandon/.julia/dev/JuliaFormatter/src/pretty.jl:149
   [17] pretty(::DefaultStyle, ::CSTParser.EXPR, ::JuliaFormatter.State; kwargs::Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}) at /home/brandon/.julia/dev/JuliaFormatter/src/pretty.jl:125
   [18] pretty(::DefaultStyle, ::CSTParser.EXPR, ::JuliaFormatter.State) at /home/brandon/.julia/dev/JuliaFormatter/src/pretty.jl:3
   [19] _format_text(::DefaultStyle, ::JuliaFormatter.State, ::CSTParser.EXPR) at /home/brandon/.julia/dev/JuliaFormatter/src/JuliaFormatter.jl:270
   [20] format_text(::String; indent::Int64, margin::Int64, style::DefaultStyle, always_for_in::Bool, whitespace_typedefs::Bool, whitespace_ops_in_indices::Bool, remove_extra_newlines::Bool, import_to_using::Bool, pipe_to_function_call::Bool, short_to_long_function_def::Bool, always_use_return::Bool, whitespace_in_kwargs::Bool, annotate_untyped_fields_with_any::Bool) at /home/brandon/.julia/dev/JuliaFormatter/src/JuliaFormatter.jl:258
   [21] #fmt1#3 at /home/brandon/.julia/dev/JuliaFormatter/test/runtests.jl:6 [inlined]
   [22] #fmt#5 at /home/brandon/.julia/dev/JuliaFormatter/test/runtests.jl:13 [inlined]
   [23] fmt(::String) at /home/brandon/.julia/dev/JuliaFormatter/test/runtests.jl:13
   [24] top-level scope at /home/brandon/.julia/dev/JuliaFormatter/test/default_style.jl:1529
   [25] top-level scope at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.4/Test/src/Test.jl:1113
   [26] top-level scope at /home/brandon/.julia/dev/JuliaFormatter/test/default_style.jl:1514
   [27] top-level scope at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.4/Test/src/Test.jl:1113
   [28] top-level scope at /home/brandon/.julia/dev/JuliaFormatter/test/default_style.jl:2
  
Format docstrings: Error During Test at /home/brandon/.julia/dev/JuliaFormatter/test/default_style.jl:4882
  Test threw exception
  Expression: fmt(unformatted) == formatted
  UndefRefError: access to undefined reference
  Stacktrace:
   [1] getproperty at ./Base.jl:33 [inlined]
   [2] (::CommonMark.var"#49#51"{CommonMark.TableRule})(::CommonMark.InlineParser, ::CommonMark.Node) at /home/brandon/.julia/dev/CommonMark/src/extensions/tables.jl:111
   [3] parse_inlines(::CommonMark.InlineParser, ::CommonMark.Node) at /home/brandon/.julia/dev/CommonMark/src/parsers/inlines.jl:125
   [4] parse at /home/brandon/.julia/dev/CommonMark/src/parsers/inlines.jl:129 [inlined]
   [5] process_inlines(::CommonMark.Parser, ::CommonMark.Node) at /home/brandon/.julia/dev/CommonMark/src/parsers/blocks.jl:416
   [6] parse(::CommonMark.Parser, ::Base.GenericIOBuffer{Array{UInt8,1}}; kws::Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}) at /home/brandon/.julia/dev/CommonMark/src/parsers/blocks.jl:446
   [7] parse at /home/brandon/.julia/dev/CommonMark/src/parsers/blocks.jl:427 [inlined]
   [8] #_#21 at /home/brandon/.julia/dev/CommonMark/src/parsers/blocks.jl:451 [inlined]
   [9] Parser at /home/brandon/.julia/dev/CommonMark/src/parsers/blocks.jl:451 [inlined]
   [10] #_#20 at /home/brandon/.julia/dev/CommonMark/src/parsers/blocks.jl:450 [inlined]
   [11] Parser at /home/brandon/.julia/dev/CommonMark/src/parsers/blocks.jl:450 [inlined]
   [12] #p_literal#45 at /home/brandon/.julia/dev/JuliaFormatter/src/pretty.jl:344 [inlined]
   [13] p_macrocall(::DefaultStyle, ::CSTParser.EXPR, ::JuliaFormatter.State) at /home/brandon/.julia/dev/JuliaFormatter/src/pretty.jl:458
   [14] pretty(::DefaultStyle, ::CSTParser.EXPR, ::JuliaFormatter.State; kwargs::Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}) at /home/brandon/.julia/dev/JuliaFormatter/src/pretty.jl:71
   [15] pretty(::DefaultStyle, ::CSTParser.EXPR, ::JuliaFormatter.State) at /home/brandon/.julia/dev/JuliaFormatter/src/pretty.jl:3
   [16] p_fileh(::DefaultStyle, ::CSTParser.EXPR, ::JuliaFormatter.State) at /home/brandon/.julia/dev/JuliaFormatter/src/pretty.jl:149
   [17] pretty(::DefaultStyle, ::CSTParser.EXPR, ::JuliaFormatter.State; kwargs::Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}) at /home/brandon/.julia/dev/JuliaFormatter/src/pretty.jl:125
   [18] pretty(::DefaultStyle, ::CSTParser.EXPR, ::JuliaFormatter.State) at /home/brandon/.julia/dev/JuliaFormatter/src/pretty.jl:3
   [19] _format_text(::DefaultStyle, ::JuliaFormatter.State, ::CSTParser.EXPR) at /home/brandon/.julia/dev/JuliaFormatter/src/JuliaFormatter.jl:270
   [20] format_text(::String; indent::Int64, margin::Int64, style::DefaultStyle, always_for_in::Bool, whitespace_typedefs::Bool, whitespace_ops_in_indices::Bool, remove_extra_newlines::Bool, import_to_using::Bool, pipe_to_function_call::Bool, short_to_long_function_def::Bool, always_use_return::Bool, whitespace_in_kwargs::Bool, annotate_untyped_fields_with_any::Bool) at /home/brandon/.julia/dev/JuliaFormatter/src/JuliaFormatter.jl:258
   [21] #fmt1#3 at /home/brandon/.julia/dev/JuliaFormatter/test/runtests.jl:6 [inlined]
   [22] #fmt#5 at /home/brandon/.julia/dev/JuliaFormatter/test/runtests.jl:13 [inlined]
   [23] fmt(::String) at /home/brandon/.julia/dev/JuliaFormatter/test/runtests.jl:13
   [24] top-level scope at /home/brandon/.julia/dev/JuliaFormatter/test/default_style.jl:4882
   [25] top-level scope at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.4/Test/src/Test.jl:1113
   [26] top-level scope at /home/brandon/.julia/dev/JuliaFormatter/test/default_style.jl:4807
   [27] top-level scope at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.4/Test/src/Test.jl:1113
   [28] top-level scope at /home/brandon/.julia/dev/JuliaFormatter/test/default_style.jl:2

Julia 1.0: no method matching get!(::getfield(CommonMark, Symbol("##106#108")), ::IdDict{CommonMark.Node,Dict{String,Int64}}, ::CommonMark.Node)

A wrote a sanity check for MarkdownLiteral.jl and it fails on Julia 1.0.

https://github.com/JuliaPluto/MarkdownLiteral.jl/runs/4565349515?check_suite_focus=true#step:4:55

You can run these tests yourself with:

julia> ]
pkg> add https://github.com/JuliaPluto/MarkdownLiteral.jl
pkg> test MarkdownLiteral

It looks like the problem is that Julia 1.0 did not have this method yet:

julia> get!(rand, IdDict(), 4)
ERROR: MethodError: no method matching get!(::typeof(rand), ::IdDict{Any,Any}, ::Int64)
Closest candidates are:
  get!(::IdDict{K,V}, ::Any, ::Any) where {K, V} at abstractdict.jl:649
  get!(::Dict{K,V}, ::Any, ::Any) where {K, V} at dict.jl:421
  get!(::Union{Function, Type}, ::Dict{K,V}, ::K) where {K, V} at dict.jl:448
  ...
Stacktrace:
 [1] top-level scope at none:0

Markdown Macro for CommonMark

Thanks for this wonderful package...I'd been struggling with the stdlib markdown library for over a week until I found this today and it felt like suddenly the issues I couldn't find a way past in terms of incomplete tests and inconsistent parsing were all resolved at once.

One lovely feature of the existing markdown lib, however, is the md"..." string macro which prevents clunky syntax in notebooks like parse(raw"``\epsilon + 1``"). Do you know whether your library could easily accommodate its own string macro, something like md2"..." or even something that overrides the md macro when CommonMark is loaded into the main environment?

`benchmark/`: simplify

re the benchmark/ dir I introduced in

Just jotting down some ideas to make that better.
Mostly: simplify it.

  • Only one project (Literate and CommonMark can be in the same project, it's okay)
  • Only one benchmarking script (it can test everything: both startup latency (TTFParse etc), and full compiled speed)
    • (Right now it's setup for 'possible future' other benchmarking scripts, but let's be realistic)
  • Only one .md file? It's nice that github auto-opens the ReadMe when opening the benchmarks folder on GitHub. So maybe that could be the output file generated with Literate.jl from the benchmarking script.
    Or two markdown files (ReadMe.md, benchmark.md), that's fine too

Misc:

  • Print now() (yes you can look at commit date, but that's a bit hidden and could be != run date)

Also, maybe:

  • add it to CI, to track performance regressions.
    • You can then flip through the 'History' of the benchmark markdown file on github, to see how timings change
      • Not ideal. Also not automated

Migrate to PrecompileTools

Would be great if this package could be migrated from SnoopPrecompile to PrecompileTools. For various reasons we can't ship packages that use SnoopPrecompile in the VS Code extension, but everything works fine with PrecompileTools. JuliaFormatter uses CommonMark, and so at the moment we are blocked from shipping the latest formatting code in the extension until CommonMark is also migrated to PrecompileTools. domluna/JuliaFormatter.jl#710 has an example what needs to be done, I think it is fairly easy.

dot/period creates another node with Typography rule

Thank you for this excellent package. I was trying to parse some markdown and look at the parser tree. I was wondering if the Typography rule is supposed to make dot be a separate node?

using CommonMark

parser = Parser()
enable!(parser, TypographyRule())

parser("# Heading. \n This is a Heading.") |> CommonMark.ast_dump
Document
  Heading
    Text
      "Heading"
    Text
      "."
  Paragraph
    Text
      "This is a Heading"
    Text
      "."

I could not figure out the exact change needed however when checking for ellipsis I think you are checking for one or more dots.
Maybe only append_child when it matches 3 dots in the function.

function parse_ellipsis(parser::InlineParser, block::Node)
    m = consume(parser, match(r"^\.+", parser))
    append_child(block, text(length(m.match) === 3 ? "\u2026" : m.match))
    return true
end

Thank You!

[Bug?] CommonMark Markdown Table cells are not CommonMark leaf blocks

better example

it turned out the actual confusion is better grasped by different examples
see #66 (comment)

original example

given the following text

html_str = "<bond def=\"mu\" unique_id=\"zOnak3/Y4WZl\"><input type='range' min='1' max='5' value='3'/><script>const input_el = currentScript.previousElementSibling; const output_el = currentScript.nextElementSibling; const displays = [\"-3\", \"-1\", \"0\", \"1\", \"3\"]; input_el.addEventListener(\"input\", () => {output_el.value = displays[input_el.valueAsNumber - 1]})</script><output style=' font-family: system-ui; font-size: 15px; margin-left: 3px; transform: translateY(-4px); display: inline-block;'>0</output></bond>"

it works perfectly in HTML, but CommonMark exchanges symbols such that the script does no longer work

"<p><bond def=\"mu\" unique_id=\"zOnak3/Y4WZl\"><input type='range' min='1' max='5' value='3'/><script>const input_el = currentScript.previousElementSibling; const output_el = currentScript.nextElementSibling; const displays = [&quot;-3&quot;, &quot;-1&quot;, &quot;0&quot;, &quot;1&quot;, &quot;3&quot;]; input_el.addEventListener(&quot;input&quot;, () =&gt; {output_el.value = displays[input_el.valueAsNumber - 1]});</script><output style=' font-family: system-ui; font-size: 15px; margin-left: 3px; transform: translateY(-4px); display: inline-block;'>0</output></bond></p> "

EDIT: the above does not parse because the html tag <bond> is non-standard. It is confusing example. Better look at #66 (comment)

I am using the following parser

import CommonMark
parser = CommonMark.Parser()
CommonMark.enable!(parser, CommonMark.DollarMathRule())
CommonMark.enable!(parser, CommonMark.TableRule())
parser(html_str)

Motivation

I am using CommonMark to create Pluto interface for Python. There the most intuitive way for interpolation is to interpolate valid html text into the plain markdown string. Hence it would be really really great, if <script> tags could be supported (they appear already in PlutoUI.Slider for instance)

Implementing a table of contents feature for CommonMark

Hi, I have been trying to implement a toc (table of contents) feature using the ast generated from Commonmark. However, my implementation is too convoluted and messy. It would really help if I can get any feedback on better ways to create the toc and if I could contribute this in any way to this excellent package.

The logic of my implementation is to create a hierarchical JSON data structure from ast created by CommonMark. I wanted to have it in JSON, as we can use the JSON data in javascript to create different types of toc's at multiple levels on a webpage. If it is just HTML ul,li list then there may be other better approaches.

This is the function I wrote to create a hierarchical dict:

# input for this function is an ast for a markdown document created with a commonmark parser
function create_toc(ast)
    toc = [] # initialize toc

    ## iterate through toc and extract all headings, links and level  of the headings
    for (node, enter) in ast
        if enter && node.t isa CommonMark.Heading
            hstr = string(node.t)
            hlvl = match(r"CommonMark.Heading\((.)\)", hstr)

            title_str = ""
            for (node, enter) in node
                if enter && node.t isa CommonMark.Text
                    title_str *= node.literal
                end
            end
            push!(toc, 
        Dict{Any,Any}("hlvl" => hlvl[1],
              "id" => node.meta["id"],
              "title" => title_str,
              "children" => nothing )) # create children field as it is needed
        end
    end

    # create another copy of the toc and modify it to create a hierarchical dict 
    ntoc = deepcopy(toc)

    ## this function finds the parent of a heading if any otherwise 0
    function findparent(tlist, nid)
        x = [i["hlvl"] for i in tlist]
        x = parse.(Int, x)
        a1 = x[nid]
        if a1 == 1 
            return 0
        else
            return findlast(x[1:nid] .== (a1 - 1))
        end
    end
    
    ## find parents for each heading
    pnode = [findparent(toc, i) for i in 1:length(toc)]

    for i in 1:length(toc)
        node = toc[i]
        push!(node, "idno" => i)
        push!(node, "parent" => pnode[i])
    end
    
    # attach children to their parents in reverse creating a hierarchical structure
    for node in reverse(toc)
        if node["parent"] != 0
            if ntoc[node["parent"]]["children"] === nothing
                ntoc[node["parent"]]["children"] = [ntoc[node["idno"]]] 
            else
                ntoc[node["parent"]]["children"] = [ntoc[node["idno"]], ntoc[node["parent"]]["children"]]
            end
        end
    end
    return ntoc[pnode.==0]
end

Example usage

using CommonMark
using JSON3
using YAML
parser = CommonMark.Parser()
CommonMark.enable!(parser, CommonMark.DollarMathRule())
CommonMark.enable!(parser, CommonMark.RawContentRule())
CommonMark.enable!(parser, CommonMark.TypographyRule())
CommonMark.enable!(parser, CommonMark.FrontMatterRule(yaml=YAML.load))
CommonMark.enable!(parser, CommonMark.AttributeRule())
CommonMark.enable!(parser, CommonMark.AutoIdentifierRule())
CommonMark.enable!(parser, CommonMark.AdmonitionRule())
CommonMark.enable!(parser, CommonMark.FootnoteRule())
CommonMark.enable!(parser, CommonMark.TableRule())
CommonMark.enable!(parser, CommonMark.MathRule())
CommonMark.enable!(parser, CommonMark.CitationRule())

ast = parser("---\r\ntitle: \"test toc\"\r\n---\r\n# Hello *world*\r\n\r\nThis is first heading.\r\n\r\n# Second heading\r\nthis is second heading\r\n\r\n## Level2 heading\r\n\r\nthis is level 2 heading\r\n\r\n### Level3 heading\r\n\r\nthis is level 
3 heading\r\n\r\n## Level2 heading\r\n\r\n\r\nduplicate level 2 heading\r\n\r\n# Third heading\r\nthis is third heading\r\n\r\n\r\n\r\n## Level2 heading\r\n\r\n\r\nduplicate level 2 heading\r\n\r\n## Level3 heading\r\n\r\nduplicate level3 heading")

toc = create_toc(ast)

JSON3.write(toc)

Any feedback on implementation or other alternatives is appreciated. Thank You!

Markup -> Plaintext

Hello Mike,

In the context of dealing with RSS (not the most fun thing to figure out), it'd be useful to have a way to go from "markdown" to "plaintext". The reason is that in RSS the <description> field, if given along with a <content:encoded> one, should be plain text (no markup). You can write <[CDATA... stuff but in theory you shouldn't, the encoding should go in the content encoded.

As a result the useful thing to have is a way for people to write:

+++
abstract = "This is a description of the blog post with **markup** and [links](https://juliacon.org)"
+++

{{insert abstract}}

... Rest of the blog post ...

and have the {{insert abstract}} do two things:

  • insert the markup-ed text in the blog post as standard, where it would eventually be converted to HTML (standard path MD -> X)
  • add an RSS item entry where the <description> gets plaintext(abstract), in the above case:
This is a description of the blog post with markup and links.

so that would use a "reverse" path: MD -> text. Is that doable easily with common mark?

Thanks!

Content inside `<script>` inside paragraph

I am having some trouble using <script> tags inside CommonMark, because the script contents are changed.

I have the following commonmark input:

asdf

asdf <script>
console.log("123")
console.log(1 *2* 3)
</script>

When parsed with the default CommonMark parser and rendered to text/html, we get:

<p>asdf</p>
<p>asdf <script>
console.log(&quot;123&quot;)
console.log(1 <em>2</em> 3)
</script></p>

(Also, with the TypographyRule, the directed quotes don't work in JS):

<p>asdf</p>
<p>asdf <script>
console.log(“123”)
console.log(1 <em>2</em> 3)
</script></p>

Would it be possible to special-case <script> and leave the contents as-is?

Test code ```julia # ╔═╡ fc0d1ac3-073f-444c-8120-ac4259735d48 repr(MIME"text/html"(), cm_parser(""" asdf

asdf <script>
console.log("123")
console.log(1 2 3)
</script>
""")) |> Text

╔═╡ aeafb8c2-083c-47c9-a86e-2d1c2dff445e

begin
cm_parser = CommonMark.Parser()
CommonMark.enable!(cm_parser, [
# CommonMark.AdmonitionRule(),
# CommonMark.AttributeRule(),
# CommonMark.AutoIdentifierRule(),
# CommonMark.CitationRule(),
# CommonMark.FootnoteRule(),
# CommonMark.MathRule(),
# CommonMark.RawContentRule(),
# CommonMark.TableRule(),
# CommonMark.TypographyRule(),
])
end



</details>

Disabling LinkRule (but not ImageRule) does not disable link parsing

I believe this is because the inline_rules for [ and ] that go into p.inline_parser.inline_parsers are identical for LinkRule and ImageRule (except that ImageRule also triggers on !):

struct LinkRule end
inline_rule(::LinkRule) = (Rule(parse_open_bracket, 1, "["), Rule(parse_close_bracket, 1, "]"))
struct ImageRule end
inline_rule(::ImageRule) = (Rule(parse_bang, 1, "!"), inline_rule(LinkRule())...)

My understanding of the parser is that parse_open_bracket will trigger anyway, because once it has been added to p.inline_parser.inline_parsers, it doesn't know whether it was added by LinkRule or ImageRule.

For an MWE, setting up the parser as follows to remove LinkRule (and it's associated inline_rules):

using CommonMark
p = Parser()
rules = filter(r -> !isa(r, LinkRule), p.rules)
disable!(p, copy(p.rules))
enable!(p, rules)
n = p("![a](b)\n\n[x](y)")

The resulting AST still has the Link nodes:

julia> n.first_child.first_child.t
CommonMark.Image("b", "")

julia> n.first_child.nxt.first_child.t
CommonMark.Link("y", "")

Some detailed code issues

The code advance_offset(parser, length(parser.buf) - parser.pos + 1, false) appeared several times. I guess it means to advance to end, but parser.pos does not seem to be the current char number. And this calculation is not needed, so I guess a advance_offset_to_end(parser::Parser, columns::Bool) = advance_offset(parser, typemax(Int), columns) is needed.

Also note that you defined many parser.pos related functions, but they are not always used.

Interpolation Error (?)

using CommonMark

cat = "test!"

# Works:
cm"""<b>Well done, your cat is called $(cat) now.</b>"""
cm"""<b>Well done, your cat is called $cat now.</b>"""

# Doesn't work:
cm"""<p><b>Well done, your cat is called $(cat) now.</b></p>"""
cm"""<p><b>Well done, your cat is called $cat now.</b></p>"""

Does not interpolate variable $cat.

Seems related to the nested xml...

Newlines disappearing from code blocks

Apologies if this is an option or intended. Newlines seem to be disappearing from code-blocks:

using CommonMark

markdown(stdout, Parser()("""
Docstring

\```jldoctest
julia> a = 1
1

julia> b = 2
2
\```
"""))

[question] Strict alignment of pipes in tables

In the docs you specify that the pipe symbols must be strictly aligned; is this a common mark requirement? would there be any ambiguity if that were relaxed // would it be possible to add a custom rule where this is relaxed?

We use it over in PkgPage and I've had feedback from people that the tables didn't look good which usually was because their | were not aligned (I think this is not required in GFM for instance)

Thanks!

Edit: maybe to substantiate why this rule seems unnecessarily constraining, consider this case:

| Column One | Column Two | Column Three |
|:---------- | ---------- |:------------:|
| Row `1`    | Column `2` |              |
| *Row* 2    | **Row** 2  | A very long cell with really a lot of text which might span multiple lines if your editor is constrained to 80 columns etc etc, it would be really dumb to have to require the top pipe to match this length, it would make the whole table less readable |

That stuff would work fine in GitHub:

Column One Column Two Column Three
Row 1 Column 2
Row 2 Row 2 A very long cell with really a lot of text which might span multiple lines if your editor is constrained to 80 columns etc etc, it would be really dumb to have to require the top pipe to match this length, it would make the whole table less readable and you would have to adjust all pipes whenever you're adding an entry that is longer than previous ones

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.