markly 0.18.0 → 0.19.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a6df3d157ca441e13f840de336bf04408669aae72ff791d35be091ee008da87c
4
- data.tar.gz: 682356cc37b69d2a486624ffd0e0c02f75a717b07f32c0a41d838e2032730223
3
+ metadata.gz: e8acce4b2ae6c9e79d70bb21548ece0ec72bf0ef3e7cd77aaae270cc408daa2c
4
+ data.tar.gz: d48929fd7d802c68ad1ef18bb186f3f3b349d147be7b9c64dddb853f361eedac
5
5
  SHA512:
6
- metadata.gz: 70315346cac721d22cbc67c0efaee8bd6a035198fec894388a827136c3710d60844c24e29a26a4374a899af01212618412d929acfd840db03ce887f79e2b80ef
7
- data.tar.gz: 895682bb5cd75d95306c2e60b9bb446b4459ade9800d8a29e5ecd13b4fb9f8e1caa33518ac1d3f6779143af9f213d50a0c17460ded2fc94eea57872e7aba9c26
6
+ metadata.gz: e7f9545009613d2e140762a337babf3ddcac13520b585d8fa07ec4ab6c90a7248a4be8df2998afbdb9a4428c315bc969426f05d521219aba671dca05371f1e1c
7
+ data.tar.gz: 460d177e3361f7416b7d20ab48e900f9635c83c48c132bf489a025a50bfd532c678e0b9e7467d1e7441ef362030e0c96bd5f13a8e1c0d39f9746cd096934c4e8
checksums.yaml.gz.sig CHANGED
Binary file
@@ -0,0 +1,232 @@
1
+ # Extensions
2
+
3
+ This guide explains how to enable and use Markly's Markdown extensions.
4
+
5
+ ## Choosing a Markdown Dialect
6
+
7
+ Markly parses standard CommonMark by default. Extensions change which syntax is
8
+ recognized, so applications should enable them explicitly and consistently.
9
+ This is especially important when several components parse or render the same
10
+ document.
11
+
12
+ Pass extension names as symbols using the `extensions:` keyword:
13
+
14
+ ``` ruby
15
+ EXTENSIONS = %i[table tasklist strikethrough autolink].freeze
16
+
17
+ html = Markly.render_html(markdown, extensions: EXTENSIONS)
18
+ ```
19
+
20
+ When parsing and rendering separately, use the same configuration at both
21
+ boundaries:
22
+
23
+ ``` ruby
24
+ document = Markly.parse(markdown, extensions: EXTENSIONS)
25
+ html = document.to_html(extensions: EXTENSIONS)
26
+ ```
27
+
28
+ Extension names must be symbols. A string such as `"table"` raises `TypeError`,
29
+ and an unknown symbol raises `ArgumentError`.
30
+
31
+ ## GitHub Flavored Markdown Extensions
32
+
33
+ Markly includes the five syntax extensions defined by GitHub Flavored Markdown.
34
+ None are enabled by default.
35
+
36
+ ### Tables
37
+
38
+ The `:table` extension recognizes a paragraph followed by a delimiter row as a
39
+ table. Colons in the delimiter row specify column alignment:
40
+
41
+ ``` ruby
42
+ markdown = <<~MARKDOWN
43
+ | Package | Status |
44
+ | :--- | ---: |
45
+ | Markly | Ready |
46
+ MARKDOWN
47
+
48
+ document = Markly.parse(markdown, extensions: [:table])
49
+ table = document.first_child
50
+
51
+ table.type
52
+ # => :table
53
+
54
+ table.table_alignments
55
+ # => [:left, :right]
56
+ ```
57
+
58
+ The table AST contains `:table`, `:table_header`, `:table_row`, and
59
+ `:table_cell` nodes. By default, HTML rendering uses `align` attributes for
60
+ aligned cells. Pass `Markly::TABLE_PREFER_STYLE_ATTRIBUTES` when rendering to
61
+ use `style="text-align: ..."` instead:
62
+
63
+ ``` ruby
64
+ document.to_html(
65
+ flags: Markly::TABLE_PREFER_STYLE_ATTRIBUTES,
66
+ extensions: [:table],
67
+ )
68
+ ```
69
+
70
+ ### Task Lists
71
+
72
+ The `:tasklist` extension recognizes checked and unchecked list items:
73
+
74
+ ``` markdown
75
+ - [x] Parse Markdown
76
+ - [ ] Render HTML
77
+ ```
78
+
79
+ Task-list state is available on the list-item node and can be changed before
80
+ rendering:
81
+
82
+ ``` ruby
83
+ document = Markly.parse("- [x] Parse Markdown", extensions: [:tasklist])
84
+ item = document.first_child.first_child
85
+
86
+ item.tasklist_item_checked?
87
+ # => true
88
+
89
+ item.tasklist_item_checked = false
90
+ ```
91
+
92
+ ### Strikethrough
93
+
94
+ The `:strikethrough` extension renders strikethrough text using `<del>`:
95
+
96
+ ``` ruby
97
+ Markly.render_html("~~obsolete~~", extensions: [:strikethrough])
98
+ # => "<p><del>obsolete</del></p>\n"
99
+ ```
100
+
101
+ Pass `Markly::STRIKETHROUGH_DOUBLE_TILDE` while parsing to accept only spans
102
+ surrounded by exactly two tildes. This is useful when compatibility requires
103
+ single or longer runs of tildes to remain literal:
104
+
105
+ ``` ruby
106
+ Markly.render_html(
107
+ "~one~ ~~two~~ ~~~three~~~",
108
+ flags: Markly::STRIKETHROUGH_DOUBLE_TILDE,
109
+ extensions: [:strikethrough],
110
+ )
111
+ # => "<p>~one~ <del>two</del> ~~~three~~~</p>\n"
112
+ ```
113
+
114
+ ### Autolinks
115
+
116
+ The `:autolink` extension turns plain URLs and email addresses into links
117
+ without requiring angle brackets or Markdown link syntax:
118
+
119
+ ``` ruby
120
+ Markly.render_html(
121
+ "Visit https://socketry.io or email hello@example.com.",
122
+ extensions: [:autolink],
123
+ )
124
+ ```
125
+
126
+ Use this extension when rendering prose where authors expect GitHub-style link
127
+ detection. Leave it disabled when plain URL-like text must remain unchanged.
128
+
129
+ ### Tag Filtering
130
+
131
+ The `:tagfilter` extension escapes the raw HTML tags prohibited by the GitHub
132
+ Flavored Markdown specification. It is typically combined with
133
+ `Markly::UNSAFE`, which otherwise permits raw HTML:
134
+
135
+ ``` ruby
136
+ Markly.render_html(
137
+ "<script>alert('no')</script><strong>yes</strong>",
138
+ flags: Markly::UNSAFE,
139
+ extensions: [:tagfilter],
140
+ )
141
+ ```
142
+
143
+ Tag filtering is not a general-purpose HTML sanitizer. It filters the specific
144
+ tag names required by the GFM specification, while other raw HTML remains
145
+ available when `Markly::UNSAFE` is enabled. Sanitize untrusted HTML separately
146
+ when the application requires a stricter policy.
147
+
148
+ ## Markly Syntax Features
149
+
150
+ Markly also provides syntax features that are not named GFM extensions. Enable
151
+ these with parser flags rather than adding names to `extensions:`.
152
+
153
+ ### Front Matter
154
+
155
+ `Markly::FRONT_MATTER` recognizes a `---` delimited block only at the beginning
156
+ of a document:
157
+
158
+ ``` ruby
159
+ markdown = <<~MARKDOWN
160
+ --- yaml
161
+ title: Extensions
162
+ ---
163
+ # Document
164
+ MARKDOWN
165
+
166
+ document = Markly.parse(markdown, flags: Markly::FRONT_MATTER)
167
+ front_matter = document.first_child
168
+
169
+ front_matter.type
170
+ # => :front_matter
171
+
172
+ front_matter.string_content
173
+ # => "title: Extensions\n"
174
+
175
+ front_matter.code_info
176
+ # => "yaml"
177
+ ```
178
+
179
+ Front matter is omitted from HTML and plain-text output. Markly exposes its raw
180
+ contents but does not interpret YAML, TOML, or any other format. Treat the
181
+ contents as untrusted input and parse them according to the application's own
182
+ policy.
183
+
184
+ The closing delimiter must be an exact `---` line. If it is missing, the rest
185
+ of the document belongs to the front-matter node.
186
+
187
+ ### Inline Code Information
188
+
189
+ `Markly::INLINE_CODE_INFO` recognizes a language prefix immediately before an
190
+ inline code span:
191
+
192
+ ``` ruby
193
+ document = Markly.parse(
194
+ "ruby:`Object.new`",
195
+ flags: Markly::INLINE_CODE_INFO,
196
+ )
197
+ code = document.first_child.first_child
198
+
199
+ code.code_info
200
+ # => "ruby"
201
+
202
+ code.code_language
203
+ # => "ruby"
204
+
205
+ document.to_html
206
+ # => "<p><code class=\"language-ruby\">Object.new</code></p>\n"
207
+ ```
208
+
209
+ Without the flag, the prefix remains ordinary text. Inline code information is
210
+ a single language token; richer code-block information belongs on a fenced code
211
+ block instead.
212
+
213
+ ### Code Block Metadata
214
+
215
+ `Node#code_info` is the general information-string accessor for fenced code
216
+ blocks and front matter. `Node#code_language` returns the first token of that
217
+ information string.
218
+
219
+ For a fenced code block, `Node#fence` returns a {ruby Markly::Node::Fence}
220
+ containing the fence character, length, and indentation:
221
+
222
+ ``` ruby
223
+ block = Markly.parse(" ~~~~ ruby\n Object.new\n ~~~~").first_child
224
+
225
+ block.code_info
226
+ # => "ruby"
227
+
228
+ block.fence
229
+ # => #<struct Markly::Node::Fence character="~", length=4, indent=2>
230
+ ```
231
+
232
+ Indented code blocks and other node types return `nil` from `Node#fence`.
@@ -78,57 +78,21 @@ To have multiple options applied, `|` (or) the flags together:
78
78
  Markly.render_html("\"'Shelob' is my name.\"", flags: Markly::HARD_BREAKS|Markly::SOURCE_POSITION)
79
79
  ```
80
80
 
81
- Inline code language prefixes are opt-in. The language is available through
82
- `Node#code_info` and is rendered as a `language-...` class:
83
-
84
- ``` ruby
85
- document = Markly.parse("ruby:`Object.new`", flags: Markly::INLINE_CODE_INFO)
86
- code = document.first_child.first_child
87
-
88
- code.code_info
89
- # => "ruby"
90
-
91
- code.code_language
92
- # => "ruby"
93
-
94
- document.to_html
95
- # => <p><code class="language-ruby">Object.new</code></p>
96
- ```
97
-
98
- `Node#code_info` is also the general info-string accessor for fenced code
99
- blocks and front matter. `Node#code_language` returns the first token of that
100
- info string, while `Node#fence_info` remains available for compatibility on
101
- those block nodes.
102
-
103
- For a fenced code block, `Node#fence` returns a `Node::Fence` structure with
104
- the fence character, length, and indentation:
105
-
106
- ``` ruby
107
- block = Markly.parse(" ~~~~ ruby\n Object.new\n ~~~~").first_child
108
-
109
- block.fence
110
- # => #<struct Markly::Node::Fence character="~", length=4, indent=2>
111
- ```
112
-
113
- Indented code blocks and other node types return `nil`.
114
-
115
81
  ## Extensions
116
82
 
117
- Both `render_html` and `parse` take an optional `extensions:` argument defining the extensions you want enabled as your CommonMark document is being processed:
83
+ Markly parses standard CommonMark by default. GitHub Flavored Markdown syntax
84
+ and Markly-specific syntax are opt-in so applications can choose their accepted
85
+ Markdown dialect explicitly:
118
86
 
119
87
  ``` ruby
120
- Markly.render_html("<script>hi</script>", flags: Markly::UNSAFE, extensions: [:tagfilter])
88
+ Markly.render_html(
89
+ "| Name | Status |\n| --- | --- |\n| Markly | Ready |",
90
+ extensions: [:table],
91
+ )
121
92
  ```
122
93
 
123
- The documentation for these extensions are [defined in this spec](https://github.github.com/gfm/), and the rationale is provided [in this blog post](https://githubengineering.com/a-formal-spec-for-github-markdown/).
124
-
125
- The available extensions are:
126
-
127
- - `:table` - This provides support for tables.
128
- - `:tasklist` - This provides support for task list items.
129
- - `:strikethrough` - This provides support for strikethroughs.
130
- - `:autolink` - This provides support for automatically converting URLs to anchor tags.
131
- - `:tagfilter` - This escapes [several "unsafe" HTML tags](https://github.github.com/gfm/#disallowed-raw-html-extension-), causing them to not have any effect.
94
+ See [Extensions](../extensions/index) for the supported extensions, related
95
+ flags, and generated AST.
132
96
 
133
97
  ## Developing Locally
134
98
 
data/context/index.yaml CHANGED
@@ -10,6 +10,9 @@ files:
10
10
  - path: getting-started.md
11
11
  title: Getting Started
12
12
  description: This guide explains now to install and use Markly.
13
+ - path: extensions.md
14
+ title: Extensions
15
+ description: This guide explains how to enable and use Markly's Markdown extensions.
13
16
  - path: abstract-syntax-tree.md
14
17
  title: Abstract Syntax Tree
15
18
  description: This guide explains how to use Markly's abstract syntax tree (AST)
data/ext/markly/blocks.c CHANGED
@@ -91,8 +91,8 @@ static CMARK_INLINE bool S_ends_on_current_line(cmark_parser *parser, cmark_node
91
91
  // similar to fenced code blocks.
92
92
  // Types 6-7 end at a blank line, so their last content line is
93
93
  // the previous line and they should NOT match here.
94
- (S_type(b) == CMARK_NODE_HTML_BLOCK && b->as.html_block_type >= 1 &&
95
- b->as.html_block_type <= 5) ||
94
+ (S_type(b) == CMARK_NODE_HTML_BLOCK && b->as.html_block.type >= 1 &&
95
+ b->as.html_block.type <= 5) ||
96
96
  // Single-line blocks: finalized on same line they started
97
97
  b->start_line == parser->line_number;
98
98
  }
@@ -1037,7 +1037,7 @@ static bool parse_code_block_prefix(cmark_parser *parser, cmark_chunk *input,
1037
1037
  static bool parse_html_block_prefix(cmark_parser *parser,
1038
1038
  cmark_node *container) {
1039
1039
  bool res = false;
1040
- int html_block_type = container->as.html_block_type;
1040
+ int html_block_type = container->as.html_block.type;
1041
1041
 
1042
1042
  assert(html_block_type >= 1 && html_block_type <= 7);
1043
1043
  switch (html_block_type) {
@@ -1051,7 +1051,32 @@ static bool parse_html_block_prefix(cmark_parser *parser,
1051
1051
  break;
1052
1052
  case 6:
1053
1053
  case 7:
1054
- res = !parser->blank;
1054
+ if (!(parser->options & CMARK_OPT_HTML_BLOCK_BLANK_LINES)) {
1055
+ res = !parser->blank;
1056
+ } else if (parser->blank) {
1057
+ // Tentatively retain blank lines. The next nonblank line determines
1058
+ // whether the HTML block continues:
1059
+ res = true;
1060
+ } else if (S_last_line_blank(container)) {
1061
+ // Establish the content indentation lazily so a blank line may follow
1062
+ // the opening tag. A non-indented line still terminates the block:
1063
+ if (container->as.html_block.indent == 0 && parser->indent > 0) {
1064
+ container->as.html_block.indent = parser->indent;
1065
+ }
1066
+
1067
+ res = container->as.html_block.indent > 0 &&
1068
+ parser->indent >= container->as.html_block.indent;
1069
+ } else {
1070
+ // Record the shallowest positive content indentation before a blank
1071
+ // line. Deeper nested HTML may then continue without changing it:
1072
+ if (parser->indent > 0 &&
1073
+ (container->as.html_block.indent == 0 ||
1074
+ parser->indent < container->as.html_block.indent)) {
1075
+ container->as.html_block.indent = parser->indent;
1076
+ }
1077
+
1078
+ res = true;
1079
+ }
1055
1080
  break;
1056
1081
  }
1057
1082
 
@@ -1224,7 +1249,7 @@ static void open_new_blocks(cmark_parser *parser, cmark_node **container,
1224
1249
  input, parser->first_nonspace))))) {
1225
1250
  *container = add_child(parser, *container, CMARK_NODE_HTML_BLOCK,
1226
1251
  parser->first_nonspace + 1);
1227
- (*container)->as.html_block_type = matched;
1252
+ (*container)->as.html_block.type = matched;
1228
1253
  // note, we don't adjust parser->offset because the tag is part of the
1229
1254
  // text
1230
1255
  } else if (!indented && cont_type == CMARK_NODE_PARAGRAPH &&
@@ -1421,7 +1446,7 @@ static void add_text_to_container(cmark_parser *parser, cmark_node *container,
1421
1446
  add_line(container, input, parser);
1422
1447
 
1423
1448
  int matches_end_condition;
1424
- switch (container->as.html_block_type) {
1449
+ switch (container->as.html_block.type) {
1425
1450
  case 1:
1426
1451
  // </script>, </style>, </pre>
1427
1452
  matches_end_condition =
@@ -798,6 +798,12 @@ char *cmark_render_latex_with_mem(cmark_node *root, int options, int width, cmar
798
798
  */
799
799
  #define CMARK_OPT_INLINE_CODE_INFO (1 << 19)
800
800
 
801
+ /** Allow indented content in type 6 and 7 HTML blocks to continue across
802
+ * blank lines. The indentation established by the HTML content must be
803
+ * preserved after each blank line.
804
+ */
805
+ #define CMARK_OPT_HTML_BLOCK_BLANK_LINES (1 << 20)
806
+
801
807
  /**
802
808
  * ## Version information
803
809
  */
data/ext/markly/node.h CHANGED
@@ -33,6 +33,11 @@ typedef struct {
33
33
  int8_t fenced;
34
34
  } cmark_code;
35
35
 
36
+ typedef struct {
37
+ int type;
38
+ int indent;
39
+ } cmark_html_block;
40
+
36
41
  typedef struct {
37
42
  int level;
38
43
  bool setext;
@@ -104,7 +109,7 @@ struct cmark_node {
104
109
  cmark_heading heading;
105
110
  cmark_link link;
106
111
  cmark_custom custom;
107
- int html_block_type;
112
+ cmark_html_block html_block;
108
113
  int cell_index; // For keeping track of TABLE_CELL table alignments
109
114
  void *opaque;
110
115
  } as;
data/lib/markly/flags.rb CHANGED
@@ -25,10 +25,13 @@ module Markly
25
25
  FRONT_MATTER = 1 << 18
26
26
  # Parse language prefixes on inline code spans, e.g. ruby:`Object.new`.
27
27
  INLINE_CODE_INFO = 1 << 19
28
+ # Allow consistently indented HTML content to continue across blank lines.
29
+ HTML_BLOCK_BLANK_LINES = 1 << 20
28
30
 
29
31
  PARSE_FLAGS = {
30
32
  front_matter: FRONT_MATTER,
31
33
  inline_code_info: INLINE_CODE_INFO,
34
+ html_block_blank_lines: HTML_BLOCK_BLANK_LINES,
32
35
  validate_utf8: VALIDATE_UTF8,
33
36
  smart_quotes: SMART,
34
37
  liberal_html_tags: LIBERAL_HTML_TAG,
@@ -9,5 +9,5 @@
9
9
  # @namespace
10
10
  module Markly
11
11
  # @constant [String] The version of the Markly gem.
12
- VERSION = "0.18.0"
12
+ VERSION = "0.19.0"
13
13
  end
data/readme.md CHANGED
@@ -16,6 +16,8 @@ Please see the [project documentation](https://socketry.github.io/markly/) for m
16
16
 
17
17
  - [Getting Started](https://socketry.github.io/markly/guides/getting-started/index) - This guide explains now to install and use Markly.
18
18
 
19
+ - [Extensions](https://socketry.github.io/markly/guides/extensions/index) - This guide explains how to enable and use Markly's Markdown extensions.
20
+
19
21
  - [Abstract Syntax Tree](https://socketry.github.io/markly/guides/abstract-syntax-tree/index) - This guide explains how to use Markly's abstract syntax tree (AST) to parse and manipulate Markdown documents.
20
22
 
21
23
  - [Headings](https://socketry.github.io/markly/guides/headings/index) - This guide explains how to work with headings in Markly, including extracting them for navigation and handling duplicate heading text.
@@ -24,6 +26,10 @@ Please see the [project documentation](https://socketry.github.io/markly/) for m
24
26
 
25
27
  Please see the [project releases](https://socketry.github.io/markly/releases/index) for all releases.
26
28
 
29
+ ### v0.19.0
30
+
31
+ - Add `Markly::HTML_BLOCK_BLANK_LINES` for keeping consistently indented HTML content together across blank lines.
32
+
27
33
  ### v0.18.0
28
34
 
29
35
  - Preserve complete node and extension metadata when duplicating node trees.
data/releases.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Releases
2
2
 
3
+ ## v0.19.0
4
+
5
+ - Add `Markly::HTML_BLOCK_BLANK_LINES` for keeping consistently indented HTML content together across blank lines.
6
+
3
7
  ## v0.18.0
4
8
 
5
9
  - Preserve complete node and extension metadata when duplicating node trees.
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: markly
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.18.0
4
+ version: 0.19.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Garen Torikian
@@ -65,6 +65,7 @@ extensions:
65
65
  extra_rdoc_files: []
66
66
  files:
67
67
  - context/abstract-syntax-tree.md
68
+ - context/extensions.md
68
69
  - context/getting-started.md
69
70
  - context/headings.md
70
71
  - context/index.yaml
metadata.gz.sig CHANGED
Binary file