coradoc-adoc 2.0.35 → 2.0.36

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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/grammar/coradoc-adoc.artifact.json +1 -0
  3. data/grammar/coradoc-adoc.parg +396 -0
  4. data/lib/coradoc/asciidoc/formatter.rb +75 -0
  5. data/lib/coradoc/asciidoc/lint/document_title.rb +26 -0
  6. data/lib/coradoc/asciidoc/lint/duplicate_ids.rb +36 -0
  7. data/lib/coradoc/asciidoc/lint/empty_section.rb +45 -0
  8. data/lib/coradoc/asciidoc/lint/heading_levels.rb +44 -0
  9. data/lib/coradoc/asciidoc/lint.rb +27 -0
  10. data/lib/coradoc/asciidoc/model/text_element.rb +18 -5
  11. data/lib/coradoc/asciidoc/parser/base.rb +28 -11
  12. data/lib/coradoc/asciidoc/parser/citation.rb +10 -0
  13. data/lib/coradoc/asciidoc/parser/inline.rb +8 -4
  14. data/lib/coradoc/asciidoc/parser/list.rb +19 -2
  15. data/lib/coradoc/asciidoc/parser/paragraph.rb +1 -1
  16. data/lib/coradoc/asciidoc/parser/positioned_string.rb +40 -0
  17. data/lib/coradoc/asciidoc/parser/rule_dispatcher.rb +3 -2
  18. data/lib/coradoc/asciidoc/parser/tree_materializer.rb +28 -0
  19. data/lib/coradoc/asciidoc/parser.rb +2 -0
  20. data/lib/coradoc/asciidoc/serializer/serializers/block/source_code.rb +3 -1
  21. data/lib/coradoc/asciidoc/serializer/serializers/list/item.rb +4 -0
  22. data/lib/coradoc/asciidoc/serializer/serializers/table.rb +8 -2
  23. data/lib/coradoc/asciidoc/serializer/serializers/text_element.rb +3 -1
  24. data/lib/coradoc/asciidoc/transform/from_core_model/list_building.rb +86 -0
  25. data/lib/coradoc/asciidoc/transform/from_core_model.rb +100 -29
  26. data/lib/coradoc/asciidoc/transform/frontmatter_attribute_map.rb +3 -2
  27. data/lib/coradoc/asciidoc/transform/text_extract_visitor.rb +5 -2
  28. data/lib/coradoc/asciidoc/transform/to_core_model.rb +12 -2
  29. data/lib/coradoc/asciidoc/transform.rb +1 -0
  30. data/lib/coradoc/asciidoc/transformer/source_line_extractor.rb +1 -1
  31. data/lib/coradoc/asciidoc/transformer/table_cell_builder.rb +18 -7
  32. data/lib/coradoc/asciidoc/version.rb +1 -1
  33. data/lib/coradoc/asciidoc.rb +12 -0
  34. data/lib/coradoc/util/asciidoc.rb +56 -0
  35. metadata +16 -5
@@ -109,15 +109,18 @@ module Coradoc
109
109
 
110
110
  # Collections
111
111
  def visit_array(models)
112
+ # Fragments of one source line concatenate (an escape splits
113
+ # `foo\_bar` into three pieces); a soft line break joins
114
+ # with a space (soft-wrapped paragraph lines).
112
115
  result = []
113
116
  models.each_with_index do |item, idx|
114
117
  text = visit(item)
115
118
  next if text.empty?
116
119
 
117
120
  result << text
118
- next unless idx < models.length - 1 && !text.empty?
121
+ next if idx == models.length - 1
119
122
 
120
- result << ' ' if item.is_a?(Model::TextElement) && item.line_break != '+'
123
+ result << ' ' if item.is_a?(Model::TextElement) && item.line_break == "\n"
121
124
  end
122
125
  result.join
123
126
  end
@@ -108,12 +108,22 @@ module Coradoc
108
108
  DelimiterMapping::CHAR_TO_SEMANTIC[char] || :open
109
109
  end
110
110
 
111
+ # Reused parser/transformer for inline re-parsing: Parsanol's
112
+ # VM program cache is keyed by atom identity, so a fresh parser
113
+ # per call recompiled the grammar every time (see also
114
+ # Transformer::TableCellBuilder).
115
+ def inline_reparse
116
+ @inline_reparse ||= [
117
+ Coradoc::AsciiDoc::Parser::Base.new,
118
+ Coradoc::AsciiDoc::Transformer.new
119
+ ]
120
+ end
121
+
111
122
  def parse_inline_text(raw_text)
112
123
  return [] if raw_text.nil? || raw_text.to_s.strip.empty?
113
124
 
114
125
  text = raw_text.to_s
115
- parser = Coradoc::AsciiDoc::Parser::Base.new
116
- transformer = Coradoc::AsciiDoc::Transformer.new
126
+ parser, transformer = inline_reparse
117
127
 
118
128
  parsed = parser.text_any.parse(text)
119
129
  result = transformer.apply({ text: parsed })
@@ -8,6 +8,7 @@ module Coradoc
8
8
  autoload :ToCoreModel, "#{__dir__}/transform/to_core_model"
9
9
  autoload :ToCoreModelRegistrations, "#{__dir__}/transform/to_core_model_registrations"
10
10
  autoload :FromCoreModel, "#{__dir__}/transform/from_core_model"
11
+ autoload :ListBuilding, "#{__dir__}/transform/from_core_model/list_building"
11
12
  autoload :FromCoreModelRegistrations, "#{__dir__}/transform/from_core_model_registrations"
12
13
  autoload :TextExtractVisitor, "#{__dir__}/transform/text_extract_visitor"
13
14
  autoload :InlineTransformVisitor, "#{__dir__}/transform/inline_transform_visitor"
@@ -33,7 +33,7 @@ module Coradoc
33
33
 
34
34
  def extract(node)
35
35
  case node
36
- when Parsanol::Slice then line_of(node)
36
+ when Parsanol::Slice, Coradoc::AsciiDoc::Parser::PositionedString then line_of(node)
37
37
  when Coradoc::AsciiDoc::Model::Base then node.source_line
38
38
  when Hash then extract_from_hash(node)
39
39
  when Array then extract_from_array(node)
@@ -13,6 +13,16 @@ module Coradoc
13
13
  module TableCellBuilder
14
14
  module_function
15
15
 
16
+ # Reused parser for cell-content re-parsing. Atoms are memoized
17
+ # per parser instance and Parsanol's VM program cache is keyed by
18
+ # atom identity — a fresh parser per cell recompiled the whole
19
+ # grammar for every cell (tables transformed ~50× slower than
20
+ # they parsed). Sequential reuse only; parse state lives in the
21
+ # per-call context, not the parser.
22
+ def inline_parser
23
+ @inline_parser ||= Coradoc::AsciiDoc::Parser::Base.new
24
+ end
25
+
16
26
  # @param format [Hash, String, Object, nil] Cell format from parser
17
27
  # @param content [Object] Cell content
18
28
  # @return [Model::TableCell]
@@ -59,14 +69,15 @@ module Coradoc
59
69
  # @param style [String, nil] 'a' (AsciiDoc), 'l' (literal), or nil
60
70
  # @return [Array<Model::TextElement>]
61
71
  def parse_inline_content(text, style = nil)
62
- return [Model::TextElement.new(content: '')] if text.nil? || text.to_s.strip.empty?
72
+ text = text.to_s.strip unless style == 'l'
73
+ return [Model::TextElement.new(content: '')] if text.empty?
63
74
 
64
75
  return parse_block_content(text) if style == 'a'
65
76
  return [Model::TextElement.new(content: text.to_s)] if style == 'l'
66
77
 
67
- parser = Coradoc::AsciiDoc::Parser::Base.new
78
+ parser = inline_parser
68
79
  begin
69
- ast = parser.text_any.parse(text.to_s)
80
+ ast = Parser::TreeMaterializer.convert(parser.text_any.parse(text.to_s))
70
81
  transformed = Transformer.new.apply(ast)
71
82
  content_array = transformed.is_a?(Array) ? transformed : [transformed]
72
83
  [Model::TextElement.new(content: content_array)]
@@ -81,7 +92,7 @@ module Coradoc
81
92
  def parse_block_content(text)
82
93
  return [Model::TextElement.new(content: '')] if text.nil? || text.to_s.strip.empty?
83
94
 
84
- parser = Coradoc::AsciiDoc::Parser::Base.new
95
+ parser = inline_parser
85
96
  text_str = text.to_s
86
97
 
87
98
  if /^(\*+|-+|\d+\.)/m.match?(text_str)
@@ -89,14 +100,14 @@ module Coradoc
89
100
  if list_match
90
101
  list_text = list_match[1] + list_match[2]
91
102
  begin
92
- ast = parser.list.parse(list_text)
103
+ ast = Parser::TreeMaterializer.convert(parser.list.parse(list_text))
93
104
  transformed = Transformer.new.apply(ast)
94
105
 
95
106
  before_list = text_str[0, list_match.begin(1) - 1].strip
96
107
  before_elements = []
97
108
  unless before_list.empty?
98
109
  begin
99
- before_ast = parser.text_any.parse(before_list)
110
+ before_ast = Parser::TreeMaterializer.convert(parser.text_any.parse(before_list))
100
111
  before_transformed = Transformer.new.apply(before_ast)
101
112
  before_array = before_transformed.is_a?(Array) ? before_transformed : [before_transformed]
102
113
  before_elements = [Model::TextElement.new(content: before_array)]
@@ -113,7 +124,7 @@ module Coradoc
113
124
  end
114
125
 
115
126
  begin
116
- ast = parser.text_any.parse(text_str)
127
+ ast = Parser::TreeMaterializer.convert(parser.text_any.parse(text_str))
117
128
  transformed = Transformer.new.apply(ast)
118
129
  content_array = transformed.is_a?(Array) ? transformed : [transformed]
119
130
  [Model::TextElement.new(content: content_array)]
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Coradoc
4
4
  module AsciiDoc
5
- VERSION = '2.0.35'
5
+ VERSION = '2.0.36'
6
6
  end
7
7
  end
@@ -32,6 +32,9 @@ module Coradoc
32
32
  autoload :Model, "#{__dir__}/asciidoc/model"
33
33
  autoload :Parser, "#{__dir__}/asciidoc/parser"
34
34
  autoload :Transformer, "#{__dir__}/asciidoc/transformer"
35
+ # Registration side effect: loading registers rules into Coradoc::Lint.registry.
36
+ autoload :Lint, "#{__dir__}/asciidoc/lint"
37
+ autoload :Formatter, "#{__dir__}/asciidoc/formatter"
35
38
  autoload :Serializer, "#{__dir__}/asciidoc/serializer"
36
39
  autoload :Transform, "#{__dir__}/asciidoc/transform"
37
40
  autoload :Builder, "#{__dir__}/asciidoc/builder"
@@ -79,6 +82,15 @@ module Coradoc
79
82
  Transform::ToCoreModel.transform(document)
80
83
  end
81
84
 
85
+ # Canonical-format AsciiDoc text (#110): parse to the model
86
+ # tree, serialize, and normalize inter-block whitespace.
87
+ #
88
+ # @param text [String] AsciiDoc content
89
+ # @return [String] canonically formatted AsciiDoc
90
+ def format(text)
91
+ Formatter.call(text)
92
+ end
93
+
82
94
  # Serialize a document model to AsciiDoc string
83
95
  #
84
96
  # @param document [Coradoc::AsciiDoc::Model::Document, Coradoc::CoreModel::Base]
@@ -50,6 +50,62 @@ module Coradoc
50
50
  result
51
51
  end
52
52
 
53
+ # Escape delimiter characters in plain-text content so the text
54
+ # survives a reparse (#92): `__x__`, `**x**`, `x~2~` and `` `x` ``
55
+ # would otherwise re-enter as formatting. Runs of two or more
56
+ # delimiters (`__`, `**`, `~~`) are dangerous anywhere; a single
57
+ # `*`, `_` or `~` only forms a constrained pair at a
58
+ # boundary/word transition; `^` and `` ` `` attach to words.
59
+ def self.escape_text_delimiters(text)
60
+ return '' if text.nil?
61
+
62
+ text.to_s.gsub(/(?<!\\)[`*_~^]+/) do |run|
63
+ match = Regexp.last_match
64
+ dangerous =
65
+ case run[0]
66
+ when '`'
67
+ true
68
+ when '^'
69
+ word_transition?(text, match)
70
+ else
71
+ run.length >= 2 || word_transition?(text, match)
72
+ end
73
+ dangerous ? run.chars.map { |c| "\\#{c}" }.join : run
74
+ end
75
+ end
76
+
77
+ WHITESPACE = /\s/
78
+ WORD_CHAR = /[[:word:]]/
79
+ PUNCT_CHAR = /[[:punct:]]/
80
+ private_constant :WHITESPACE, :WORD_CHAR, :PUNCT_CHAR
81
+
82
+ # A delimiter run at [start, stop) is dangerous when it can open
83
+ # a pair (boundary on its left, word char on its right) or close
84
+ # one (word char on its left, boundary-or-punctuation on its
85
+ # right — AsciiDoc lets a closing delimiter precede punctuation).
86
+ def self.word_transition?(text, match)
87
+ start = match.begin(0)
88
+ stop = match.end(0)
89
+ before = start.zero? ? nil : text[start - 1]
90
+ after = stop >= text.length ? nil : text[stop]
91
+
92
+ opens = boundary?(before) && word?(after)
93
+ closes = word?(before) && (boundary?(after) || punctuation?(after))
94
+ opens || closes
95
+ end
96
+
97
+ def self.boundary?(char)
98
+ char.nil? || char.match?(WHITESPACE)
99
+ end
100
+
101
+ def self.word?(char)
102
+ !char.nil? && char.match?(WORD_CHAR)
103
+ end
104
+
105
+ def self.punctuation?(char)
106
+ !char.nil? && char.match?(PUNCT_CHAR)
107
+ end
108
+
53
109
  # Unescape AsciiDoc characters in content
54
110
  #
55
111
  # @param content [String] The content to unescape
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.35
4
+ version: 2.0.36
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -41,16 +41,16 @@ dependencies:
41
41
  name: parsanol
42
42
  requirement: !ruby/object:Gem::Requirement
43
43
  requirements:
44
- - - ">="
44
+ - - "~>"
45
45
  - !ruby/object:Gem::Version
46
- version: '0'
46
+ version: 1.3.123
47
47
  type: :runtime
48
48
  prerelease: false
49
49
  version_requirements: !ruby/object:Gem::Requirement
50
50
  requirements:
51
- - - ">="
51
+ - - "~>"
52
52
  - !ruby/object:Gem::Version
53
- version: '0'
53
+ version: 1.3.123
54
54
  - !ruby/object:Gem::Dependency
55
55
  name: rake
56
56
  requirement: !ruby/object:Gem::Requirement
@@ -104,6 +104,8 @@ extra_rdoc_files: []
104
104
  files:
105
105
  - ".rspec"
106
106
  - Rakefile
107
+ - grammar/coradoc-adoc.artifact.json
108
+ - grammar/coradoc-adoc.parg
107
109
  - lib/coradoc/asciidoc.rb
108
110
  - lib/coradoc/asciidoc/builder.rb
109
111
  - lib/coradoc/asciidoc/builder/block_builder.rb
@@ -112,6 +114,12 @@ files:
112
114
  - lib/coradoc/asciidoc/builder/list_builder.rb
113
115
  - lib/coradoc/asciidoc/builder/text_builder.rb
114
116
  - lib/coradoc/asciidoc/delimiter_mapping.rb
117
+ - lib/coradoc/asciidoc/formatter.rb
118
+ - lib/coradoc/asciidoc/lint.rb
119
+ - lib/coradoc/asciidoc/lint/document_title.rb
120
+ - lib/coradoc/asciidoc/lint/duplicate_ids.rb
121
+ - lib/coradoc/asciidoc/lint/empty_section.rb
122
+ - lib/coradoc/asciidoc/lint/heading_levels.rb
115
123
  - lib/coradoc/asciidoc/model.rb
116
124
  - lib/coradoc/asciidoc/model/admonition.rb
117
125
  - lib/coradoc/asciidoc/model/anchorable.rb
@@ -229,12 +237,14 @@ files:
229
237
  - lib/coradoc/asciidoc/parser/list.rb
230
238
  - lib/coradoc/asciidoc/parser/metadata_detector.rb
231
239
  - lib/coradoc/asciidoc/parser/paragraph.rb
240
+ - lib/coradoc/asciidoc/parser/positioned_string.rb
232
241
  - lib/coradoc/asciidoc/parser/rule_dispatcher.rb
233
242
  - lib/coradoc/asciidoc/parser/section.rb
234
243
  - lib/coradoc/asciidoc/parser/stem.rb
235
244
  - lib/coradoc/asciidoc/parser/table.rb
236
245
  - lib/coradoc/asciidoc/parser/term.rb
237
246
  - lib/coradoc/asciidoc/parser/text.rb
247
+ - lib/coradoc/asciidoc/parser/tree_materializer.rb
238
248
  - lib/coradoc/asciidoc/reference_materializers.rb
239
249
  - lib/coradoc/asciidoc/reference_materializers/navigation.rb
240
250
  - lib/coradoc/asciidoc/serializer.rb
@@ -330,6 +340,7 @@ files:
330
340
  - lib/coradoc/asciidoc/transform/element_transformers/other_transformer.rb
331
341
  - lib/coradoc/asciidoc/transform/element_transformers/table_transformer.rb
332
342
  - lib/coradoc/asciidoc/transform/from_core_model.rb
343
+ - lib/coradoc/asciidoc/transform/from_core_model/list_building.rb
333
344
  - lib/coradoc/asciidoc/transform/from_core_model_registrations.rb
334
345
  - lib/coradoc/asciidoc/transform/frontmatter_attribute_map.rb
335
346
  - lib/coradoc/asciidoc/transform/inline_transform_visitor.rb