Skip to content

Commit ecee040

Browse files
committed
feat: add support for markdown style source blocks
1 parent b8290d3 commit ecee040

File tree

3 files changed

+300
-8
lines changed

3 files changed

+300
-8
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
Traditional AsciiDoc format:
2+
3+
[source,ruby]
4+
----
5+
require 'sinatra'
6+
get '/hi' do
7+
"Hello World!"
8+
end
9+
----
10+
11+
New Markdown format:
12+
13+
```ruby
14+
require 'sinatra'
15+
get '/hi' do
16+
"Hello World!"
17+
end
18+
```
19+
20+
Markdown without language:
21+
22+
```
23+
plain code block
24+
without language
25+
```
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
{
2+
"name": "document",
3+
"type": "block",
4+
"blocks": [
5+
{
6+
"name": "paragraph",
7+
"type": "block",
8+
"inlines": [
9+
{
10+
"name": "text",
11+
"type": "string",
12+
"value": "Traditional AsciiDoc format:",
13+
"location": [
14+
{
15+
"line": 1,
16+
"col": 1
17+
},
18+
{
19+
"line": 1,
20+
"col": 28
21+
}
22+
]
23+
}
24+
],
25+
"location": [
26+
{
27+
"line": 1,
28+
"col": 1
29+
},
30+
{
31+
"line": 1,
32+
"col": 28
33+
}
34+
]
35+
},
36+
{
37+
"name": "listing",
38+
"type": "block",
39+
"form": "delimited",
40+
"delimiter": "----",
41+
"inlines": [
42+
{
43+
"name": "text",
44+
"type": "string",
45+
"value": "require 'sinatra'\nget '/hi' do\n \"Hello World!\"\nend",
46+
"location": [
47+
{
48+
"line": 5,
49+
"col": 1
50+
},
51+
{
52+
"line": 8,
53+
"col": 3
54+
}
55+
]
56+
}
57+
],
58+
"metadata": {
59+
"attributes": {
60+
"ruby": null
61+
},
62+
"style": "source"
63+
},
64+
"location": [
65+
{
66+
"line": 3,
67+
"col": 1
68+
},
69+
{
70+
"line": 9,
71+
"col": 4
72+
}
73+
]
74+
},
75+
{
76+
"name": "paragraph",
77+
"type": "block",
78+
"inlines": [
79+
{
80+
"name": "text",
81+
"type": "string",
82+
"value": "New Markdown format:",
83+
"location": [
84+
{
85+
"line": 11,
86+
"col": 1
87+
},
88+
{
89+
"line": 11,
90+
"col": 20
91+
}
92+
]
93+
}
94+
],
95+
"location": [
96+
{
97+
"line": 11,
98+
"col": 1
99+
},
100+
{
101+
"line": 11,
102+
"col": 20
103+
}
104+
]
105+
},
106+
{
107+
"name": "listing",
108+
"type": "block",
109+
"form": "delimited",
110+
"delimiter": "```",
111+
"inlines": [
112+
{
113+
"name": "text",
114+
"type": "string",
115+
"value": "require 'sinatra'\nget '/hi' do\n \"Hello World!\"\nend",
116+
"location": [
117+
{
118+
"line": 14,
119+
"col": 1
120+
},
121+
{
122+
"line": 17,
123+
"col": 3
124+
}
125+
]
126+
}
127+
],
128+
"metadata": {
129+
"attributes": {
130+
"ruby": null
131+
}
132+
},
133+
"location": [
134+
{
135+
"line": 13,
136+
"col": 1
137+
},
138+
{
139+
"line": 18,
140+
"col": 3
141+
}
142+
]
143+
},
144+
{
145+
"name": "paragraph",
146+
"type": "block",
147+
"inlines": [
148+
{
149+
"name": "text",
150+
"type": "string",
151+
"value": "Markdown without language:",
152+
"location": [
153+
{
154+
"line": 20,
155+
"col": 1
156+
},
157+
{
158+
"line": 20,
159+
"col": 26
160+
}
161+
]
162+
}
163+
],
164+
"location": [
165+
{
166+
"line": 20,
167+
"col": 1
168+
},
169+
{
170+
"line": 20,
171+
"col": 26
172+
}
173+
]
174+
},
175+
{
176+
"name": "listing",
177+
"type": "block",
178+
"form": "delimited",
179+
"delimiter": "```",
180+
"inlines": [
181+
{
182+
"name": "text",
183+
"type": "string",
184+
"value": "plain code block\nwithout language",
185+
"location": [
186+
{
187+
"line": 23,
188+
"col": 1
189+
},
190+
{
191+
"line": 24,
192+
"col": 16
193+
}
194+
]
195+
}
196+
],
197+
"location": [
198+
{
199+
"line": 22,
200+
"col": 1
201+
},
202+
{
203+
"line": 25,
204+
"col": 3
205+
}
206+
]
207+
}
208+
],
209+
"location": [
210+
{
211+
"line": 1,
212+
"col": 1
213+
},
214+
{
215+
"line": 25,
216+
"col": 3
217+
}
218+
]
219+
}

acdc-parser/src/grammar/document.rs

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,5 @@
11
#![allow(clippy::too_many_arguments)]
22
use crate::{
3-
error::Detail,
4-
grammar::{
5-
author_revision::{generate_initials, process_revision_info, RevisionInfo},
6-
inline_processing::{parse_inlines, preprocess_inline_content, process_inlines},
7-
location_mapping::map_inline_locations,
8-
LineMap,
9-
},
10-
model::{ListLevel, SectionLevel},
113
Admonition, AdmonitionVariant, Anchor, AttributeValue, Audio, Author, Autolink, Block,
124
BlockMetadata, Bold, Button, CurvedApostrophe, CurvedQuotation, DelimitedBlock,
135
DelimitedBlockType, DiscreteHeader, Document, DocumentAttribute, DocumentAttributes, Error,
@@ -16,6 +8,14 @@ use crate::{
168
PageBreak, Paragraph, Pass, PassthroughKind, Plain, Raw, Section, Source,
179
StandaloneCurvedApostrophe, Subscript, Substitution, Superscript, Table, TableColumn,
1810
TableOfContents, TableRow, ThematicBreak, UnorderedList, Url, Video,
11+
error::Detail,
12+
grammar::{
13+
LineMap,
14+
author_revision::{RevisionInfo, generate_initials, process_revision_info},
15+
inline_processing::{parse_inlines, preprocess_inline_content, process_inlines},
16+
location_mapping::map_inline_locations,
17+
},
18+
model::{ListLevel, SectionLevel},
1919
};
2020

2121
#[derive(Debug)]
@@ -512,6 +512,7 @@ peg::parser! {
512512
rule sidebar_delimiter() -> &'input str = delim:$("*"*<4,>) { delim }
513513
rule table_delimiter() -> &'input str = delim:$((['|' | ',' | ':' | '!'] "="*<3,>)) { delim }
514514
rule pass_delimiter() -> &'input str = delim:$("+"*<4,>) { delim }
515+
rule markdown_code_delimiter() -> &'input str = delim:$("`"*<3,>) { delim }
515516
rule quote_delimiter() -> &'input str = delim:$("_"*<4,>) { delim }
516517

517518
rule until_comment_delimiter() -> &'input str
@@ -568,6 +569,18 @@ peg::parser! {
568569
content
569570
}
570571

572+
rule until_markdown_code_delimiter() -> &'input str
573+
= content:$((!(eol() markdown_code_delimiter()) [_])*)
574+
{
575+
content
576+
}
577+
578+
rule markdown_language() -> &'input str
579+
= lang:$((['a'..='z'] / ['A'..='Z'] / ['0'..='9'] / "_" / "+" / "-")+)
580+
{
581+
lang
582+
}
583+
571584
rule example_block(start: usize, offset: usize, block_metadata: &BlockParsingMetadata) -> Result<Block, Error>
572585
= open_delim:example_delimiter() eol()
573586
content_start:position!() content:until_example_delimiter() content_end:position!()
@@ -643,6 +656,10 @@ peg::parser! {
643656
}
644657

645658
rule listing_block(start: usize, offset: usize, block_metadata: &BlockParsingMetadata) -> Result<Block, Error>
659+
= traditional_listing_block(start, offset, block_metadata)
660+
/ markdown_listing_block(start, offset, block_metadata)
661+
662+
rule traditional_listing_block(start: usize, offset: usize, block_metadata: &BlockParsingMetadata) -> Result<Block, Error>
646663
= open_delim:listing_delimiter() eol()
647664
content_start:position!() content:until_listing_delimiter() content_end:position!()
648665
eol() close_delim:listing_delimiter() end:position!()
@@ -667,6 +684,37 @@ peg::parser! {
667684
}))
668685
}
669686

687+
rule markdown_listing_block(start: usize, offset: usize, block_metadata: &BlockParsingMetadata) -> Result<Block, Error>
688+
= open_delim:markdown_code_delimiter() lang:markdown_language()? eol()
689+
content_start:position!() content:until_markdown_code_delimiter() content_end:position!()
690+
eol() close_delim:markdown_code_delimiter() end:position!()
691+
{
692+
if open_delim != close_delim {
693+
return Err(Error::MismatchedDelimiters("listing".to_string()));
694+
}
695+
let mut metadata = block_metadata.metadata.clone();
696+
697+
// If we captured a language, add it as a positional attribute
698+
if let Some(language) = lang {
699+
metadata.positional_attributes.insert(0, language.to_string());
700+
}
701+
702+
metadata.move_positional_attributes_to_attributes();
703+
let location = state.create_location(start+offset, (end+offset).saturating_sub(1));
704+
let content_location = state.create_location(content_start+offset, (content_end+offset).saturating_sub(1));
705+
706+
Ok(Block::DelimitedBlock(DelimitedBlock {
707+
metadata: metadata.clone(),
708+
delimiter: open_delim.to_string(),
709+
inner: DelimitedBlockType::DelimitedListing(vec![InlineNode::PlainText(Plain {
710+
content: content.to_string(),
711+
location: content_location,
712+
})]),
713+
title: block_metadata.title.clone(),
714+
location,
715+
}))
716+
}
717+
670718
pub(crate) rule literal_block(start: usize, offset: usize, block_metadata: &BlockParsingMetadata) -> Result<Block, Error>
671719
=
672720
open_delim:literal_delimiter()

0 commit comments

Comments
 (0)