├── .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 | --------------------------------------------------------------------------------