Skip to content
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
2 changes: 1 addition & 1 deletion cmark-gfm
10 changes: 10 additions & 0 deletions ext/markly/cmark-gfm-extension_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,10 @@ typedef void (*cmark_opaque_free_func) (cmark_syntax_extension *extension,
cmark_mem *mem,
cmark_node *node);

typedef void (*cmark_opaque_copy_func)(cmark_syntax_extension *extension,
cmark_mem *mem, cmark_node *node,
cmark_node *source);

/** Free a cmark_syntax_extension.
*/
CMARK_GFM_EXPORT
Expand Down Expand Up @@ -406,6 +410,12 @@ CMARK_GFM_EXPORT
void cmark_syntax_extension_set_opaque_free_func(cmark_syntax_extension *extension,
cmark_opaque_free_func func);

/** See the documentation for 'cmark_syntax_extension'
*/
CMARK_GFM_EXPORT
void cmark_syntax_extension_set_opaque_copy_func(
cmark_syntax_extension *extension, cmark_opaque_copy_func func);

/** See the documentation for 'cmark_syntax_extension'
*/
CMARK_GFM_EXPORT
Expand Down
6 changes: 6 additions & 0 deletions ext/markly/cmark-gfm.h
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,12 @@ CMARK_GFM_EXPORT cmark_node *cmark_node_new_with_mem_and_ext(cmark_node_type typ
cmark_mem *mem,
cmark_syntax_extension *extension);

/** Creates an independent deep copy of 'node' and all its children.
* User data is not copied. Returns NULL when extension-specific node data
* cannot be copied.
*/
CMARK_GFM_EXPORT cmark_node *cmark_node_clone(cmark_node *node);

/** Frees the memory allocated for a node and any children.
*/
CMARK_GFM_EXPORT void cmark_node_free(cmark_node *node);
Expand Down
24 changes: 24 additions & 0 deletions ext/markly/extensions/table.c
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,29 @@ static void opaque_free(cmark_syntax_extension *self, cmark_mem *mem, cmark_node
}
}

static void opaque_copy(cmark_syntax_extension *self, cmark_mem *mem,
cmark_node *node, cmark_node *source) {
if (node->type == CMARK_NODE_TABLE) {
node_table *table = (node_table *)node->as.opaque;
node_table *source_table = (node_table *)source->as.opaque;

table->n_columns = source_table->n_columns;
table->n_rows = source_table->n_rows;
table->n_nonempty_cells = source_table->n_nonempty_cells;

if (source_table->alignments) {
table->alignments = mem->calloc(source_table->n_columns, sizeof(uint8_t));
memcpy(table->alignments, source_table->alignments,
source_table->n_columns * sizeof(uint8_t));
}
} else if (node->type == CMARK_NODE_TABLE_ROW) {
*(node_table_row *)node->as.opaque = *(node_table_row *)source->as.opaque;
} else if (node->type == CMARK_NODE_TABLE_CELL) {
mem->free(node->as.opaque);
node->as.cell_index = source->as.cell_index;
}
}

static int escape(cmark_syntax_extension *self, cmark_node *node, int c) {
return
node->type != CMARK_NODE_TABLE &&
Expand All @@ -867,6 +890,7 @@ cmark_syntax_extension *create_table_extension(void) {
cmark_syntax_extension_set_html_render_func(self, html_render);
cmark_syntax_extension_set_opaque_alloc_func(self, opaque_alloc);
cmark_syntax_extension_set_opaque_free_func(self, opaque_free);
cmark_syntax_extension_set_opaque_copy_func(self, opaque_copy);
cmark_syntax_extension_set_commonmark_escape_func(self, escape);
CMARK_NODE_TABLE = cmark_syntax_extension_add_node(0);
CMARK_NODE_TABLE_ROW = cmark_syntax_extension_add_node(0);
Expand Down
5 changes: 3 additions & 2 deletions ext/markly/html.c
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ static int S_render_node(cmark_html_renderer *renderer, cmark_node *node,

case CMARK_NODE_PARAGRAPH:
parent = cmark_node_parent(node);
grandparent = cmark_node_parent(parent);
grandparent = parent ? cmark_node_parent(parent) : NULL;
if (grandparent != NULL && grandparent->type == CMARK_NODE_LIST) {
tight = grandparent->as.list.tight;
} else {
Expand All @@ -316,7 +316,8 @@ static int S_render_node(cmark_html_renderer *renderer, cmark_node *node,
cmark_html_render_sourcepos(node, html, options);
cmark_strbuf_putc(html, '>');
} else {
if (parent->type == CMARK_NODE_FOOTNOTE_DEFINITION && node->next == NULL) {
if (parent && parent->type == CMARK_NODE_FOOTNOTE_DEFINITION &&
node->next == NULL) {
cmark_strbuf_putc(html, ' ');
S_put_footnote_backref(renderer, html, parent);
}
Expand Down
16 changes: 16 additions & 0 deletions ext/markly/markly.c
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,21 @@ static VALUE Markly_Node_new(VALUE self, VALUE type) {
return Markly_Node_wrap(node);
}

/*
* Duplicate the current node and all its children.
*/
static VALUE Markly_Node_duplicate(VALUE self) {
cmark_node *node;
TypedData_Get_Struct(self, cmark_node, &Markly_Node_Type, node);

cmark_node *copy = cmark_node_clone(node);
if (copy == NULL) {
rb_raise(Markly_Error, "could not duplicate node");
}

return Markly_Node_wrap(copy);
}

static VALUE Markly_Node_replace(VALUE self, VALUE other) {
cmark_node *current_node = NULL, *replacement_node = NULL;

Expand Down Expand Up @@ -1273,6 +1288,7 @@ static void Init_Markly_Node(VALUE Markly) {
rb_undef_alloc_func(Markly_Node);
rb_define_singleton_method(Markly_Node, "new", Markly_Node_new, 1);
Markly_Node_Fence = rb_struct_define_under(Markly_Node, "Fence", "character", "length", "indent", NULL);
rb_define_method(Markly_Node, "_dup", Markly_Node_duplicate, 0);

rb_define_method(Markly_Node, "replace", Markly_Node_replace, 1);

Expand Down
138 changes: 138 additions & 0 deletions ext/markly/node.c
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,144 @@ cmark_node *cmark_node_new(cmark_node_type type) {
return cmark_node_new_with_ext(type, NULL);
}

static cmark_chunk S_clone_chunk(cmark_mem *mem, const cmark_chunk *source) {
cmark_chunk clone = {NULL, source->len, 1};
clone.data = (unsigned char *)mem->calloc(source->len + 1, 1);
if (source->len > 0) {
memcpy(clone.data, source->data, source->len);
}
return clone;
}

static cmark_node *S_clone_node(cmark_node *node) {
cmark_mem *mem = NODE_MEM(node);
cmark_node *clone = cmark_node_new_with_mem_and_ext(
(cmark_node_type)node->type, mem, node->extension);

if (!clone) {
return NULL;
}

cmark_strbuf_set(&clone->content, node->content.ptr, node->content.size);
clone->start_line = node->start_line;
clone->start_column = node->start_column;
clone->end_line = node->end_line;
clone->end_column = node->end_column;
clone->internal_offset = node->internal_offset;
clone->flags = node->flags;
clone->footnote = node->footnote;

switch (node->type) {
case CMARK_NODE_HEADING:
clone->as.heading = node->as.heading;
break;
case CMARK_NODE_LIST:
case CMARK_NODE_ITEM:
clone->as.list = node->as.list;
break;
case CMARK_NODE_CODE_BLOCK:
case CMARK_NODE_FRONT_MATTER:
case CMARK_NODE_CODE:
clone->as.code = node->as.code;
clone->as.code.info = S_clone_chunk(mem, &node->as.code.info);
clone->as.code.literal = S_clone_chunk(mem, &node->as.code.literal);
break;
case CMARK_NODE_TEXT:
case CMARK_NODE_HTML_INLINE:
case CMARK_NODE_HTML_BLOCK:
case CMARK_NODE_FOOTNOTE_REFERENCE:
case CMARK_NODE_FOOTNOTE_DEFINITION:
clone->as.literal = S_clone_chunk(mem, &node->as.literal);
break;
case CMARK_NODE_LINK:
case CMARK_NODE_IMAGE:
clone->as.link.url = S_clone_chunk(mem, &node->as.link.url);
clone->as.link.title = S_clone_chunk(mem, &node->as.link.title);
break;
case CMARK_NODE_CUSTOM_BLOCK:
case CMARK_NODE_CUSTOM_INLINE:
clone->as.custom.on_enter = S_clone_chunk(mem, &node->as.custom.on_enter);
clone->as.custom.on_exit = S_clone_chunk(mem, &node->as.custom.on_exit);
break;
default:
break;
}

if (node->extension && node->extension->opaque_alloc_func) {
if (!node->extension->opaque_copy_func) {
cmark_node_free(clone);
return NULL;
}

node->extension->opaque_copy_func(node->extension, mem, clone, node);
}

for (cmark_node *child = node->first_child; child; child = child->next) {
cmark_node *child_clone = S_clone_node(child);
if (!child_clone) {
cmark_node_free(clone);
return NULL;
}

if (!cmark_node_append_child(clone, child_clone)) {
cmark_node_free(child_clone);
cmark_node_free(clone);
return NULL;
}
}

return clone;
}

static cmark_node *S_find_clone(cmark_node *source, cmark_node *clone,
cmark_node *target) {
if (source == target) {
return clone;
}

cmark_node *source_child = source->first_child;
cmark_node *clone_child = clone->first_child;
while (source_child && clone_child) {
cmark_node *result = S_find_clone(source_child, clone_child, target);
if (result) {
return result;
}
source_child = source_child->next;
clone_child = clone_child->next;
}

return NULL;
}

static void S_clone_footnote_links(cmark_node *source, cmark_node *clone,
cmark_node *source_root,
cmark_node *clone_root) {
if (source->parent_footnote_def) {
clone->parent_footnote_def =
S_find_clone(source_root, clone_root, source->parent_footnote_def);
}

cmark_node *source_child = source->first_child;
cmark_node *clone_child = clone->first_child;
while (source_child && clone_child) {
S_clone_footnote_links(source_child, clone_child, source_root, clone_root);
source_child = source_child->next;
clone_child = clone_child->next;
}
}

cmark_node *cmark_node_clone(cmark_node *node) {
if (!node) {
return NULL;
}

cmark_node *clone = S_clone_node(node);
if (clone) {
S_clone_footnote_links(node, clone, node, clone);
}
return clone;
}

static void free_node_as(cmark_node *node) {
switch (node->type) {
case CMARK_NODE_CODE_BLOCK:
Expand Down
5 changes: 5 additions & 0 deletions ext/markly/syntax_extension.c
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,11 @@ void cmark_syntax_extension_set_opaque_free_func(cmark_syntax_extension *extensi
extension->opaque_free_func = func;
}

void cmark_syntax_extension_set_opaque_copy_func(
cmark_syntax_extension *extension, cmark_opaque_copy_func func) {
extension->opaque_copy_func = func;
}

void cmark_syntax_extension_set_commonmark_escape_func(cmark_syntax_extension *extension,
cmark_commonmark_escape_func func) {
extension->commonmark_escape_func = func;
Expand Down
1 change: 1 addition & 0 deletions ext/markly/syntax_extension.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ struct cmark_syntax_extension {
cmark_postprocess_func postprocess_func;
cmark_opaque_alloc_func opaque_alloc_func;
cmark_opaque_free_func opaque_free_func;
cmark_opaque_copy_func opaque_copy_func;
cmark_commonmark_escape_func commonmark_escape_func;
};

Expand Down
10 changes: 1 addition & 9 deletions lib/markly/node.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,7 @@ class Node
#
# @returns [Markly::Node] The duplicated node tree.
def dup
# This is a bit crazy, but it's the best I can come up with right now:
node = Markly.parse(self.to_markdown)

# If we aren't duplicating a document, we return `first_child` as the root will be a document node:
if self.type == :document
return node
else
return node.first_child
end
_dup
end

# Walk the node tree recursively.
Expand Down
4 changes: 4 additions & 0 deletions releases.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Releases

## Unreleased

- Preserve complete node and extension metadata when duplicating node trees.

## v0.17.0

- Add opt-in language prefixes for inline code spans with `Markly::INLINE_CODE_INFO`, expose code metadata through `Node#code_info`, and provide `Node#code_language` as a convenient language accessor.
Expand Down
8 changes: 8 additions & 0 deletions test/markly/node.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@
expect(dup.to_html).to be == document.first_child.to_html
expect(dup).not.to be_equal(document.first_child)
end

it "preserves extension metadata" do
document = Markly.parse("| Left | Right |\n| :--- | ---: |\n| A | B |\n", extensions: [:table])
dup = document.dup

expect(dup.first_child.table_alignments).to be == [:left, :right]
expect(dup.to_html(extensions: [:table])).to be == document.to_html(extensions: [:table])
end
end

with "#type" do
Expand Down
8 changes: 8 additions & 0 deletions test/markly/node/inline_code_info.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@
expect(document.to_commonmark).to be == "ruby:`Object.new`\n"
end

it "preserves the language when duplicated" do
copy = document.dup
code = copy.first_child.first_child

expect(code.code_info).to be == "ruby"
expect(copy.to_html).to be == document.to_html
end

it "can update and clear the language" do
code.code_info = "c++"
expect(code.code_info).to be == "c++"
Expand Down
Loading