coradoc-adoc 2.0.17 → 2.0.19

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: 99b9b654a8eebde5418dfbee6f7b184e65bbbcd830b24f0211963d61ed6c1201
4
- data.tar.gz: 93e69befaf29a65358e5b6655b7b6cf63d0fb9baab9a828848d7468290c87e3b
3
+ metadata.gz: 817eba65c55a5dea765ab8e80ee5542a1353741659c8a32ef4eb80e39f052dcb
4
+ data.tar.gz: 25a07bc63696b7ba27933c9724c0a5ada466dc7cd0f37350ae4c9a4a475afbec
5
5
  SHA512:
6
- metadata.gz: 1480db996c2768544ae15657c7f394bcaed954c31c79bdf1bfd3f3493b608aad77dc078ed44058ac0964632626585f096a7c96d6047a256ed07f902291ac204a
7
- data.tar.gz: b3d0d2044b40702ad13d2f500e06e4e5881e865eda9ac0bc3f01fba94c82175b9b1752e3076b05781d7fd03075af7d276b8af55ef18b4be08576ea5018ea07b0
6
+ metadata.gz: c3d26bcc8f91f799f455f051109933875545219b14e9556cc4d5774dcda16e9abdcb1f0e6a090f1d9aea35c49d4d7f5ed8c6bae2b1e47a76cc75a86270e47cd7
7
+ data.tar.gz: 50a575b3666d1d6df0a8813a9761345ced5d895b2c73bf80347d8fb24e36c396389d4901fa22a6f34e1adcea6cb4d5b697bdab121dc495603da88740753e821d
@@ -44,7 +44,7 @@ module Coradoc
44
44
  end
45
45
 
46
46
  def positional_value_unquoted
47
- match('[^\]\s,]').repeat(1) >> space.absent?
47
+ match('[^\],]').repeat(1)
48
48
  end
49
49
 
50
50
  def positional_value_single_quote
@@ -22,7 +22,8 @@ module Coradoc
22
22
  # - "|" separates cells within the table
23
23
 
24
24
  def block(n_deep = 3)
25
- (example_block(n_deep) |
25
+ (markdown_code_block(n_deep) |
26
+ example_block(n_deep) |
26
27
  sidebar_block(n_deep) |
27
28
  source_block(n_deep) |
28
29
  quote_block(n_deep) |
@@ -60,6 +61,38 @@ module Coradoc
60
61
  block_style_exact(n_deep, '-', 2)
61
62
  end
62
63
 
64
+ # Markdown-style fenced code block: triple-backtick (or longer)
65
+ # fence with an optional language tag on the opening line. Behaves
66
+ # as a verbatim source block — same model as `[source,lang]\n----`.
67
+ # Pragmatic permissiveness for content that originates from (or is
68
+ # edited alongside) Markdown; not standard AsciiDoc but widely
69
+ # accepted (GitHub's renderer treats ``` as a listing delimiter).
70
+ def markdown_code_block(n_deep = 3)
71
+ capture_key = :"md_fence_#{n_deep}"
72
+ opening_fence = str('`').repeat(3).capture(capture_key)
73
+ closing_fence = dynamic do |_s, c|
74
+ str(c.captures[capture_key].to_s.strip)
75
+ end
76
+ language = (space? >> match("[A-Za-z0-9_+.-]").repeat(1).as(:language)).maybe
77
+
78
+ block_content_with_closing = dynamic do |_s, c|
79
+ fence_str = c.captures[capture_key].to_s.strip
80
+ closing_pattern = closing_fence >> space? >> newline
81
+
82
+ content = text_line(false, unguarded: true, verbatim: true) |
83
+ empty_line.as(:line_break)
84
+
85
+ (closing_pattern.absent? >> content).repeat(1)
86
+ end
87
+
88
+ block_header >>
89
+ line_start? >>
90
+ opening_fence.as(:delimiter) >> language >> newline >>
91
+ block_content_with_closing.as(:lines) >>
92
+ line_start? >>
93
+ closing_fence >> space? >> newline
94
+ end
95
+
63
96
  def block_title
64
97
  (line_start? >> block_delimiter.absent?) >>
65
98
  str('.') >> space.absent? >> text.as(:title) >> newline
@@ -80,8 +113,9 @@ module Coradoc
80
113
  c.repeat(1)
81
114
  end
82
115
 
83
- # Block delimiter: 4+ identical characters (or 2 for open block)
84
- # Used by paragraph.rb to reject lines that look like block delimiters.
116
+ # Block delimiter: 4+ identical characters, or 2 dashes for open
117
+ # blocks, or 3+ backticks for Markdown-style code fences. Used by
118
+ # paragraph.rb to reject lines that look like block delimiters.
85
119
  # NOTE: repeat(4,) means 4 or more (not exactly 4)
86
120
  def block_delimiter
87
121
  line_start? >>
@@ -91,7 +125,8 @@ module Coradoc
91
125
  str('+') |
92
126
  str('.') |
93
127
  str('-')).repeat(4) | # 4+ characters for most blocks
94
- str('-').repeat(2, 2)) >> # Exactly 2 for open block
128
+ str('-').repeat(2, 2) | # Exactly 2 for open block
129
+ str('`').repeat(3)) >> # 3+ for Markdown code fences
95
130
  newline
96
131
  end
97
132
 
@@ -118,6 +118,7 @@ module Coradoc
118
118
 
119
119
  def inline_image
120
120
  (str('image:').present? >> str('image:') >>
121
+ str(':').absent? >>
121
122
  match('[A-Za-z0-9_.\\-:/&?=+,%#~;]+').repeat(1).as(:path) >>
122
123
  (str('[') >> match('[^\\]]').repeat(1).as(:text) >> str(']')).maybe
123
124
  ).as(:inline_image)
@@ -72,11 +72,35 @@ module Coradoc
72
72
  def ulist_marker(nesting_level = 1)
73
73
  line_start? >>
74
74
  (nesting_level > 1 ? literal_space.maybe : str('')) >>
75
- str('*' * nesting_level) >>
75
+ (
76
+ asterisk_marker(nesting_level) |
77
+ dash_marker(nesting_level)
78
+ )
79
+ end
80
+
81
+ # AsciiDoc standard bullet: `*`, `**`, `***`, ... matching the
82
+ # nesting level. Excludes table delimiters (`|===`) and deeper
83
+ # asterisk runs that belong to a sibling level.
84
+ def asterisk_marker(nesting_level)
85
+ str('*' * nesting_level) >>
76
86
  str('*').absent? >>
77
87
  str('===').absent?
78
88
  end
79
89
 
90
+ # Markdown-style dash bullet: `-`. Accepted only at the top level
91
+ # because Markdown nests via indentation rather than multi-char
92
+ # markers — deeper levels stay on the AsciiDoc `*` form. Guards
93
+ # exclude em-dashes (`--`), delimited-block fences (`----`),
94
+ # and negative-number runs (`-1`, `-42`) which are not list
95
+ # markers in any common dialect.
96
+ def dash_marker(nesting_level)
97
+ return match('').absent? unless nesting_level == 1
98
+
99
+ str('-') >>
100
+ str('-').absent? >>
101
+ match('[0-9]').absent?
102
+ end
103
+
80
104
  def ulist_item(nesting_level = 1)
81
105
  item = ulist_marker(nesting_level).as(:marker) >>
82
106
  str(' [[[').absent? >>
@@ -128,7 +128,7 @@ module Coradoc
128
128
 
129
129
  def transform_typed_block(block, klass, extra_attrs = {})
130
130
  raw_lines = Array(block.lines)
131
- has_nested_blocks = raw_lines.any?(Coradoc::AsciiDoc::Model::Block::Core)
131
+ has_nested_blocks = raw_lines.any? { |line| block_level_child?(line) }
132
132
 
133
133
  if has_nested_blocks
134
134
  children = raw_lines.reject do |line|
@@ -195,6 +195,17 @@ module Coradoc
195
195
  groups << current if current.any?
196
196
  groups
197
197
  end
198
+
199
+ # A line that should be emitted as a direct child of the
200
+ # enclosing block rather than joined into a paragraph sibling.
201
+ # The AsciiDoc model layer declares level via `block_level?`
202
+ # (true for Block::Core delimited blocks, BlockImage, Table,
203
+ # List::Core, Section, CommentBlock, Attached). Inline content
204
+ # (String, TextElement, LineBreak, PageBreak) returns false and
205
+ # stays inside paragraphs.
206
+ def block_level_child?(line)
207
+ line.is_a?(Coradoc::AsciiDoc::Model::Base) && line.block_level?
208
+ end
198
209
  end
199
210
  end
200
211
  end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module AsciiDoc
5
+ module Transform
6
+ module ElementTransformers
7
+ # Transforms AsciiDoc {Model::Include} into the canonical
8
+ # {CoreModel::Include}. The CoreModel node carries the parsed
9
+ # options as a typed {CoreModel::IncludeOptions} instance plus
10
+ # the raw bracket body for verbatim round-trip.
11
+ class IncludeTransformer
12
+ class << self
13
+ # @param model [Coradoc::AsciiDoc::Model::Include]
14
+ # @return [Coradoc::CoreModel::Include]
15
+ def transform_include(model)
16
+ options_hash = extract_options_hash(model.attributes)
17
+ options = CoreModel::IncludeOptions.from_hash(options_hash)
18
+ raw = extract_raw_options(model.attributes)
19
+
20
+ CoreModel::Include.new(
21
+ target: model.path.to_s,
22
+ options: options,
23
+ raw_options: raw,
24
+ line_break: model.line_break.to_s
25
+ )
26
+ end
27
+
28
+ private
29
+
30
+ def extract_options_hash(attrs)
31
+ return {} unless attrs.is_a?(Coradoc::AsciiDoc::Model::AttributeList)
32
+
33
+ attrs.named.each_with_object({}) do |attr, hash|
34
+ hash[attr.name.to_s] = serialize_named_value(attr.value)
35
+ end
36
+ end
37
+
38
+ def serialize_named_value(value)
39
+ case value
40
+ when Array then value.map(&:to_s).join(';')
41
+ else value.to_s
42
+ end
43
+ end
44
+
45
+ def extract_raw_options(attrs)
46
+ return '' unless attrs.is_a?(Coradoc::AsciiDoc::Model::AttributeList)
47
+
48
+ adoc = attrs.to_adoc(show_empty: false).to_s
49
+ adoc.gsub(/\A\[|\]\z/, '')
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -27,9 +27,19 @@ module Coradoc
27
27
  def transform_image(image)
28
28
  src = image.src.to_s
29
29
  src = src[1..] if src.start_with?(':')
30
+ positional = image.attributes&.positional || []
31
+ positional_alt = positional.first&.value&.to_s || ''
32
+ caption = positional[1]&.value&.to_s
33
+ title = image.title&.to_s
34
+ # AsciiDoc block-title (`.Caption`) is semantically a caption,
35
+ # but for compatibility with the existing pipeline that treated
36
+ # it as alt when no positional alt was supplied, fall back to it.
37
+ alt = positional_alt.empty? ? title : positional_alt
30
38
  Coradoc::CoreModel::Image.new(
31
39
  src: src,
32
- alt: image.title&.to_s,
40
+ alt: alt,
41
+ caption: caption,
42
+ title: title,
33
43
  width: image.attributes&.[]('width'),
34
44
  height: image.attributes&.[]('height')
35
45
  )
@@ -10,6 +10,7 @@ module Coradoc
10
10
  autoload :InlineTransformer, "#{__dir__}/element_transformers/inline_transformer"
11
11
  autoload :TableTransformer, "#{__dir__}/element_transformers/table_transformer"
12
12
  autoload :OtherTransformer, "#{__dir__}/element_transformers/other_transformer"
13
+ autoload :IncludeTransformer, "#{__dir__}/element_transformers/include_transformer"
13
14
  end
14
15
  end
15
16
  end
@@ -388,8 +388,51 @@ module Coradoc
388
388
  )
389
389
  end
390
390
 
391
+ def transform_include(include)
392
+ Coradoc::AsciiDoc::Model::Include.new(
393
+ path: include.target.to_s,
394
+ attributes: build_include_attributes(include),
395
+ line_break: include.line_break.to_s
396
+ )
397
+ end
398
+
391
399
  private
392
400
 
401
+ def build_include_attributes(include)
402
+ list = Coradoc::AsciiDoc::Model::AttributeList.new
403
+ options = include.options
404
+ return list if options.nil?
405
+
406
+ add_tag_attribute(list, options)
407
+ add_simple_attribute(list, 'lines', options.lines_spec)
408
+ add_leveloffset_attribute(list, options)
409
+ add_simple_attribute(list, 'indent', options.indent&.to_s)
410
+ add_simple_attribute(list, 'encoding', options.file_encoding)
411
+ list
412
+ end
413
+
414
+ def add_simple_attribute(list, name, value)
415
+ return if value.nil? || value.to_s.empty?
416
+
417
+ list.add_named(name, value.to_s)
418
+ end
419
+
420
+ def add_tag_attribute(list, options)
421
+ if options.tags_wildcard
422
+ list.add_named('tags', '*')
423
+ elsif options.tags_inverted
424
+ list.add_named('tags', '**')
425
+ elsif options.tags.any?
426
+ list.add_named('tags', options.tags.join(';'))
427
+ end
428
+ end
429
+
430
+ def add_leveloffset_attribute(list, options)
431
+ return if options.leveloffset.nil?
432
+
433
+ list.add_named('leveloffset', options.leveloffset.to_s)
434
+ end
435
+
393
436
  # If the first CoreModel child is a FrontmatterBlock, serialize
394
437
  # it to YAML text via Codec (single source of truth) and pop it
395
438
  # from the children list. Returns [remaining_children,
@@ -120,6 +120,11 @@ module Coradoc
120
120
  Coradoc::CoreModel::CommentLine,
121
121
  ->(model) { FromCoreModel.transform_comment_line(model) }
122
122
  )
123
+
124
+ Registry.register(
125
+ Coradoc::CoreModel::Include,
126
+ ->(model) { FromCoreModel.transform_include(model) }
127
+ )
123
128
  end
124
129
  end
125
130
  end
@@ -10,6 +10,7 @@ module Coradoc
10
10
  Inl = ElementTransformers::InlineTransformer
11
11
  Tbl = ElementTransformers::TableTransformer
12
12
  Oth = ElementTransformers::OtherTransformer
13
+ Inc = ElementTransformers::IncludeTransformer
13
14
 
14
15
  class << self
15
16
  def register_all!
@@ -231,8 +232,16 @@ module Coradoc
231
232
  ->(model) { Oth.transform_bibliography_entry(model) }
232
233
  )
233
234
 
234
- [
235
+ # Include directives become first-class CoreModel::Include nodes
236
+ # (link edges in a text graph). To splice their content inline,
237
+ # call +Coradoc.resolve_includes(doc, base_dir:)+ on the parsed
238
+ # document — that step is intentionally separate from parse.
239
+ Registry.register(
235
240
  Coradoc::AsciiDoc::Model::Include,
241
+ ->(model) { Inc.transform_include(model) }
242
+ )
243
+
244
+ [
236
245
  Coradoc::AsciiDoc::Model::Audio,
237
246
  Coradoc::AsciiDoc::Model::Video,
238
247
  Coradoc::AsciiDoc::Model::ContentList,
@@ -25,6 +25,10 @@ module Coradoc
25
25
  lines: lines,
26
26
  ordering: ordering
27
27
  }
28
+ # Markdown fences carry the language tag inline (```ruby);
29
+ # pass it through so the SourceCode classifier entry can set
30
+ # block.lang directly, which extract_block_language prefers.
31
+ opts[:lang] = block[:language].to_s if block.key?(:language) && !block[:language].nil?
28
32
  BlockTypeClassifier.classify(delimiter, opts, attribute_list)
29
33
  end
30
34
 
@@ -46,7 +50,7 @@ module Coradoc
46
50
  id = block_image[:id]
47
51
  title = block_image[:title]
48
52
  path = block_image[:path]
49
- attrs = AttributeListNormalizer.coerce(block_image[:attribute_list])
53
+ attrs = AttributeListNormalizer.coerce(block_image[:attribute_list_macro])
50
54
  Model::Image::BlockImage.new(
51
55
  title: title,
52
56
  id: id,
@@ -28,7 +28,15 @@ module Coradoc
28
28
  ['.', 4, nil, ->(opts, attrs) { Model::Block::Literal.new(**opts.merge(attributes: attrs)) }],
29
29
  ['_', 4, nil, ->(opts, attrs) { Model::Block::Quote.new(**opts.merge(attributes: attrs)) }],
30
30
  ['-', 4, nil, ->(opts, attrs) { Model::Block::SourceCode.new(**opts.merge(attributes: attrs)) }],
31
- ['-', 2, 2, ->(opts, attrs) { Model::Block::Open.new(**opts.merge(attributes: attrs)) }]
31
+ ['-', 2, 2, ->(opts, attrs) { Model::Block::Open.new(**opts.merge(attributes: attrs)) }],
32
+ # Markdown-style triple-backtick fence: behaves as a SourceCode
33
+ # block. The language tag parsed from the opening fence is passed
34
+ # through opts[:lang]; extract_block_language prefers block.lang.
35
+ ['`', 3, nil, ->(opts, attrs) {
36
+ model_opts = opts.merge(attributes: attrs, delimiter_char: '`')
37
+ model_opts[:lang] = opts[:lang] if opts.key?(:lang)
38
+ Model::Block::SourceCode.new(**model_opts)
39
+ }]
32
40
  ].freeze
33
41
 
34
42
  module_function
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Coradoc
4
4
  module AsciiDoc
5
- VERSION = '2.0.17'
5
+ VERSION = '2.0.19'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: coradoc-adoc
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.17
4
+ version: 2.0.19
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -319,6 +319,7 @@ files:
319
319
  - lib/coradoc/asciidoc/transform/element_transformers.rb
320
320
  - lib/coradoc/asciidoc/transform/element_transformers/block_transformer.rb
321
321
  - lib/coradoc/asciidoc/transform/element_transformers/document_transformer.rb
322
+ - lib/coradoc/asciidoc/transform/element_transformers/include_transformer.rb
322
323
  - lib/coradoc/asciidoc/transform/element_transformers/inline_transformer.rb
323
324
  - lib/coradoc/asciidoc/transform/element_transformers/list_transformer.rb
324
325
  - lib/coradoc/asciidoc/transform/element_transformers/other_transformer.rb