commonmarker 2.10.0 → 2.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,4 @@
1
+ use crate::rctree::Node;
1
2
  use comrak::arena_tree::Node as ComrakNode;
2
3
  use comrak::nodes::{
3
4
  AlertType, Ast as ComrakAst, AstNode as ComrakAstNode, ListDelimType, ListType, NodeAlert,
@@ -8,7 +9,6 @@ use comrak::nodes::{
8
9
  };
9
10
  use magnus::{function, method, scan_args, Module, Object, RHash, RModule, Symbol, Value};
10
11
  use magnus::{RArray, Ruby};
11
- use rctree::Node;
12
12
  use typed_arena::Arena;
13
13
 
14
14
  use std::cell::RefCell;
@@ -599,42 +599,50 @@ impl CommonmarkerNode {
599
599
  })
600
600
  }
601
601
 
602
+ /// Copies a comrak tree into a Ruby-visible tree, failing if any node sits
603
+ /// more than `max_nesting_depth` levels below the root.
602
604
  pub fn new_from_comrak_node<'a>(
605
+ ruby: &Ruby,
603
606
  comrak_root_node: &'a ComrakAstNode<'a>,
607
+ max_nesting_depth: usize,
604
608
  ) -> Result<CommonmarkerNode, magnus::Error> {
605
- let comrak_ast = comrak_root_node.data.clone().into_inner();
606
-
607
- fn iter_nodes<'a>(comrak_node: &'a ComrakAstNode<'a>) -> CommonmarkerNode {
608
- let comrak_node_ast = comrak_node.data.clone().into_inner();
609
- let commonmark_node = CommonmarkerNode {
610
- inner: Node::new(CommonmarkerAst {
611
- data: comrak_node_ast,
612
- }),
609
+ let commonmarker_root_node = Node::new(CommonmarkerAst {
610
+ data: comrak_root_node.data.borrow().clone(),
611
+ });
612
+
613
+ // Depth-first walk with an explicit stack instead of recursion, so that
614
+ // deeply nested input (e.g. thousands of nested blockquotes) cannot
615
+ // overflow the native stack. Each entry pairs an iterator over a comrak
616
+ // node's remaining children with the copy of that node the children get
617
+ // appended to.
618
+ let mut stack = vec![(comrak_root_node.children(), commonmarker_root_node.clone())];
619
+
620
+ loop {
621
+ // The stack holds one entry per ancestor, so its length is the
622
+ // depth of the next child produced.
623
+ let depth = stack.len();
624
+ let Some((children, parent)) = stack.last_mut() else {
625
+ break;
626
+ };
627
+ let Some(comrak_child) = children.next() else {
628
+ stack.pop();
629
+ continue;
613
630
  };
614
631
 
615
- for c in comrak_node.children() {
616
- match commonmark_node.append_child_node(&iter_nodes(c)) {
617
- Ok(_) => {}
618
- Err(e) => {
619
- panic!("cannot append node: {}", e);
620
- }
621
- }
632
+ if depth > max_nesting_depth {
633
+ return Err(crate::nesting::exceeded_error(ruby, max_nesting_depth));
622
634
  }
623
635
 
624
- commonmark_node
625
- }
626
-
627
- let commonmarker_root_node = CommonmarkerNode {
628
- inner: Node::new(CommonmarkerAst { data: comrak_ast }),
629
- };
630
-
631
- for child in comrak_root_node.children() {
632
- let new_child = iter_nodes(child);
633
-
634
- commonmarker_root_node.append_child_node(&new_child)?;
636
+ let node = Node::new(CommonmarkerAst {
637
+ data: comrak_child.data.borrow().clone(),
638
+ });
639
+ parent.append(node.clone());
640
+ stack.push((comrak_child.children(), node));
635
641
  }
636
642
 
637
- Ok(commonmarker_root_node)
643
+ Ok(CommonmarkerNode {
644
+ inner: commonmarker_root_node,
645
+ })
638
646
  }
639
647
 
640
648
  fn type_to_symbol(ruby: &Ruby, rb_self: &Self) -> Symbol {
@@ -1127,6 +1135,39 @@ impl CommonmarkerNode {
1127
1135
  }
1128
1136
  }
1129
1137
 
1138
+ /// Copies this node and its descendants into `arena` as a comrak tree so that
1139
+ /// comrak's formatters can render it.
1140
+ ///
1141
+ /// Depth-first walk with an explicit stack instead of recursion, so that
1142
+ /// deeply nested input cannot overflow the native stack. Each entry pairs an
1143
+ /// iterator over a node's remaining children with the copy of that node the
1144
+ /// children get appended to.
1145
+ fn to_comrak_node<'a>(&self, arena: &'a Arena<ComrakAstNode<'a>>) -> &'a ComrakAstNode<'a> {
1146
+ let comrak_root_node: &'a ComrakAstNode<'a> = arena.alloc(ComrakNode::new(RefCell::new(
1147
+ self.inner.borrow().data.clone(),
1148
+ )));
1149
+
1150
+ let mut stack = vec![(self.inner.children(), comrak_root_node)];
1151
+
1152
+ loop {
1153
+ let Some((children, parent)) = stack.last_mut() else {
1154
+ break;
1155
+ };
1156
+ let parent: &'a ComrakAstNode<'a> = parent;
1157
+ let Some(child) = children.next() else {
1158
+ stack.pop();
1159
+ continue;
1160
+ };
1161
+
1162
+ let comrak_node: &'a ComrakAstNode<'a> =
1163
+ arena.alloc(ComrakNode::new(RefCell::new(child.borrow().data.clone())));
1164
+ parent.append(comrak_node);
1165
+ stack.push((child.children(), comrak_node));
1166
+ }
1167
+
1168
+ comrak_root_node
1169
+ }
1170
+
1130
1171
  fn to_html(ruby: &Ruby, rb_self: &Self, args: &[Value]) -> Result<String, magnus::Error> {
1131
1172
  let args = scan_args::scan_args::<(), (), (), (), _, ()>(args)?;
1132
1173
 
@@ -1171,42 +1212,18 @@ impl CommonmarkerNode {
1171
1212
  };
1172
1213
 
1173
1214
  match syntect_adapter {
1174
- Some(ref adapter) => comrak_plugins.render.codefence_syntax_highlighter = Some(adapter),
1215
+ Some(ref adapter) => {
1216
+ comrak_plugins.render.codefence_syntax_highlighter = Some(adapter.as_ref())
1217
+ }
1175
1218
  None => comrak_plugins.render.codefence_syntax_highlighter = None,
1176
1219
  }
1177
1220
 
1178
1221
  let arena: Arena<ComrakAstNode> = Arena::new();
1179
- fn iter_nodes<'a>(
1180
- arena: &'a Arena<comrak::arena_tree::Node<'a, RefCell<ComrakAst>>>,
1181
- node: &CommonmarkerNode,
1182
- ) -> &'a comrak::arena_tree::Node<'a, std::cell::RefCell<comrak::nodes::Ast>> {
1183
- let comrak_node: &'a mut ComrakAstNode = arena.alloc(ComrakNode::new(RefCell::new(
1184
- node.inner.borrow().data.clone(),
1185
- )));
1186
-
1187
- for c in node.inner.children() {
1188
- let child = CommonmarkerNode { inner: c };
1189
- let child_node = iter_nodes(arena, &child);
1190
- comrak_node.append(child_node);
1191
- }
1192
-
1193
- comrak_node
1194
- }
1195
-
1196
- let comrak_root_node: ComrakNode<RefCell<ComrakAst>> =
1197
- ComrakNode::new(RefCell::new(rb_self.inner.borrow().data.clone()));
1198
-
1199
- for c in rb_self.inner.children() {
1200
- let child = CommonmarkerNode { inner: c };
1201
-
1202
- let new_child = iter_nodes(&arena, &child);
1203
-
1204
- comrak_root_node.append(new_child);
1205
- }
1222
+ let comrak_root_node = rb_self.to_comrak_node(&arena);
1206
1223
 
1207
1224
  let mut output = String::new();
1208
1225
  match comrak::format_html_with_plugins(
1209
- &comrak_root_node,
1226
+ comrak_root_node,
1210
1227
  &comrak_options,
1211
1228
  &mut output,
1212
1229
  &comrak_plugins,
@@ -1267,42 +1284,18 @@ impl CommonmarkerNode {
1267
1284
  };
1268
1285
 
1269
1286
  match syntect_adapter {
1270
- Some(ref adapter) => comrak_plugins.render.codefence_syntax_highlighter = Some(adapter),
1287
+ Some(ref adapter) => {
1288
+ comrak_plugins.render.codefence_syntax_highlighter = Some(adapter.as_ref())
1289
+ }
1271
1290
  None => comrak_plugins.render.codefence_syntax_highlighter = None,
1272
1291
  }
1273
1292
 
1274
1293
  let arena: Arena<ComrakAstNode> = Arena::new();
1275
- fn iter_nodes<'a>(
1276
- arena: &'a Arena<comrak::arena_tree::Node<'a, RefCell<ComrakAst>>>,
1277
- node: &CommonmarkerNode,
1278
- ) -> &'a comrak::arena_tree::Node<'a, std::cell::RefCell<comrak::nodes::Ast>> {
1279
- let comrak_node: &'a mut ComrakAstNode = arena.alloc(ComrakNode::new(RefCell::new(
1280
- node.inner.borrow().data.clone(),
1281
- )));
1282
-
1283
- for c in node.inner.children() {
1284
- let child = CommonmarkerNode { inner: c };
1285
- let child_node = iter_nodes(arena, &child);
1286
- comrak_node.append(child_node);
1287
- }
1288
-
1289
- comrak_node
1290
- }
1291
-
1292
- let comrak_root_node: ComrakNode<RefCell<ComrakAst>> =
1293
- ComrakNode::new(RefCell::new(rb_self.inner.borrow().data.clone()));
1294
-
1295
- for c in rb_self.inner.children() {
1296
- let child = CommonmarkerNode { inner: c };
1297
-
1298
- let new_child = iter_nodes(&arena, &child);
1299
-
1300
- comrak_root_node.append(new_child);
1301
- }
1294
+ let comrak_root_node = rb_self.to_comrak_node(&arena);
1302
1295
 
1303
1296
  let mut output = String::new();
1304
1297
  match comrak::format_commonmark_with_plugins(
1305
- &comrak_root_node,
1298
+ comrak_root_node,
1306
1299
  &comrak_options,
1307
1300
  &mut output,
1308
1301
  &comrak_plugins,
@@ -1311,7 +1304,7 @@ impl CommonmarkerNode {
1311
1304
  Err(e) => {
1312
1305
  return Err(magnus::Error::new(
1313
1306
  ruby.exception_runtime_error(),
1314
- format!("cannot convert into html: {}", e),
1307
+ format!("cannot convert into commonmark: {}", e),
1315
1308
  ));
1316
1309
  }
1317
1310
  }
@@ -117,7 +117,6 @@ pub fn iterate_render_options(comrak_options: &mut comrak::options::Render, opti
117
117
  }
118
118
 
119
119
  const EXTENSION_STRIKETHROUGH: &str = "strikethrough";
120
- const EXTENSION_TAGFILTER: &str = "tagfilter";
121
120
  const EXTENSION_TABLE: &str = "table";
122
121
  const EXTENSION_AUTOLINK: &str = "autolink";
123
122
  const EXTENSION_TASKLIST: &str = "tasklist";
@@ -156,9 +155,6 @@ pub fn iterate_extension_options(
156
155
  Cow::Borrowed(EXTENSION_STRIKETHROUGH) => {
157
156
  comrak_options.strikethrough = TryConvert::try_convert(value)?;
158
157
  }
159
- Cow::Borrowed(EXTENSION_TAGFILTER) => {
160
- comrak_options.tagfilter = TryConvert::try_convert(value)?;
161
- }
162
158
  Cow::Borrowed(EXTENSION_TABLE) => {
163
159
  comrak_options.table = TryConvert::try_convert(value)?;
164
160
  }
@@ -1,4 +1,5 @@
1
1
  use std::path::PathBuf;
2
+ use std::sync::{Arc, OnceLock};
2
3
 
3
4
  use comrak::plugins::syntect::{SyntectAdapter, SyntectAdapterBuilder};
4
5
 
@@ -11,7 +12,7 @@ use crate::EMPTY_STR;
11
12
  pub fn construct_syntax_highlighter_from_plugin(
12
13
  ruby: &Ruby,
13
14
  rb_plugins: Option<RHash>,
14
- ) -> Result<Option<SyntectAdapter>, magnus::Error> {
15
+ ) -> Result<Option<Arc<SyntectAdapter>>, magnus::Error> {
15
16
  match rb_plugins {
16
17
  None => Ok(None),
17
18
  Some(rb_plugins) => {
@@ -34,8 +35,11 @@ pub fn construct_syntax_highlighter_from_plugin(
34
35
  Some(theme) => {
35
36
  if theme.is_empty() {
36
37
  // no theme? uss css classes
37
- adapter = SyntectAdapter::new(None);
38
- Ok(Some(adapter))
38
+ // Avoid reloading syntax sets and recompiling regexes for every CSS-mode render.
39
+ static CSS_ADAPTER: OnceLock<Arc<SyntectAdapter>> = OnceLock::new();
40
+ Ok(Some(Arc::clone(
41
+ CSS_ADAPTER.get_or_init(|| Arc::new(SyntectAdapter::new(None))),
42
+ )))
39
43
  } else {
40
44
  let path = match rb_plugins
41
45
  .get(ruby.to_symbol(super::SYNTAX_HIGHLIGHTER_PLUGIN))
@@ -80,7 +84,7 @@ pub fn construct_syntax_highlighter_from_plugin(
80
84
 
81
85
  adapter = builder.theme_set(ts).theme(&theme).build();
82
86
 
83
- Ok(Some(adapter))
87
+ Ok(Some(Arc::new(adapter)))
84
88
  } else {
85
89
  // no path? default theme lookup
86
90
  ThemeSet::load_defaults()
@@ -93,7 +97,7 @@ pub fn construct_syntax_highlighter_from_plugin(
93
97
  )
94
98
  })?;
95
99
  adapter = SyntectAdapter::new(Some(&theme));
96
- Ok(Some(adapter))
100
+ Ok(Some(Arc::new(adapter)))
97
101
  }
98
102
  }
99
103
  }