Skip to content
This repository was archived by the owner on Oct 17, 2021. It is now read-only.
Merged
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
5 changes: 5 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Added `replace(child:with:)` methods for container nodes.
#26 by @mattt.

## [0.5.0] - 2021-01-14

### Fixed
Expand Down
177 changes: 145 additions & 32 deletions Sources/CommonMark/Supporting Types/Children.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,22 +36,6 @@ struct CMarkNodeChildIterator: IteratorProtocol {

// MARK: -

fileprivate func add<Child: Node>(_ child: Child, with operation: () -> Int32) -> Bool {
let status = operation()
switch status {
case 1:
child.managed = false
return true
case 0:
return false
default:
assertionFailure("unexpected status code: \(status)")
return false
}
}

// MARK: -

public protocol ContainerOfBlocks: Node {
var children: [Block & Node] { get }
}
Expand Down Expand Up @@ -92,7 +76,11 @@ extension ContainerOfBlocks {
*/
@discardableResult
public func prepend(child: Block & Node) -> Bool {
return add(child) { cmark_node_prepend_child(cmark_node, child.cmark_node) }
guard cmark_node_prepend_child(cmark_node, child.cmark_node) == 1 else { return false }

child.managed = false

return true
}

/**
Expand All @@ -104,7 +92,11 @@ extension ContainerOfBlocks {
*/
@discardableResult
public func append(child: Block & Node) -> Bool {
return add(child) { cmark_node_append_child(cmark_node, child.cmark_node) }
guard cmark_node_append_child(cmark_node, child.cmark_node) == 1 else { return false }

child.managed = false

return true
}

/**
Expand All @@ -117,7 +109,11 @@ extension ContainerOfBlocks {
*/
@discardableResult
public func insert(child: Block & Node, before sibling: Block & Node) -> Bool {
return add(child) { cmark_node_insert_before(sibling.cmark_node, child.cmark_node) }
guard cmark_node_insert_before(sibling.cmark_node, child.cmark_node) == 1 else { return false }

child.managed = false

return true
}

/**
Expand All @@ -130,7 +126,29 @@ extension ContainerOfBlocks {
*/
@discardableResult
public func insert(child: Block & Node, after sibling: Block & Node) -> Bool {
return add(child) { cmark_node_insert_after(sibling.cmark_node, child.cmark_node) }
guard cmark_node_insert_after(sibling.cmark_node, child.cmark_node) == 1 else { return false }

child.managed = false

return true
}

/**
Replaces a block with the specified node.

- Parameters:
- child: The block to replace.
- replacement: The block to replace the existing block.
- Returns: `true` if successful, otherwise `false`.
*/
@discardableResult
public func replace(child: Block & Node, with replacement: Block & Node) -> Bool {
guard cmark_node_replace(child.cmark_node, replacement.cmark_node) == 1 else { return false }

replacement.managed = false
child.managed = true

return true
}

/**
Expand All @@ -143,6 +161,7 @@ extension ContainerOfBlocks {
@discardableResult
public func remove(child: Block & Node) -> Bool {
guard child.parent == self else { return false }

child.unlink()

return true
Expand Down Expand Up @@ -214,7 +233,11 @@ extension ContainerOfInlineElements {
*/
@discardableResult
public func prepend(child: Inline & Node) -> Bool {
return add(child) { cmark_node_prepend_child(cmark_node, child.cmark_node) }
guard cmark_node_prepend_child(cmark_node, child.cmark_node) == 1 else { return false }

child.managed = false

return true
}

/**
Expand All @@ -226,7 +249,11 @@ extension ContainerOfInlineElements {
*/
@discardableResult
public func append(child: Inline & Node) -> Bool {
return add(child) { cmark_node_append_child(cmark_node, child.cmark_node) }
guard cmark_node_append_child(cmark_node, child.cmark_node) == 1 else { return false }

child.managed = false

return true
}

/**
Expand All @@ -239,7 +266,11 @@ extension ContainerOfInlineElements {
*/
@discardableResult
public func insert(child: Inline & Node, before sibling: Inline & Node) -> Bool {
return add(child) { cmark_node_insert_before(sibling.cmark_node, child.cmark_node) }
guard cmark_node_insert_before(sibling.cmark_node, child.cmark_node) == 1 else { return false }

child.managed = false

return true
}

/**
Expand All @@ -252,7 +283,29 @@ extension ContainerOfInlineElements {
*/
@discardableResult
public func insert(child: Inline & Node, after sibling: Inline & Node) -> Bool {
return add(child) { cmark_node_insert_after(sibling.cmark_node, child.cmark_node) }
guard cmark_node_insert_after(sibling.cmark_node, child.cmark_node) == 1 else { return false }

child.managed = false

return true
}

/**
Replaces an inline element with the specified node.

- Parameters:
- child: The inline element to replace.
- replacement: The inline element to replace the existing inline element.
- Returns: `true` if successful, otherwise `false`.
*/
@discardableResult
public func replace(child: Inline & Node, with replacement: Inline & Node) -> Bool {
guard cmark_node_replace(child.cmark_node, replacement.cmark_node) == 1 else { return false }

replacement.managed = false
child.managed = true

return true
}

/**
Expand All @@ -265,6 +318,7 @@ extension ContainerOfInlineElements {
@discardableResult
public func remove(child: Inline & Node) -> Bool {
guard child.parent == self else { return false }

child.unlink()

return true
Expand Down Expand Up @@ -323,7 +377,11 @@ extension List {
*/
@discardableResult
public func prepend(child: Item) -> Bool {
return add(child) { cmark_node_prepend_child(cmark_node, child.cmark_node) }
guard cmark_node_prepend_child(cmark_node, child.cmark_node) == 1 else { return false }

child.managed = false

return true
}

/**
Expand All @@ -335,7 +393,11 @@ extension List {
*/
@discardableResult
public func append(child: Item) -> Bool {
return add(child) { cmark_node_append_child(cmark_node, child.cmark_node) }
guard cmark_node_append_child(cmark_node, child.cmark_node) == 1 else { return false }

child.managed = false

return true
}

/**
Expand All @@ -348,7 +410,11 @@ extension List {
*/
@discardableResult
public func insert(child: Item, before sibling: Item) -> Bool {
return add(child) { cmark_node_insert_before(sibling.cmark_node, child.cmark_node) }
guard cmark_node_insert_before(sibling.cmark_node, child.cmark_node) == 1 else { return false }

child.managed = false

return true
}

/**
Expand All @@ -361,7 +427,29 @@ extension List {
*/
@discardableResult
public func insert(child: Item, after sibling: Item) -> Bool {
return add(child) { cmark_node_insert_after(sibling.cmark_node, child.cmark_node) }
guard cmark_node_insert_after(sibling.cmark_node, child.cmark_node) == 1 else { return false }

child.managed = false

return true
}

/**
Replaces an item with the specified node.

- Parameters:
- child: The item to replace.
- replacement: The item to replace the existing item.
- Returns: `true` if successful, otherwise `false`.
*/
@discardableResult
public func replace(child: Item, with replacement: Item) -> Bool {
guard cmark_node_replace(child.cmark_node, replacement.cmark_node) == 1 else { return false }

replacement.managed = false
child.managed = true

return true
}

/**
Expand Down Expand Up @@ -432,7 +520,9 @@ extension List.Item {
*/
@discardableResult
public func prepend(child: Node) -> Bool {
return add(child) { cmark_node_prepend_child(cmark_node, child.cmark_node) }
guard cmark_node_prepend_child(cmark_node, child.cmark_node) == 1 else { return false }
child.managed = false
return true
}

/**
Expand All @@ -444,7 +534,11 @@ extension List.Item {
*/
@discardableResult
public func append(child: Node) -> Bool {
return add(child) { cmark_node_append_child(cmark_node, child.cmark_node) }
guard cmark_node_append_child(cmark_node, child.cmark_node) == 1 else { return false }

child.managed = false

return true
}

/**
Expand All @@ -457,7 +551,9 @@ extension List.Item {
*/
@discardableResult
public func insert(child: Node, before sibling: Node) -> Bool {
return add(child) { cmark_node_insert_before(sibling.cmark_node, child.cmark_node) }
guard cmark_node_insert_before(sibling.cmark_node, child.cmark_node) == 1 else { return false }
child.managed = false
return true
}

/**
Expand All @@ -470,7 +566,24 @@ extension List.Item {
*/
@discardableResult
public func insert(child: Node, after sibling: Node) -> Bool {
return add(child) { cmark_node_insert_after(sibling.cmark_node, child.cmark_node) }
guard cmark_node_insert_after(sibling.cmark_node, child.cmark_node) == 1 else { return false }
child.managed = false
return true
}

/**
Replaces a node with a specified node.

- Parameters:
- child: The node to replace.
- replacement: The node to replace the existing node.
- Returns: `true` if successful, otherwise `false`.
*/
@discardableResult
public func replace(child: Node, with replacement: Node) -> Bool {
guard cmark_node_replace(child.cmark_node, replacement.cmark_node) == 1 else { return false }
replacement.managed = false
return true
}

/**
Expand Down
24 changes: 22 additions & 2 deletions Tests/CommonMarkTests/ContainerManipulationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,33 @@ final class ContainerManipulationTests: XCTestCase {
political or other opinion, national or social origin, property, birth or other status.
"""#)
let wasNewParagraphInserted = document.insert(child: newParagraph, after: paragraph)

XCTAssertTrue(wasNewParagraphInserted)
XCTAssertEqual(document.children.count, 4)
XCTAssertEqual(newParagraph, document.children[3])

let replacementParagraph = Paragraph(text:
#"""
Everyone is entitled to all the rights and freedoms set forth in this Declaration,
without distinction of any kind, such as
race,
colour,
sex,
language,
religion,
political or other opinion,
national or social origin,
property,
birth or
other status.
"""#
)
let wasNewParagraphReplaced = document.replace(child: newParagraph, with: replacementParagraph)
XCTAssertTrue(wasNewParagraphReplaced)
XCTAssertEqual(document.children.count, 4)
XCTAssertEqual(replacementParagraph, document.children[3])

let newSubheading = Heading(level: 2, text: "Article 2.")
let wasSubheadingInserted = document.insert(child: newSubheading, before: newParagraph)
let wasSubheadingInserted = document.insert(child: newSubheading, before: replacementParagraph)
XCTAssertTrue(wasSubheadingInserted)
XCTAssertEqual(document.children.count, 5)
XCTAssertEqual(newSubheading, document.children[3])
Expand Down