├── .gitignore
├── README.md
├── build.sbt
├── project
├── build.properties
└── plugins.sbt
└── src
├── main
└── scala
│ └── cmark
│ ├── Iter.scala
│ ├── Node.scala
│ ├── Options.scala
│ ├── Parser.scala
│ ├── Render.scala
│ ├── cmark.scala
│ └── package.scala
└── test
└── scala
└── cmark
└── CMarkSuite.scala
/.gitignore:
--------------------------------------------------------------------------------
1 | *.class
2 | *.log
3 |
4 | # sbt specific
5 | .cache/
6 | .history/
7 | .lib/
8 | dist/*
9 | target/
10 | lib_managed/
11 | src_managed/
12 | project/boot/
13 | project/plugins/project/
14 |
15 | # Scala-IDE specific
16 | .scala_dependencies
17 | .worksheet
18 | .idea
19 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #
2 | cmark-scala provides [Scala Native](http://www.scala-native.org/) bindings for [cmark](https://github.com/commonmark/cmark). cmark allows to parse, manipulate and render CommonMark documents.
3 |
4 | The bindings were directly derived from [cmark.h](https://github.com/commonmark/cmark/blob/master/src/cmark.h). Comments were retained and adapted if necessary. The naming of functions and their encapsulation follows Scala's conventions. Note that `*_new` functions were renamed to `create` as to prevent name collisions with the eponymous Scala keyword.
5 |
6 | ## Example
7 | ```scala
8 | import cmark._
9 | import scalanative.unsafe._
10 | import scalanative.unsigned._
11 |
12 | var level = -1
13 | def onNode(eventType: EventType, node: Ptr[Node]): Unit = {
14 | eventType match {
15 | case EventType.Enter => level += 1
16 | case EventType.Exit => level -= 1
17 | }
18 |
19 | val levelStr = " " * level
20 | val startLine = Node.getStartLine(node)
21 | val endLine = Node.getEndLine(node)
22 |
23 | Node.getType(node) match {
24 | case NodeType.Text =>
25 | val text = fromCString(Node.getLiteral(node))
26 | println(s"${levelStr}text node @ line $startLine-$endLine: $text")
27 |
28 | case _ =>
29 | val nodeTypeStr = fromCString(Node.getTypeString(node))
30 | println(s"$levelStr$nodeTypeStr node @ $startLine-$endLine")
31 | }
32 | }
33 |
34 | val test =
35 | """# Chapter
36 | |## Section
37 | |### Sub-section
38 | |
39 | |Hello World from *cmark-scala*!
40 | """.stripMargin
41 |
42 | println("cmark version: " + fromCString(cmark.versionString()))
43 | println()
44 |
45 | val docNode = Parser.parseDocument(
46 | toCString(test), test.length.toULong, Options.SourcePosition)
47 | val iter = Iter.create(docNode)
48 | var evType = Iter.next(iter)
49 | while (evType != EventType.Done) {
50 | onNode(evType, Iter.getNode(iter))
51 | evType = Iter.next(iter)
52 | }
53 | Iter.free(iter)
54 |
55 | val html = fromCString(Render.html(docNode, Options.Default))
56 | println()
57 | println(html)
58 |
59 | Node.free(docNode)
60 | ```
61 |
62 | **Output:**
63 |
64 | ```
65 | cmark version: 0.27.1
66 |
67 | document node @ 1-6
68 | heading node @ 1-1
69 | text node @ line 0-0: Chapter
70 | heading node @ 1-1
71 | heading node @ 2-2
72 | text node @ line 0-0: Section
73 | heading node @ 2-2
74 | heading node @ 3-3
75 | text node @ line 0-0: Sub-section
76 | heading node @ 3-3
77 | paragraph node @ 5-5
78 | text node @ line 0-0: Hello World from
79 | emph node @ 0-0
80 | text node @ line 0-0: cmark-scala
81 | emph node @ 0-0
82 | text node @ line 0-0: !
83 | paragraph node @ 5-5
84 | document node @ 1-6
85 |
86 |
Chapter
87 | Section
88 | Sub-section
89 | Hello World from cmark-scala!
90 | ```
91 |
92 | ## Dependency
93 | ```scala
94 | libraryDependencies += "tech.sparse" %% "cmark-scala" % "0.2.0-SNAPSHOT"
95 | ```
96 |
97 | ## Install Native Library
98 | In order to use this library you need to install `cmark` which installs `libcmark`.
99 |
100 | * macOS users can use the following command.
101 |
102 | ```
103 | $ brew install cmark
104 | ```
105 |
106 | * Linux/Ubuntu users can use the following commands.
107 |
108 | ```
109 | $ sudo apt update
110 | $ sudo apt install cmark
111 | ```
112 |
113 | ## License
114 | cmark-scala is licensed under the terms of the Apache v2.0 license. Its function interfaces and comments were derived from `cmark.h`, which is licensed under BSD-2-Clause.
115 |
116 | ## Authors
117 | * Tim Nieradzik
118 |
--------------------------------------------------------------------------------
/build.sbt:
--------------------------------------------------------------------------------
1 | enablePlugins(ScalaNativePlugin)
2 |
3 | name := "cmark-scala"
4 |
5 | version := "0.2.0-SNAPSHOT"
6 |
7 | organization := "tech.sparse"
8 |
9 | scalaVersion := "2.13.6"
10 |
11 | libraryDependencies += "org.scalameta" %%% "munit" % "0.7.29" % Test
12 |
13 | testFrameworks += new TestFramework("munit.Framework")
14 |
--------------------------------------------------------------------------------
/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=1.5.5
2 |
--------------------------------------------------------------------------------
/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.0")
2 |
--------------------------------------------------------------------------------
/src/main/scala/cmark/Iter.scala:
--------------------------------------------------------------------------------
1 | package cmark
2 |
3 | import scalanative.unsafe._
4 |
5 | /**
6 | * ## Iterator
7 | *
8 | * An iterator will walk through a tree of nodes, starting from a root
9 | * node, returning one node at a time, together with information about
10 | * whether the node is being entered or exited. The iterator will
11 | * first descend to a child node, if there is one. When there is no
12 | * child, the iterator will go to the next sibling. When there is no
13 | * next sibling, the iterator will return to the parent (but with
14 | * an [[EventType]] of [[EventType.Exit]]). The iterator will
15 | * return [[EventType.Done]] when it reaches the root node again. One natural
16 | * application is an HTML renderer, where an [[EventType.Enter]] event
17 | * outputs an open tag and an [[EventType.Exit]] event outputs a close tag.
18 | * An iterator might also be used to transform an AST in some systematic
19 | * way, for example, turning all level-3 headings into regular paragraphs.
20 | *
21 | * def usageExample(root: Ptr[Node]) {
22 | * val iter = Iter.create(root)
23 | * var evType = Iter.next(iter)
24 | *
25 | * while (evType != EventType.Done) {
26 | * val cur = Iter.getNode(iter)
27 | * // Do something with `cur` and `evType`
28 | * evType = Iter.next(iter)
29 | * }
30 | *
31 | * Iter.free(iter)
32 | * }
33 | *
34 | * Iterators will never return `EXIT` events for leaf nodes, which are nodes
35 | * of type:
36 | *
37 | * - [[NodeType.HtmlBlock]]
38 | * - [[NodeType.ThematicBreak]]
39 | * - [[NodeType.CodeBlock]]
40 | * - [[NodeType.Text]]
41 | * - [[NodeType.SoftBreak]]
42 | * - [[NodeType.LineBreak]]
43 | * - [[NodeType.Code]]
44 | * - [[NodeType.HtmlInline]]
45 | *
46 | * Nodes must only be modified after an [[EventType.Exit]] event, or an
47 | * [[EventType.Enter]] event for leaf nodes.
48 | */
49 |
50 | @link("cmark")
51 | @extern
52 | object Iter {
53 | /** Creates a new iterator starting at 'root'. The current node and event
54 | * type are undefined until [[Iter.next]] is called for the first time.
55 | * The memory allocated for the iterator should be released using
56 | * [[Iter.free]] when it is no longer needed.
57 | */
58 | @name("cmark_iter_new")
59 | def create(root: Ptr[Node]): Ptr[Iter] = extern
60 |
61 | /** Frees the memory allocated for an iterator. */
62 | @name("cmark_iter_free")
63 | def free(iter: Ptr[Iter]): Unit = extern
64 |
65 | /** Advances to the next node and returns the event type ([[EventType.Enter]],
66 | * [[EventType.Exit]] or [[EventType.Done]]).
67 | */
68 | @name("cmark_iter_next")
69 | def next(iter: Ptr[Iter]): EventType = extern
70 |
71 | /** Returns the current node. */
72 | @name("cmark_iter_get_node")
73 | def getNode(iter: Ptr[Iter]): Ptr[Node] = extern
74 |
75 | /** Returns the current event type. */
76 | @name("cmark_iter_get_event_type")
77 | def getEventType(iter: Ptr[Iter]): EventType = extern
78 |
79 | /** Returns the root node. */
80 | @name("cmark_iter_get_root")
81 | def getRoot(iter: Ptr[Iter]): Ptr[Node] = extern
82 |
83 | /** Resets the iterator so that the current node is 'current' and
84 | * the event type is [[EventType]]. The new current node must be a
85 | * descendant of the root node or the root node itself.
86 | */
87 | @name("cmark_iter_reset")
88 | def reset(iter: Ptr[Iter], current: Ptr[Node], eventType: EventType): Unit = extern
89 | }
90 |
--------------------------------------------------------------------------------
/src/main/scala/cmark/Node.scala:
--------------------------------------------------------------------------------
1 | package cmark
2 |
3 | import scalanative.unsafe._
4 |
5 | /**
6 | * ## Node
7 | */
8 | @link("cmark")
9 | @extern
10 | object Node {
11 | /** Creates a new node of type 'type'. Note that the node may have
12 | * other required properties, which it is the caller's responsibility
13 | * to assign.
14 | */
15 | @name("cmark_node_new")
16 | def create(nodeType: NodeType): Ptr[Node] = extern
17 |
18 | /** Frees the memory allocated for a node and any children. */
19 | @name("cmark_node_free")
20 | def free(doc: Ptr[Node]): Unit = extern
21 |
22 | /**
23 | * ## Tree Traversal
24 | */
25 |
26 | /** @return The next node in the sequence after 'node', or null if
27 | * there is none.
28 | */
29 | @name("cmark_node_next")
30 | def nodeNext(node: Ptr[Node]): Ptr[Node] = extern
31 |
32 | /** @return The previous node in the sequence after 'node', or null if
33 | * there is none.
34 | */
35 | @name("cmark_node_previous")
36 | def nodePrevious(node: Ptr[Node]): Ptr[Node] = extern
37 |
38 | /** @return The parent of 'node', or null if there is none. */
39 | @name("cmark_node_parent")
40 | def parent(node: Ptr[Node]): Ptr[Node] = extern
41 |
42 | /** @return The first child of 'node', or null if 'node' has no children.
43 | */
44 | @name("cmark_node_first_child")
45 | def firstChild(node: Ptr[Node]): Ptr[Node] = extern
46 |
47 | /** @return The last child of 'node', or null if 'node' has no children.
48 | */
49 | @name("cmark_node_last_child")
50 | def lastChild(node: Ptr[Node]): Ptr[Node] = extern
51 |
52 | /**
53 | * ## Accessors
54 | */
55 |
56 | /** @return The user data of 'node'. */
57 | @name("cmark_node_get_user_data")
58 | def getUserData(node: Ptr[Node]): Ptr[Byte] = extern
59 |
60 | /** Sets arbitrary user data for 'node'.
61 | * @return 1 on success, 0 on failure.
62 | */
63 | @name("cmark_node_set_user_data")
64 | def setUserData(node: Ptr[Node], userData: Ptr[Byte]): CInt = extern
65 |
66 | /** @return The type of 'node', or [[NodeType.None]] on error. */
67 | @name("cmark_node_get_type")
68 | def getType(node: Ptr[Node]): NodeType = extern
69 |
70 | /** Like 'cmark_node_get_type', but returns a string representation
71 | of the type, or `""`.
72 | */
73 | @name("cmark_node_get_type_string")
74 | def getTypeString(node: Ptr[Node]): CString = extern
75 |
76 | /** @return The string contents of 'node', or an empty string if none
77 | * is set.
78 | */
79 | @name("cmark_node_get_literal")
80 | def getLiteral(node: Ptr[Node]): CString = extern
81 |
82 | /** Sets the string contents of 'node'.
83 | * @return 1 on success, 0 on failure.
84 | */
85 | @name("cmark_node_set_literal")
86 | def setLiteral(node: Ptr[Node], content: CString): CInt = extern
87 |
88 | /** @return The heading level of 'node', or 0 if 'node' is not a heading.
89 | */
90 | @name("cmark_node_get_heading_level")
91 | def getHeadingLevel(node: Ptr[Node]): CInt = extern
92 |
93 | /** Sets the heading level of 'node'
94 | * @return 1 on success and 0 on error.
95 | */
96 | @name("cmark_node_set_heading_level")
97 | def setHeadingLevel(node: Ptr[Node], level: CInt): CInt = extern
98 |
99 | /** @return The list type of 'node', or [[ListType.None]] if 'node'
100 | * is not a list.
101 | */
102 | @name("cmark_node_get_list_type")
103 | def getListType(node: Ptr[Node]): ListType = extern
104 |
105 | /** Sets the list type of 'node'
106 | * @return 1 on success and 0 on error.
107 | */
108 | @name("cmark_node_set_list_type")
109 | def setListType(node: Ptr[Node], tpe: ListType): CInt = extern
110 |
111 | /** @return The list delimiter type of 'node', or [[DelimType.None]] if 'node'
112 | * is not a list.
113 | */
114 | @name("cmark_node_get_list_delim")
115 | def getListDelim(node: Ptr[Node]): DelimType = extern
116 |
117 | /** Sets the list delimiter type of 'node'
118 | * @return 1 on success and 0 on error.
119 | */
120 | @name("cmark_node_set_list_delim")
121 | def setListDelim(node: Ptr[Node], delim: DelimType): CInt = extern
122 |
123 | /** @return starting number of 'node', if it is an ordered list, otherwise 0. */
124 | @name("cmark_node_get_list_start")
125 | def getListStart(node: Ptr[Node]): CInt = extern
126 |
127 | /** Sets starting number of 'node', if it is an ordered list.
128 | * @return 1 on success, 0 on failure.
129 | */
130 | @name("cmark_node_set_list_start")
131 | def setListStart(node: Ptr[Node], start: CInt): CInt = extern
132 |
133 | /** @return 1 if 'node' is a tight list, 0 otherwise. */
134 | @name("cmark_node_get_list_tight")
135 | def getListTight(node: Ptr[Node]): CInt = extern
136 |
137 | /** Sets the "tightness" of a list.
138 | * @return 1 on success, 0 on failure.
139 | */
140 | @name("cmark_node_set_list_tight")
141 | def setListTight(node: Ptr[Node], tight: CInt): CInt = extern
142 |
143 | /** @return The info string from a fenced code block. */
144 | @name("cmark_node_get_fence_info")
145 | def getFenceInfo(node: Ptr[Node]): CString = extern
146 |
147 | /** Sets the info string in a fenced code block
148 | * @return 1 on success and 0 on failure.
149 | */
150 | @name("cmark_node_set_fence_info")
151 | def setFenceInfo(node: Ptr[Node], info: CString): CInt = extern
152 |
153 | /** @return The URL of a link or image 'node', or an empty string if no URL
154 | * is set.
155 | */
156 | @name("cmark_node_get_url")
157 | def getUrl(node: Ptr[Node]): CString = extern
158 |
159 | /** Sets the URL of a link or image 'node'.
160 | * @return 1 on success, 0 on failure.
161 | */
162 | @name("cmark_node_set_url")
163 | def setUrl(node: Ptr[Node], url: CString): CInt = extern
164 |
165 | /** @return The title of a link or image 'node', or an empty string if no
166 | * title is set.
167 | */
168 | @name("cmark_node_get_title")
169 | def getTitle(node: Ptr[Node]): CString = extern
170 |
171 | /** Sets the title of a link or image 'node'.
172 | * @return 1 on success, 0 on failure.
173 | */
174 | @name("cmark_node_set_title")
175 | def setTitle(node: Ptr[Node], title: CString): CInt = extern
176 |
177 | /** @return The literal "on enter" text for a custom 'node', or
178 | an empty string if no onEnter is set.
179 | */
180 | @name("cmark_node_get_on_enter")
181 | def getOnEnter(node: Ptr[Node]): CString = extern
182 |
183 | /** Sets the literal text to render "on enter" for a custom 'node'. Any
184 | * children of the node will be rendered after this text.
185 | * @return 1 on success 0 on failure.
186 | */
187 | @name("cmark_node_set_on_enter")
188 | def setOnEnter(node: Ptr[Node], onEnter: CString): CInt = extern
189 |
190 | /** @return The literal "on exit" text for a custom 'node', or an empty string
191 | * if no on_exit is set.
192 | */
193 | @name("cmark_node_get_on_exit")
194 | def getOnExit(node: Ptr[Node]): CString = extern
195 |
196 | /** Sets the literal text to render "on exit" for a custom 'node'.
197 | * Any children of the node will be rendered before this text.
198 | * @return 1 on success 0 on failure.
199 | */
200 | @name("cmark_node_set_on_exit")
201 | def setOnExit(node: Ptr[Node], onExit: CString): CInt = extern
202 |
203 | /** @return The line on which 'node' begins. */
204 | @name("cmark_node_get_start_line")
205 | def getStartLine(node: Ptr[Node]): CInt = extern
206 |
207 | /** @return The column at which 'node' begins. */
208 | @name("cmark_node_get_start_column")
209 | def getStartColumn(node: Ptr[Node]): CInt = extern
210 |
211 | /** @return The line on which 'node' ends. */
212 | @name("cmark_node_get_end_line")
213 | def getEndLine(node: Ptr[Node]): CInt = extern
214 |
215 | /** @return The column at which 'node' ends. */
216 | @name("cmark_node_get_end_column")
217 | def getEndColumn(node: Ptr[Node]): CInt = extern
218 |
219 | /**
220 | * ## Tree Manipulation
221 | */
222 |
223 | /** Unlinks a 'node', removing it from the tree, but not freeing its
224 | * memory. (Use [[Node.free]] for that.)
225 | */
226 | @name("cmark_node_unlink")
227 | def unlink(node: Ptr[Node]): Unit = extern
228 |
229 | /** Inserts 'sibling' before 'node'.
230 | * @return 1 on success, 0 on failure.
231 | */
232 | @name("cmark_node_insert_before")
233 | def insertBefore(node: Ptr[Node], sibling: Ptr[Node]): CInt = extern
234 |
235 | /** Inserts 'sibling' after 'node'.
236 | * @return 1 on success, 0 on failure.
237 | */
238 | @name("cmark_node_insert_after")
239 | def insertAfter(node: Ptr[Node], sibling: Ptr[Node]): CInt = extern
240 |
241 | /** Replaces 'oldNode' with 'newNode' and unlinks 'oldNode' (but does
242 | * not free its memory).
243 | * @return 1 on success, 0 on failure.
244 | */
245 | @name("cmark_node_replace")
246 | def nodeReplace(oldNode: Ptr[Node], newNode: Ptr[Node]): CInt = extern
247 |
248 | /** Adds 'child' to the beginning of the children of 'node'.
249 | * @return 1 on success, 0 on failure.
250 | */
251 | @name("cmark_node_prepend_child")
252 | def prependChild(node: Ptr[Node], child: Ptr[Node]): CInt = extern
253 |
254 | /** Adds 'child' to the end of the children of 'node'.
255 | * @return 1 on success, 0 on failure.
256 | */
257 | @name("cmark_node_append_child")
258 | def appendChild(node: Ptr[Node], child: Ptr[Node]): CInt = extern
259 |
260 | /** Consolidates adjacent text nodes. */
261 | @name("cmark_consolidate_text_nodes")
262 | def consolidateTextNodes(root: Ptr[Node]): Unit = extern
263 | }
264 |
--------------------------------------------------------------------------------
/src/main/scala/cmark/Options.scala:
--------------------------------------------------------------------------------
1 | package cmark
2 |
3 | /**
4 | * ## Parsing
5 | */
6 | object Options {
7 | /** Default options. */
8 | val Default = 0
9 |
10 | /**
11 | * ## Options affecting rendering
12 | */
13 |
14 | /** Include a `data-sourcepos` attribute on all block elements. */
15 | val SourcePosition = 1 << 1
16 |
17 | /** Render `softbreak` elements as hard line breaks. */
18 | val HardBreaks = 1 << 2
19 |
20 | /** Suppress raw HTML and unsafe links (`javascript:`, `vbscript:`,
21 | * `file:`, and `data:`, except for `image/png`, `image/gif`,
22 | * `image/jpeg`, or `image/webp` mime types). Raw HTML is replaced
23 | * by a placeholder HTML comment. Unsafe links are replaced by
24 | * empty strings.
25 | */
26 | val Safe = 1 << 3
27 |
28 | /** Render `softbreak` elements as spaces. */
29 | val NoBreaks = 1 << 4
30 |
31 | /**
32 | * ## Options affecting parsing
33 | */
34 |
35 | /** Validate UTF-8 in the input before parsing, replacing illegal
36 | * sequences with the replacement character U+FFFD.
37 | */
38 | val ValidateUtf8 = 1 << 9
39 |
40 | /** Convert straight quotes to curly, --- to em dashes, -- to en dashes. */
41 | val Smart = 1 << 10
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/scala/cmark/Parser.scala:
--------------------------------------------------------------------------------
1 | package cmark
2 |
3 | import scalanative.unsafe._
4 | import scalanative.libc.stdio.FILE
5 |
6 | /**
7 | * ## Parsing
8 | *
9 | * Simple interface:
10 | *
11 | * val document = Parser.parseDocument(c"Hello *world*", 13, Options.Default)
12 | *
13 | * Streaming interface:
14 | *
15 | * val parser = Parser.create(Options.Default)
16 | * Parser.feed(parser, buffer, bytes)
17 | * val document = Parser.finish(parser)
18 | * Parser.free(parser)
19 | */
20 | @link("cmark")
21 | @extern
22 | object Parser {
23 | /** Creates a new parser object. */
24 | @name("cmark_parser_new")
25 | def create(options: CInt): Ptr[Parser] = extern
26 |
27 | /** Creates a new parser object with the given memory allocator */
28 | @name("cmark_parser_new_with_mem")
29 | def createWithMem(options: CInt, mem: Ptr[Memory]): Ptr[Parser] = extern
30 |
31 | /** Frees memory allocated for a parser object. */
32 | @name("cmark_parser_free")
33 | def free(parser: Ptr[Parser]): Unit = extern
34 |
35 | /** Feeds a string of length 'len' to 'parser'. */
36 | @name("cmark_parser_feed")
37 | def feed(parser: Ptr[Parser], buffer: CString, bytes: CSize): Unit = extern
38 |
39 | /** Finish parsing and return a pointer to a tree of nodes. */
40 | @name("cmark_parser_finish")
41 | def finish(parser: Ptr[Parser]): Ptr[Node] = extern
42 |
43 | /** Parse a CommonMark document in 'buffer' of length 'len'.
44 | * Returns a pointer to a tree of nodes. The memory allocated for
45 | * the node tree should be released using [[Node.free]]
46 | * when it is no longer needed.
47 | */
48 | @name("cmark_parse_document")
49 | def parseDocument(buffer: CString, len: CSize, options: CInt): Ptr[Node] = extern
50 |
51 | /** Parse a CommonMark document in file 'file', returning a pointer to
52 | * a tree of nodes. The memory allocated for the node tree should be
53 | * released using [[Node.free]] when it is no longer needed.
54 | */
55 | @name("cmark_parse_file")
56 | def parseFile(file: Ptr[FILE], options: CInt): Ptr[Node] = extern
57 | }
58 |
--------------------------------------------------------------------------------
/src/main/scala/cmark/Render.scala:
--------------------------------------------------------------------------------
1 | package cmark
2 |
3 | import scalanative.unsafe._
4 |
5 | /**
6 | * ## Rendering
7 | */
8 | @link("cmark")
9 | @extern
10 | object Render {
11 | /** Render a 'node' tree as XML. It is the caller's responsibility to free
12 | * the returned buffer.
13 | */
14 | @name("cmark_render_xml")
15 | def xml(root: Ptr[Node], options: CInt): CString = extern
16 |
17 | /** Render a 'node' tree as an HTML fragment. It is up to the user
18 | * to add an appropriate header and footer. It is the caller's
19 | * responsibility to free the returned buffer.
20 | */
21 | @name("cmark_render_html")
22 | def html(root: Ptr[Node], options: CInt): CString = extern
23 |
24 | /** Render a 'node' tree as a groff man page, without the header.
25 | * It is the caller's responsibility to free the returned buffer.
26 | */
27 | @name("cmark_render_man")
28 | def man(root: Ptr[Node], options: CInt, width: CInt): CString = extern
29 |
30 | /** Render a 'node' tree as a commonmark document.
31 | * It is the caller's responsibility to free the returned buffer.
32 | */
33 | @name("cmark_render_commonmark")
34 | def commonMark(root: Ptr[Node], options: CInt, width: CInt): CString = extern
35 |
36 | /** Render a 'node' tree as a LaTeX document.
37 | * It is the caller's responsibility to free the returned buffer.
38 | */
39 | @name("cmark_render_latex")
40 | def laTeX(root: Ptr[Node], options: CInt, width: CInt): CString = extern
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/scala/cmark/cmark.scala:
--------------------------------------------------------------------------------
1 | package cmark
2 |
3 | import scalanative.unsafe._
4 |
5 | @link("cmark")
6 | @extern
7 | object cmark {
8 | /** Convert 'text' (assumed to be a UTF-8 encoded string with length
9 | * 'len') from CommonMark Markdown to HTML, returning a null-terminated,
10 | * UTF-8-encoded string. It is the caller's responsibility
11 | * to free the returned buffer.
12 | */
13 | @name("cmark_markdown_to_html")
14 | def markdownToHtml(text: CString, len: CSize, options: CInt): CString = extern
15 |
16 | /**
17 | * ## Version information
18 | */
19 |
20 | /** The library version as integer for runtime checks.
21 | *
22 | * * Bits 16-23 contain the major version.
23 | * * Bits 8-15 contain the minor version.
24 | * * Bits 0-7 contain the patchlevel.
25 | *
26 | * In hexadecimal format, the number 0x010203 represents version 1.2.3.
27 | */
28 | @name("cmark_version")
29 | def version(): CInt = extern
30 |
31 | /** The library version string for runtime checks. */
32 | @name("cmark_version_string")
33 | def versionString(): CString = extern
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/scala/cmark/package.scala:
--------------------------------------------------------------------------------
1 | import scalanative.unsafe._
2 |
3 | /**
4 | * ## Constants
5 | */
6 | package object cmark {
7 | type Node = Byte
8 | type Parser = Byte
9 | type Iter = Byte
10 | type Memory = Byte
11 |
12 | type EventType = CInt
13 | object EventType {
14 | val None = 0
15 | val Done = 1
16 | val Enter = 2
17 | val Exit = 3
18 | }
19 |
20 | type NodeType = CInt
21 | object NodeType {
22 | /* Error status */
23 | val None = 0
24 |
25 | /* Block */
26 | val Document = 1
27 | val BlockQuote = 2
28 | val List = 3
29 | val Item = 4
30 | val CodeBlock = 5
31 | val HtmlBlock = 6
32 | val CustomBlock = 7
33 | val Paragraph = 8
34 | val Heading = 9
35 | val ThematicBreak = 10
36 |
37 | val FirstBlock = Document
38 | val LastBlock = ThematicBreak
39 |
40 | /* Inline */
41 | val Text = 11
42 | val SoftBreak = 12
43 | val LineBreak = 13
44 | val Code = 14
45 | val HtmlInline = 15
46 | val CustomInline = 16
47 | val Emphasis = 17
48 | val Strong = 18
49 | val Link = 19
50 | val Image = 20
51 |
52 | val FirstInline = Text
53 | val LastInline = Image
54 | }
55 |
56 | type ListType = CInt
57 | object ListType {
58 | val None = 0
59 | val Bullet = 1
60 | val Ordered = 2
61 | }
62 |
63 | type DelimType = CInt
64 | object DelimType {
65 | val None = 0
66 | val Period = 1
67 | val Parenthesis = 2
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/test/scala/cmark/CMarkSuite.scala:
--------------------------------------------------------------------------------
1 | package cmark
2 |
3 | import scalanative.unsafe._
4 | import scalanative.unsigned._
5 |
6 | class CMarkSuite extends munit.FunSuite {
7 | test("Print version string of cmark") {
8 | println("cmark version: " + fromCString(cmark.versionString()))
9 | }
10 |
11 | test("Example") {
12 | var level = -1
13 |
14 | def onNode(eventType: EventType, node: Ptr[Node]): Unit = {
15 | eventType match {
16 | case EventType.Enter => level += 1
17 | case EventType.Exit => level -= 1
18 | }
19 |
20 | val levelStr = " " * level
21 | val startLine = Node.getStartLine(node)
22 | val endLine = Node.getEndLine(node)
23 |
24 | Node.getType(node) match {
25 | case NodeType.Text =>
26 | val text = fromCString(Node.getLiteral(node))
27 | println(s"${levelStr}text node @ line $startLine-$endLine: $text")
28 |
29 | case _ =>
30 | val nodeTypeStr = fromCString(Node.getTypeString(node))
31 | println(s"$levelStr$nodeTypeStr node @ $startLine-$endLine")
32 | }
33 | }
34 |
35 | val test =
36 | """# Chapter
37 | |## Section
38 | |### Sub-section
39 | |
40 | |Hello World from *cmark-scala*!
41 | """.stripMargin
42 |
43 | Zone { implicit z =>
44 | val docNode = Parser.parseDocument(
45 | toCString(test), test.length.toULong, Options.SourcePosition)
46 | val iter = Iter.create(docNode)
47 | var evType = Iter.next(iter)
48 | while (evType != EventType.Done) {
49 | onNode(evType, Iter.getNode(iter))
50 | evType = Iter.next(iter)
51 | }
52 | Iter.free(iter)
53 |
54 | val html = fromCString(Render.html(docNode, Options.Default))
55 | Node.free(docNode)
56 |
57 | println()
58 | println(html)
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------