Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
543 changes: 413 additions & 130 deletions CommonMark.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "CommonMark::CommonMarkTests"
BuildableName = "CommonMarkTests.xctest"
BlueprintName = "CommonMarkTests"
ReferencedContainer = "container:CommonMark.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
Expand Down
16 changes: 0 additions & 16 deletions Package.resolved

This file was deleted.

19 changes: 13 additions & 6 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
// swift-tools-version:4.2
// swift-tools-version:5.5

import PackageDescription

let package = Package(
name: "CommonMark",
products: [
.library(name: "CommonMark", targets: ["CommonMark"])
],
dependencies: [
.package(url: "https://github.com/objcio/Ccmark.git", .branch("master"))
.library(name: "CommonMark", targets: ["CommonMark"]),
.library(name: "Ccmark", targets: ["Ccmark"]),

],
dependencies: [],
targets: [
.target(name: "CommonMark")
.target(name: "CommonMark", dependencies: ["Ccmark"]),
.systemLibrary(
name: "Ccmark",
pkgConfig: "libcmark",
providers: [
.brew(["commonmark"])
]),
.testTarget(name: "CommonMarkTests", dependencies: ["CommonMark"]),
]
)
5 changes: 5 additions & 0 deletions Sources/Ccmark/module.modulemap
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module Ccmark [system] {
header "/usr/local/include/cmark.h"
link "libcmark"
export *
}
99 changes: 74 additions & 25 deletions Sources/CommonMark/Node.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
import Foundation
import Ccmark

func markdownToHtml(string: String) -> String {


func markdowntoHTML(string: String) -> String {
let outString = cmark_markdown_to_html(string, string.utf8.count, 0)!
defer { free(outString) }
return String(cString: outString)
Expand All @@ -29,12 +31,49 @@ struct Markdown {
}

extension String {
init?(unsafeCString: UnsafePointer<Int8>!) {
// We're going through Data instead of using init(cstring:) because that leaks memory on Linux.

init?(unsafeCString: UnsafePointer<CChar>!) {
guard let cString = unsafeCString else { return nil }
self.init(cString: cString)
let data = cString.withMemoryRebound(to: UInt8.self, capacity: strlen(cString), { p in
return Data(UnsafeBufferPointer(start: p, count: strlen(cString)))
})
self.init(data: data, encoding: .utf8)
}

init?(freeingCString str: UnsafeMutablePointer<CChar>?) {
guard let cString = str else { return nil }
let data = cString.withMemoryRebound(to: UInt8.self, capacity: strlen(cString), { p in
return Data(UnsafeBufferPointer(start: p, count: strlen(cString)))
})
str?.deallocate()
self.init(data: data, encoding: .utf8)
}
}

/// A position in a Markdown document. Note that both `line` and `column` are 1-based.
public struct Position {
public var line: Int32
public var column: Int32
}

public struct RenderingOptions: OptionSet {
public var rawValue: Int32
public init(rawValue: Int32 = CMARK_OPT_DEFAULT) {
self.rawValue = rawValue
}

static public let sourcePos = RenderingOptions(rawValue: CMARK_OPT_SOURCEPOS)
static public let hardBreaks = RenderingOptions(rawValue: CMARK_OPT_HARDBREAKS)
static public let safe = RenderingOptions(rawValue: CMARK_OPT_SAFE)
static public let unsafe = RenderingOptions(rawValue: CMARK_OPT_UNSAFE)
static public let noBreaks = RenderingOptions(rawValue: CMARK_OPT_NOBREAKS)
static public let normalize = RenderingOptions(rawValue: CMARK_OPT_NORMALIZE)
static public let validateUTF8 = RenderingOptions(rawValue: CMARK_OPT_VALIDATE_UTF8)
static public let smart = RenderingOptions(rawValue: CMARK_OPT_SMART)
}


/// A node in a Markdown document.
///
/// Can represent a full Markdown document (i.e. the document's root node) or
Expand All @@ -47,13 +86,14 @@ public class Node: CustomStringConvertible {
}

public init?(filename: String) {
guard let node = cmark_parse_file(fopen(filename, "r"), 0) else { return nil }
guard let file = fopen(filename, "r"),
let node = cmark_parse_file(file, 0) else { return nil }
self.node = node
}

public init?(markdown: String) {
public init(markdown: String) {
guard let node = cmark_parse_document(markdown, markdown.utf8.count, 0) else {
return nil
fatalError("cmark_parse_document returned NULL. Should never happen.")
}
self.node = node
}
Expand All @@ -63,25 +103,25 @@ public class Node: CustomStringConvertible {
cmark_node_free(node)
}

var type: cmark_node_type {
public var type: cmark_node_type {
return cmark_node_get_type(node)
}

var listType: cmark_list_type {
public var listType: cmark_list_type {
get { return cmark_node_get_list_type(node) }
set { cmark_node_set_list_type(node, newValue) }
}

var listStart: Int {
public var listStart: Int {
get { return Int(cmark_node_get_list_start(node)) }
set { cmark_node_set_list_start(node, Int32(newValue)) }
}

var typeString: String {
return String(cString: cmark_node_get_type_string(node)!)
public var typeString: String {
return String(unsafeCString: cmark_node_get_type_string(node)) ?? ""
}

var literal: String? {
public var literal: String? {
get { return String(unsafeCString: cmark_node_get_literal(node)) }
set {
if let value = newValue {
Expand All @@ -92,12 +132,19 @@ public class Node: CustomStringConvertible {
}
}

var headerLevel: Int {
public var start: Position {
return Position(line: cmark_node_get_start_line(node), column: cmark_node_get_start_column(node))
}
public var end: Position {
return Position(line: cmark_node_get_end_line(node), column: cmark_node_get_end_column(node))
}

public var headerLevel: Int {
get { return Int(cmark_node_get_heading_level(node)) }
set { cmark_node_set_heading_level(node, Int32(newValue)) }
}

var fenceInfo: String? {
public var fenceInfo: String? {
get {
return String(unsafeCString: cmark_node_get_fence_info(node)) }
set {
Expand All @@ -109,7 +156,7 @@ public class Node: CustomStringConvertible {
}
}

var urlString: String? {
public var urlString: String? {
get { return String(unsafeCString: cmark_node_get_url(node)) }
set {
if let value = newValue {
Expand All @@ -120,7 +167,7 @@ public class Node: CustomStringConvertible {
}
}

var title: String? {
public var title: String? {
get { return String(unsafeCString: cmark_node_get_title(node)) }
set {
if let value = newValue {
Expand All @@ -131,7 +178,7 @@ public class Node: CustomStringConvertible {
}
}

var children: [Node] {
public var children: [Node] {
var result: [Node] = []

var child = cmark_node_first_child(node)
Expand All @@ -143,23 +190,25 @@ public class Node: CustomStringConvertible {
}

/// Renders the HTML representation
public var html: String {
return String(cString: cmark_render_html(node, 0))
///

public func html(options: RenderingOptions = RenderingOptions()) -> String {
return String(freeingCString: cmark_render_html(node, options.rawValue)) ?? ""
}

/// Renders the XML representation
public var xml: String {
return String(cString: cmark_render_xml(node, 0))
public func xml(options: RenderingOptions = RenderingOptions()) -> String {
return String(freeingCString: cmark_render_xml(node, options.rawValue)) ?? ""
}

/// Renders the CommonMark representation
public var commonMark: String {
return String(cString: cmark_render_commonmark(node, CMARK_OPT_DEFAULT, 80))
public func commonMark(options: RenderingOptions = RenderingOptions()) -> String {
return String(freeingCString: cmark_render_commonmark(node, options.rawValue, 80)) ?? ""
}

/// Renders the LaTeX representation
public var latex: String {
return String(cString: cmark_render_latex(node, CMARK_OPT_DEFAULT, 80))
public func latex(options: RenderingOptions = RenderingOptions()) -> String {
return String(freeingCString: cmark_render_latex(node, options.rawValue, 80)) ?? ""
}

public var description: String {
Expand Down
112 changes: 112 additions & 0 deletions Sources/CommonMark/Reduce.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
//
// Reduce.swift
// CommonMark
//
// Created by Chris Eidhof on 02.04.19.
//

import Foundation
import Ccmark

/// An algebra for a block-level element
public struct InlineAlgebra<A> {
public var text: (_ text: String) -> A
public var softBreak: A
public var lineBreak: A
public var code: (_ text: String) -> A
public var html: (_ text: String) -> A
public var emphasis: (_ children: [A]) -> A
public var strong: (_ children: [A]) -> A
public var custom: (_ literal: String) -> A
public var link: (_ children: [A], _ title: String?, _ url: String?) -> A
public var image: (_ children: [A], _ title: String?, _ url: String?) -> A
}

/// An algebra for a block-level element
public struct BlockAlgebra<A> {
public var inline: InlineAlgebra<A>
public var list: (_ items: [A], _ type: ListType) -> A
public var listItem: (_ children: [A]) -> A
public var blockQuote: (_ items: [A]) -> A
public var codeBlock: (_ text: String, _ language: String?) -> A
public var html: (_ text: String) -> A
public var paragraph: (_ text: [A]) -> A
public var heading: (_ text: [A], _ level: Int) -> A
public var custom: (_ literal: String) -> A
public var thematicBreak: A
public var document: (_ children: [A]) -> A
public var defaultValue: A
}

extension Node {
public func reduce<R>(_ b: BlockAlgebra<R>) -> R {
func r(_ node: Node) -> R {
var children: [R] { return node.children.map(r) }
var lit: String { return node.literal ?? "" }
switch node.type {
case CMARK_NODE_DOCUMENT: return b.document(children)
case CMARK_NODE_BLOCK_QUOTE: return b.blockQuote(children)
case CMARK_NODE_LIST: return b.list(children, node.listType == CMARK_BULLET_LIST ? .unordered : .ordered)
case CMARK_NODE_ITEM: return b.listItem(children)
case CMARK_NODE_CODE_BLOCK: return b.codeBlock(lit, node.fenceInfo)
case CMARK_NODE_HTML_BLOCK: return b.html(lit)
case CMARK_NODE_CUSTOM_BLOCK: return b.custom(lit)
case CMARK_NODE_PARAGRAPH: return b.paragraph(children)
case CMARK_NODE_HEADING: return b.heading(children, node.headerLevel)
case CMARK_NODE_THEMATIC_BREAK: return b.thematicBreak
case CMARK_NODE_FIRST_BLOCK: return b.defaultValue
case CMARK_NODE_LAST_BLOCK: return b.defaultValue

/* Inline */
case CMARK_NODE_TEXT: return b.inline.text(lit)
case CMARK_NODE_SOFTBREAK: return b.inline.softBreak
case CMARK_NODE_LINEBREAK: return b.inline.lineBreak
case CMARK_NODE_CODE: return b.inline.code(lit)
case CMARK_NODE_HTML_INLINE: return b.inline.html(lit)
case CMARK_NODE_CUSTOM_INLINE: return b.inline.custom(lit)
case CMARK_NODE_EMPH: return b.inline.emphasis(children)
case CMARK_NODE_STRONG: return b.inline.strong(children)
case CMARK_NODE_LINK: return b.inline.link(children, node.title, node.urlString)
case CMARK_NODE_IMAGE: return b.inline.image(children, node.title, node.urlString)
default:
return b.defaultValue
}
}
return r(self)
}
}

public protocol Monoid {
init()
static func +(lhs: Self, rhs: Self) -> Self
mutating func append(_ value: Self)
}

extension Monoid {
public mutating func append(_ value: Self) {
self = self + value
}
}

extension Array: Monoid { }

extension Array where Element: Monoid {
public func flatten() -> Element {
return reduce(into: .init(), { $0.append($1) })
}
}

extension String: Monoid { }

/// This collects all elements into a result `M`.
///
/// For example, to collect all links in a document:
///
/// var links: BlockAlgebra<[String]> = collect()
/// links.inline.link = { _, _, url in url.map { [$0] } ?? [] }
/// let allLinks = Node(markdown: string)!.reduce(links)
public func collect<M: Monoid>() -> BlockAlgebra<M> {
let inline: InlineAlgebra<M> = InlineAlgebra<M>(text: { _ in .init() }, softBreak: .init(), lineBreak: .init(), code: { _ in .init()}, html: { _ in .init() }, emphasis: { $0.flatten() }, strong: { $0.flatten() }, custom: { _ in .init() }, link: { x,_,_ in x.flatten() }, image: { x,_, _ in x.flatten() })

return BlockAlgebra<M>(inline: inline, list: { x, _ in x.flatten() }, listItem: { $0.flatten() }, blockQuote: { $0.flatten() }, codeBlock: { _,_ in .init() }, html: { _ in .init() }, paragraph: { $0.flatten() }, heading: { x,_ in x.flatten() }, custom: { _ in .init() }, thematicBreak: .init(), document: { $0.flatten() }, defaultValue: .init())
}
Loading