├── .gitignore
├── test
├── test_helper.cr
└── common_mark_test.cr
├── src
├── main.cr
├── lib_cmark.cr
├── common_mark.cr
└── lib_cmark.cr.in
├── shard.lock
├── shard.yml
├── Makefile
├── ext
└── Makefile
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .crystal
2 | .shards
3 | /lib
4 | /ext
5 | /docs
6 |
--------------------------------------------------------------------------------
/test/test_helper.cr:
--------------------------------------------------------------------------------
1 | require "minitest/autorun"
2 | require "../src/common_mark"
3 |
--------------------------------------------------------------------------------
/src/main.cr:
--------------------------------------------------------------------------------
1 | require "./common_mark"
2 |
3 | md = CommonMark.new(File.read(ARGV[0]))
4 |
5 | puts case ARGV[1]?
6 | when "xml" then md.to_xml
7 | when "man" then md.to_man
8 | when "commonmark" then md.to_commonmark
9 | when "latex" then md.to_latex
10 | else md.to_html
11 | end
12 |
--------------------------------------------------------------------------------
/shard.lock:
--------------------------------------------------------------------------------
1 | version: 1.0
2 | shards:
3 | clang:
4 | github: crystal-lang/clang.cr
5 | commit: d019b9ff105cd652f31cca73e950adbf41037d52
6 |
7 | crystal_lib:
8 | github: manastech/crystal_lib
9 | commit: 85421997dd176e289d76a369b796fa96c42a3018
10 |
11 | minitest:
12 | github: ysbaddaden/minitest.cr
13 | version: 0.5.0
14 |
15 |
--------------------------------------------------------------------------------
/shard.yml:
--------------------------------------------------------------------------------
1 | name: common_mark
2 | version: 0.3.0
3 | description: libcmark bindings, the reference CommonMark C library
4 |
5 | development_dependencies:
6 | crystal_lib:
7 | github: manastech/crystal_lib
8 |
9 | minitest:
10 | github: ysbaddaden/minitest.cr
11 | version: ">= 0.5.0"
12 |
13 | scripts:
14 | postinstall: cd ext && make
15 |
16 | crystal: 0.20.0
17 |
18 | license: BSD-2-Clause
19 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: docs clean test
2 |
3 | all: ext/libcmark.a src/lib_cmark.cr
4 |
5 | ext/libcmark.a:
6 | cd ext && make
7 |
8 | src/lib_cmark.cr: src/lib_cmark.cr.in ext/*.h
9 | crystal ./lib/crystal_lib/src/main.cr -- src/lib_cmark.cr.in > src/lib_cmark.cr
10 | sed -i 's/.\/..\/ext/#{__DIR__}\/..\/ext/' src/lib_cmark.cr
11 |
12 | docs:
13 | crystal docs src/common_mark.cr
14 |
15 | clean:
16 | cd ext && make clean
17 |
18 | test:
19 | crystal test/*_test.cr
20 |
--------------------------------------------------------------------------------
/ext/Makefile:
--------------------------------------------------------------------------------
1 | VERSION ?= 0.30.2
2 | CMARK = cmark-$(VERSION)
3 | SOURCES = $(wildcard $(CMARK)/src/*.c) $(wildcard $(CMARK)/src/*.h)
4 |
5 | all: libcmark.a
6 |
7 | libcmark.a: cmark-$(VERSION) $(SOURCES)
8 | cd $(CMARK) && INSTALL_PREFIX=.. make
9 | cp $(CMARK)/src/*.h .
10 | cp $(CMARK)/build/src/*.h .
11 | cp $(CMARK)/build/src/*.a .
12 |
13 | $(CMARK):
14 | curl -L https://github.com/jgm/cmark/archive/$(VERSION).tar.gz | tar zx
15 |
16 | clean:
17 | rm -rf cmark-*
18 |
19 | distclean: clean
20 | rm -rf *.a *.h
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CommonMark
2 |
3 | Crystal wrapper for libcmark, the reference CommonMark C library.
4 |
5 | ## Install
6 |
7 | Add the dependency to `shard.yml` then run `shards install`:
8 |
9 | ```yaml
10 | dependencies:
11 | common_mark:
12 | github: ysbaddaden/crystal-cmark
13 | ```
14 |
15 | This will automatically download and compile libcmark.
16 |
17 | ## Usage
18 |
19 | ```crystal
20 | require "common_mark"
21 | html = CommonMark.new(text).to_html
22 | ```
23 |
24 | ## License
25 |
26 | Distributed under the [BSD 2 Clause](http://opensource.org/licenses/BSD-2-Clause) license.
27 |
--------------------------------------------------------------------------------
/src/lib_cmark.cr:
--------------------------------------------------------------------------------
1 | @[Link(ldflags: "-L#{__DIR__}/../ext -lcmark")]
2 | lib LibCmark
3 | type CmarkNode = Void*
4 | fun node_free = cmark_node_free(node : CmarkNode)
5 | fun parse_document = cmark_parse_document(buffer : LibC::Char*, len : LibC::SizeT, options : LibC::Int) : CmarkNode
6 | fun render_xml = cmark_render_xml(root : CmarkNode, options : LibC::Int) : LibC::Char*
7 | fun render_html = cmark_render_html(root : CmarkNode, options : LibC::Int) : LibC::Char*
8 | fun render_man = cmark_render_man(root : CmarkNode, options : LibC::Int, width : LibC::Int) : LibC::Char*
9 | fun render_commonmark = cmark_render_commonmark(root : CmarkNode, options : LibC::Int, width : LibC::Int) : LibC::Char*
10 | fun render_latex = cmark_render_latex(root : CmarkNode, options : LibC::Int, width : LibC::Int) : LibC::Char*
11 | OPT_DEFAULT = 0
12 | OPT_SOURCEPOS = (1 << 1)
13 | OPT_HARDBREAKS = (1 << 2)
14 | OPT_NORMALIZE = (1 << 8)
15 | OPT_SMART = (1 << 10)
16 | OPT_VALIDATE_UTF8 = (1 << 9)
17 | OPT_UNSAFE = (1 << 17)
18 | VERSION = (((0 << 16) | (29 << 8)) | 0)
19 | VERSION_STRING = "0.29.0"
20 | end
21 |
22 |
--------------------------------------------------------------------------------
/test/common_mark_test.cr:
--------------------------------------------------------------------------------
1 | require "./test_helper"
2 |
3 | class CommonMarkTest < Minitest::Test
4 | TEXT = <<-MARKDOWN
5 | # title
6 |
7 | some
8 | body
9 |
10 | * item 1
11 | * item 2
12 | MARKDOWN
13 |
14 | def test_options
15 | md = CommonMark.new("")
16 | refute md.sourcepos?
17 | refute md.hardbreaks?
18 | refute md.normalize?
19 | refute md.smart?
20 | refute md.unsafe?
21 |
22 | md = CommonMark.new("", sourcepos: true, hardbreaks: true, normalize: true,
23 | smart: true, unsafe: true, validate_utf8: true)
24 | assert md.sourcepos?
25 | assert md.hardbreaks?
26 | assert md.normalize?
27 | assert md.smart?
28 | assert md.unsafe?
29 | assert md.validate_utf8?
30 | end
31 |
32 | def test_to_commonmark
33 | commonmark = CommonMark.new(TEXT).to_commonmark
34 | assert_equal "# title\n\nsome body\n\n - item 1\n - item 2\n", commonmark
35 | end
36 |
37 | def test_to_html
38 | html = CommonMark.new(TEXT).to_html
39 | assert_match "
title
", html
40 | assert_match "some\nbody
", html
41 | assert_match "item 1", html
42 | assert_match "item 2", html
43 |
44 | unsafe_html = CommonMark.new(TEXT, unsafe: true).to_html
45 | assert_match "item 2", unsafe_html
46 | end
47 |
48 | def test_to_latex
49 | latex = CommonMark.new(TEXT).to_latex
50 | assert_match "\\section{title}", latex
51 | assert_match "some body", latex
52 | assert_match "begin{itemize}\n\\item item 1", latex
53 | end
54 |
55 | def test_to_man
56 | man = CommonMark.new(TEXT).to_man
57 | assert_match ".SH\ntitle", man
58 | assert_match ".PP\nsome body", man
59 | assert_match ".IP \\[bu] 2\nitem 1", man
60 | end
61 |
62 | def test_to_xml
63 | xml = CommonMark.new(TEXT, unsafe: true).to_xml
64 | assert_match %(\n title\n ), xml
65 | end
66 | end
67 |
--------------------------------------------------------------------------------
/src/common_mark.cr:
--------------------------------------------------------------------------------
1 | require "./lib_cmark"
2 |
3 | # Crystal wrapper for libcmark, the reference CommonMark C library.
4 | #
5 | # ```
6 | # md = CommonMark.new(text)
7 | # ```
8 | #
9 | # Render to another format:
10 | # ```
11 | # md.to_html # => HTML
12 | # md.to_man # => man page
13 | # ```
14 | class CommonMark
15 | # The original markdown text.
16 | getter text : String
17 |
18 | # Initializes a new CommonMark parser.
19 | #
20 | # ```
21 | # md = CommonMark.new(text, smart: true)
22 | # md.to_html
23 | # ```
24 | #
25 | # Options:
26 | # * `hardbreak` — render `softbreak` elements as hard line breaks.
27 | # * `unsafe` — keep raw HTML and unsafe links (`javascript:`, `vbscript:`, `file:`, and `data:`, except for `image/png`, `image/gif`, `image/jpeg`, or `image/webp` mime types). Raw HTML is replaced by a placeholder HTML comment. Unsafe links are replaced by empty strings.
28 | # * `sourcepos` — include a `data-sourcepos` attribute on all block elements.
29 | # * `smart` — convert straight quotes to curly, `---` to em dashes, `--` to en dashes.
30 | # * `normalize` — normalize tree by consolidating adjacent text nodes.
31 | # * `validate_utf8` — Validate UTF-8 in the input before parsing, replacing illegal sequences with the replacement character U+FFFD.
32 | def initialize(@text, sourcepos = false, hardbreaks = false, normalize = false, validate_utf8 = false, smart = false, unsafe = false)
33 | @options = LibCmark::OPT_DEFAULT
34 | @options |= LibCmark::OPT_SOURCEPOS if sourcepos
35 | @options |= LibCmark::OPT_HARDBREAKS if hardbreaks
36 | @options |= LibCmark::OPT_NORMALIZE if normalize
37 | @options |= LibCmark::OPT_VALIDATE_UTF8 if validate_utf8
38 | @options |= LibCmark::OPT_SMART if smart
39 | @options |= LibCmark::OPT_UNSAFE if unsafe
40 | end
41 |
42 | {% for option in %w(sourcepos hardbreaks normalize smart unsafe validate_utf8)%}
43 | # Returns true if the `{{ option.id }}` option was set.
44 | def {{ option.id }}?
45 | @options & LibCmark::OPT_{{ option.upcase.id }} == LibCmark::OPT_{{ option.upcase.id }}
46 | end
47 | {% end %}
48 |
49 | {% for export in %w(html xml) %}
50 | # Renders `#text` as {{ export.upcase.id }}.
51 | def to_{{ export.id }}
52 | parse do |document|
53 | result = LibCmark.render_{{ export.id }}(document, @options)
54 | String.new(result)
55 | end
56 | end
57 | {% end %}
58 |
59 | {% for export in %w(commonmark latex man) %}
60 | # Renders `#text` as a {{ export.id }} page.
61 | def to_{{ export.id }}(width = 80)
62 | parse do |document|
63 | result = LibCmark.render_{{ export.id }}(document, @options, width)
64 | String.new(result)
65 | end
66 | end
67 | {% end %}
68 |
69 | def parse
70 | document = LibCmark.parse_document(text, text.bytesize, @options)
71 | yield document
72 | ensure
73 | LibCmark.node_free(document) if document
74 | end
75 | end
76 |
--------------------------------------------------------------------------------
/src/lib_cmark.cr.in:
--------------------------------------------------------------------------------
1 | @[Include("cmark.h", flags: "-Iext")]
2 | @[Link(ldflags: "-L#{__DIR__}/../ext -lcmark")]
3 | lib LibCmark
4 | #fun markdown_to_html = cmark_markdown_to_html
5 |
6 | #fun node_new = cmark_node_new
7 | fun node_free = cmark_node_free
8 |
9 | #fun node_next = cmark_node_next
10 | #fun node_previous = cmark_node_previous
11 | #fun node_parent = cmark_node_parent
12 | #fun node_first_child = cmark_node_first_child
13 | #fun node_last_child = cmark_node_last_child
14 |
15 | #fun iter_new = cmark_iter_new
16 | #fun iter_free = cmark_iter_free
17 | #fun iter_next = cmark_iter_next
18 | #fun iter_get_node = cmark_iter_get_node
19 | #fun iter_get_event_type = cmark_iter_get_event_type
20 | #fun iter_get_root = cmark_iter_get_root
21 | #fun iter_reset = cmark_iter_reset
22 |
23 | #fun node_get_user_data = cmark_node_get_user_data
24 | #fun node_set_user_data = cmark_node_set_user_data
25 | #fun node_get_type = cmark_node_get_type
26 | #fun node_get_type_string = cmark_node_get_type_string
27 | #fun node_get_literal = cmark_node_get_literal
28 | #fun node_set_literal = cmark_node_set_literal
29 | #fun node_get_header_level = cmark_node_get_header_level
30 | #fun node_set_header_level = cmark_node_set_header_level
31 | #fun node_get_list_type = cmark_node_get_list_type
32 | #fun node_set_list_type = cmark_node_set_list_type
33 | #fun node_get_list_delim = cmark_node_get_list_delim
34 | #fun node_set_list_delim = cmark_node_set_list_delim
35 | #fun node_get_list_start = cmark_node_get_list_start
36 | #fun node_set_list_start = cmark_node_set_list_start
37 | #fun node_get_list_tight = cmark_node_get_list_tight
38 | #fun node_set_list_tight = cmark_node_set_list_tight
39 | #fun node_get_fence_info = cmark_node_get_fence_info
40 | #fun node_set_fence_info = cmark_node_set_fence_info
41 | #fun node_get_url = cmark_node_get_url
42 | #fun node_set_url = cmark_node_set_url
43 | #fun node_get_title = cmark_node_get_title
44 | #fun node_set_title = cmark_node_set_title
45 | #fun node_get_start_line = cmark_node_get_start_line
46 | #fun node_get_start_column = cmark_node_get_start_column
47 | #fun node_get_end_line = cmark_node_get_end_line
48 | #fun node_get_end_column = cmark_node_get_end_column
49 |
50 | #fun node_unlink = cmark_node_unlink
51 | #fun node_insert_before = cmark_node_insert_before
52 | #fun node_insert_after = cmark_node_insert_after
53 | #fun node_prepend_child = cmark_node_prepend_child
54 | #fun node_append_child = cmark_node_append_child
55 | #fun consolidate_text_nodes = cmark_consolidate_text_nodes
56 |
57 | #fun parser_new = cmark_parser_new
58 | #fun parser_free = cmark_parser_free
59 | #fun parser_feed = cmark_parser_feed
60 | #fun parser_finish = cmark_parser_finish
61 | fun parse_document = cmark_parse_document
62 | #fun parse_file = cmark_parse_file
63 |
64 | fun render_xml = cmark_render_xml
65 | fun render_html = cmark_render_html
66 | fun render_man = cmark_render_man
67 | fun render_commonmark = cmark_render_commonmark
68 | fun render_latex = cmark_render_latex
69 |
70 | OPT_DEFAULT = CMARK_OPT_DEFAULT
71 | OPT_SOURCEPOS = CMARK_OPT_SOURCEPOS
72 | OPT_HARDBREAKS = CMARK_OPT_HARDBREAKS
73 | OPT_NORMALIZE = CMARK_OPT_NORMALIZE
74 | OPT_SMART = CMARK_OPT_SMART
75 | OPT_VALIDATE_UTF8 = CMARK_OPT_VALIDATE_UTF8
76 | # OPT_SAFE = CMARK_OPT_SAFE
77 | OPT_UNSAFE = CMARK_OPT_UNSAFE
78 |
79 | VERSION = CMARK_VERSION
80 | VERSION_STRING = CMARK_VERSION_STRING
81 | end
82 |
--------------------------------------------------------------------------------