coradoc-adoc 2.0.9 → 2.0.10
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 +4 -4
- data/lib/coradoc/asciidoc/model/bibliography_entry.rb +18 -0
- data/lib/coradoc/asciidoc/model/document.rb +9 -0
- data/lib/coradoc/asciidoc/model/glossaries.rb +1 -1
- data/lib/coradoc/asciidoc/model/list/base.rb +41 -0
- data/lib/coradoc/asciidoc/model/list/core.rb +4 -24
- data/lib/coradoc/asciidoc/model/list/definition.rb +7 -0
- data/lib/coradoc/asciidoc/model/list/definition_item.rb +1 -1
- data/lib/coradoc/asciidoc/model/list/item.rb +1 -1
- data/lib/coradoc/asciidoc/model/list/nestable.rb +7 -3
- data/lib/coradoc/asciidoc/model/list.rb +4 -2
- data/lib/coradoc/asciidoc/parser/base.rb +10 -70
- data/lib/coradoc/asciidoc/parser/block.rb +3 -22
- data/lib/coradoc/asciidoc/parser/block_assembler.rb +37 -100
- data/lib/coradoc/asciidoc/parser/block_header.rb +55 -0
- data/lib/coradoc/asciidoc/parser/frontmatter_parser.rb +24 -0
- data/lib/coradoc/asciidoc/parser/paragraph.rb +1 -3
- data/lib/coradoc/asciidoc/parser/rule_dispatcher.rb +158 -0
- data/lib/coradoc/asciidoc/parser/section.rb +1 -3
- data/lib/coradoc/asciidoc/parser/table.rb +1 -4
- data/lib/coradoc/asciidoc/parser/text.rb +1 -3
- data/lib/coradoc/asciidoc/parser.rb +1 -0
- data/lib/coradoc/asciidoc/serializer/serializers/base.rb +1 -1
- data/lib/coradoc/asciidoc/serializer/serializers/document.rb +7 -0
- data/lib/coradoc/asciidoc/serializer/serializers/list/definition.rb +3 -1
- data/lib/coradoc/asciidoc/transform/element_transformers/block_transformer.rb +10 -1
- data/lib/coradoc/asciidoc/transform/element_transformers/document_transformer.rb +15 -1
- data/lib/coradoc/asciidoc/transform/element_transformers/other_transformer.rb +3 -1
- data/lib/coradoc/asciidoc/transform/from_core_model.rb +33 -3
- data/lib/coradoc/asciidoc/transform/from_core_model_registrations.rb +5 -1
- data/lib/coradoc/asciidoc/transform/frontmatter_attribute_map.rb +112 -0
- data/lib/coradoc/asciidoc/transform/text_extract_visitor.rb +33 -1
- data/lib/coradoc/asciidoc/transform/to_core_model.rb +10 -2
- data/lib/coradoc/asciidoc/transform/to_core_model_registrations.rb +15 -10
- data/lib/coradoc/asciidoc/transform.rb +1 -0
- data/lib/coradoc/asciidoc/transformer/attribute_list_normalizer.rb +69 -0
- data/lib/coradoc/asciidoc/transformer/block_rules.rb +4 -42
- data/lib/coradoc/asciidoc/transformer/block_type_classifier.rb +56 -0
- data/lib/coradoc/asciidoc/transformer/header_rules.rb +15 -53
- data/lib/coradoc/asciidoc/transformer/inline_rules.rb +39 -57
- data/lib/coradoc/asciidoc/transformer/misc_rules.rb +1 -24
- data/lib/coradoc/asciidoc/transformer/structural_rules.rb +18 -81
- data/lib/coradoc/asciidoc/transformer/table_cell_builder.rb +161 -0
- data/lib/coradoc/asciidoc/transformer/table_layout.rb +135 -0
- data/lib/coradoc/asciidoc/transformer/text_rules.rb +1 -25
- data/lib/coradoc/asciidoc/transformer.rb +38 -294
- data/lib/coradoc/asciidoc/version.rb +1 -1
- data/lib/coradoc/asciidoc.rb +6 -3
- metadata +10 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 78eaf011433933b47142179dd46e5e1176fe7f0686b2e803b1c35a0ed8f0fc93
|
|
4
|
+
data.tar.gz: 39f23d77a5df2807f35e895e751bbfa0e4030e34a6d1c6372007061fa211f07d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 358a5c0d2562840abc484421446110c917112f68515eb22c9bd1d69c6cafe18832a1ffb3a71c37df8640b309e4aa9ec7b7e544393ed795ee63b894c7a1bd504d
|
|
7
|
+
data.tar.gz: aed852abae11e22ef32fbb764f9c09d2334c24ef3f1e0c9bd23fe4c5295ddf2b15adeca603713f732a3ffdb2250bf3a9def286fd6ecf563c9dc5f840cc251f23
|
|
@@ -32,6 +32,24 @@ module Coradoc
|
|
|
32
32
|
attribute :document_id, :string
|
|
33
33
|
attribute :ref_text, :string
|
|
34
34
|
attribute :line_break, :string, default: -> { '' }
|
|
35
|
+
|
|
36
|
+
# Coerce a raw parser AST value into the canonical ref_text string.
|
|
37
|
+
# Accepts the shapes produced by Parser::Bibliography for `:ref_text`:
|
|
38
|
+
# nil, Parslet::Slice, plain String, single Model::Base, or an Array
|
|
39
|
+
# of any of these. Keeping this coercion on the model that owns
|
|
40
|
+
# ref_text (rather than in a transformer rule) keeps the transformer
|
|
41
|
+
# declarative and lets callers build entries from any source shape.
|
|
42
|
+
# @param raw [Object, nil]
|
|
43
|
+
# @return [String]
|
|
44
|
+
def self.coerce_ref_text(raw)
|
|
45
|
+
return '' if raw.nil?
|
|
46
|
+
|
|
47
|
+
case raw
|
|
48
|
+
when Array then raw.map { |e| coerce_ref_text(e) }.join
|
|
49
|
+
when String then raw
|
|
50
|
+
else raw.to_s
|
|
51
|
+
end
|
|
52
|
+
end
|
|
35
53
|
end
|
|
36
54
|
end
|
|
37
55
|
end
|
|
@@ -69,6 +69,15 @@ module Coradoc
|
|
|
69
69
|
Coradoc::AsciiDoc::Model::Video
|
|
70
70
|
]
|
|
71
71
|
|
|
72
|
+
# Raw YAML frontmatter text, or nil if absent.
|
|
73
|
+
#
|
|
74
|
+
# AsciiDoc treats frontmatter as opaque text — the YAML is only
|
|
75
|
+
# parsed/emitted at the CoreModel boundary by
|
|
76
|
+
# CoreModel::FrontmatterBlock::Codec (single source of truth).
|
|
77
|
+
# Storing raw text keeps the AsciiDoc parser/serializer symmetric
|
|
78
|
+
# without dragging YAML semantics into the AsciiDoc model (MECE).
|
|
79
|
+
attribute :frontmatter, :string
|
|
80
|
+
|
|
72
81
|
# @param [Integer] index The index of the section to retrieve
|
|
73
82
|
# @return [Coradoc::AsciiDoc::Model::Base] The section at the specified index
|
|
74
83
|
def [](index)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module AsciiDoc
|
|
5
|
+
module Model
|
|
6
|
+
module List
|
|
7
|
+
# Shared base class for all list container types.
|
|
8
|
+
#
|
|
9
|
+
# Every list flavor (Core, Definition, and any future ones) inherits
|
|
10
|
+
# the universal list-level attributes from this class:
|
|
11
|
+
#
|
|
12
|
+
# - +id+ optional anchor identifier (inherited from Model::Base)
|
|
13
|
+
# - +attrs+ block attribute list, e.g. +[%hardbreaks]+ or +[#my-id]+
|
|
14
|
+
#
|
|
15
|
+
# Subclasses declare their own +items+ with the appropriate item type
|
|
16
|
+
# (List::Item for ordered/unordered, List::DefinitionItem for definition)
|
|
17
|
+
# plus any flavor-specific attributes (marker, prefix, delimiter, ...).
|
|
18
|
+
#
|
|
19
|
+
# @!attribute [r] attrs
|
|
20
|
+
# @return [Coradoc::AsciiDoc::Model::AttributeList] Additional list
|
|
21
|
+
# attributes parsed from the +[...]+ block header preceding the list
|
|
22
|
+
class Base < Coradoc::AsciiDoc::Model::Base
|
|
23
|
+
include Coradoc::AsciiDoc::Model::Anchorable
|
|
24
|
+
|
|
25
|
+
attribute :attrs,
|
|
26
|
+
Coradoc::AsciiDoc::Model::AttributeList,
|
|
27
|
+
default: -> { Coradoc::AsciiDoc::Model::AttributeList.new }
|
|
28
|
+
|
|
29
|
+
asciidoc do
|
|
30
|
+
map_attribute 'id', to: :id
|
|
31
|
+
map_attribute 'attrs', to: :attrs
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def block_level?
|
|
35
|
+
true
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -4,21 +4,17 @@ module Coradoc
|
|
|
4
4
|
module AsciiDoc
|
|
5
5
|
module Model
|
|
6
6
|
module List
|
|
7
|
-
# Base class for
|
|
7
|
+
# Base class for ordered/unordered lists.
|
|
8
8
|
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
9
|
+
# Inherits universal list attributes (id, attrs) from List::Base and
|
|
10
|
+
# adds the marker-related attributes specific to bulleted/numbered lists.
|
|
11
11
|
#
|
|
12
|
-
# @!attribute [r] id
|
|
13
|
-
# @return [String, nil] Optional identifier for the list
|
|
14
12
|
# @!attribute [r] prefix
|
|
15
|
-
# @return [String, nil] List marker prefix (e.g., "*", "
|
|
13
|
+
# @return [String, nil] List marker prefix (e.g., "*", "**", etc.)
|
|
16
14
|
# @!attribute [r] items
|
|
17
15
|
# @return [Array<ListItem>] List items in this list
|
|
18
16
|
# @!attribute [r] ol_count
|
|
19
17
|
# @return [Integer] Ordered list nesting level
|
|
20
|
-
# @!attribute [r] attrs
|
|
21
|
-
# @return [AttributeList] Additional list attributes
|
|
22
18
|
# @!attribute [r] marker
|
|
23
19
|
# @return [String, nil] The marker character used for this list
|
|
24
20
|
#
|
|
@@ -27,31 +23,15 @@ module Coradoc
|
|
|
27
23
|
# list.items << Coradoc::AsciiDoc::Model::List::Item.new("Item 1")
|
|
28
24
|
#
|
|
29
25
|
class Core < Nestable
|
|
30
|
-
include Coradoc::AsciiDoc::Model::Anchorable
|
|
31
|
-
|
|
32
|
-
def block_level?
|
|
33
|
-
true
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
attribute :id, :string
|
|
37
26
|
attribute :prefix, :string
|
|
38
|
-
# attribute :anchor, Inline::Anchor, default: -> {
|
|
39
|
-
# id.nil? ? nil : Inline::Anchor.new(id)
|
|
40
|
-
# }
|
|
41
27
|
attribute :items, Coradoc::AsciiDoc::Model::List::Item, collection: true, initialize_empty: true
|
|
42
28
|
attribute :ol_count, :integer, default: -> { 1 }
|
|
43
|
-
attribute :attrs, Coradoc::AsciiDoc::Model::AttributeList, default: lambda {
|
|
44
|
-
Coradoc::AsciiDoc::Model::AttributeList.new
|
|
45
|
-
}
|
|
46
29
|
attribute :marker, :string
|
|
47
30
|
|
|
48
31
|
asciidoc do
|
|
49
|
-
map_attribute 'id', to: :id
|
|
50
|
-
map_attribute 'anchor', to: :anchor
|
|
51
32
|
map_attribute 'prefix', to: :prefix
|
|
52
33
|
map_attribute 'items', to: :items
|
|
53
34
|
map_attribute 'ol_count', to: :ol_count
|
|
54
|
-
map_attribute 'attrs', to: :attrs
|
|
55
35
|
map_attribute 'marker', to: :marker
|
|
56
36
|
end
|
|
57
37
|
end
|
|
@@ -4,6 +4,13 @@ module Coradoc
|
|
|
4
4
|
module AsciiDoc
|
|
5
5
|
module Model
|
|
6
6
|
module List
|
|
7
|
+
# Definition list container. Inherits universal list attributes
|
|
8
|
+
# (id, attrs) from List::Base.
|
|
9
|
+
#
|
|
10
|
+
# @!attribute [r] items
|
|
11
|
+
# @return [Array<DefinitionItem>] Definition items in this list
|
|
12
|
+
# @!attribute [r] delimiter
|
|
13
|
+
# @return [String] Delimiter indicating nesting depth ('::', ':::', ...)
|
|
7
14
|
class Definition < Base
|
|
8
15
|
attribute :items,
|
|
9
16
|
Coradoc::AsciiDoc::Model::Base,
|
|
@@ -26,7 +26,7 @@ module Coradoc
|
|
|
26
26
|
# item.terms << Coradoc::AsciiDoc::Model::Term.new(term: "API")
|
|
27
27
|
# item.contents << Coradoc::AsciiDoc::Model::TextElement.new("Application Programming Interface")
|
|
28
28
|
#
|
|
29
|
-
class DefinitionItem < Base
|
|
29
|
+
class DefinitionItem < Coradoc::AsciiDoc::Model::Base
|
|
30
30
|
include Coradoc::AsciiDoc::Model::Anchorable
|
|
31
31
|
|
|
32
32
|
attribute :id, :string
|
|
@@ -40,7 +40,7 @@ module Coradoc
|
|
|
40
40
|
# item.nested = Coradoc::AsciiDoc::Model::List::Unordered.new
|
|
41
41
|
# item.nested.items << Coradoc::AsciiDoc::Model::List::Item.new
|
|
42
42
|
#
|
|
43
|
-
class Item < Base
|
|
43
|
+
class Item < Coradoc::AsciiDoc::Model::Base
|
|
44
44
|
include Coradoc::AsciiDoc::Model::Anchorable
|
|
45
45
|
|
|
46
46
|
attribute :id, :string
|
|
@@ -4,9 +4,13 @@ module Coradoc
|
|
|
4
4
|
module AsciiDoc
|
|
5
5
|
module Model
|
|
6
6
|
module List
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
|
|
7
|
+
# Marker class for lists that can be nested inside a List::Item
|
|
8
|
+
# via its +nested+ attribute. Inherits universal list attributes
|
|
9
|
+
# (id, attrs) from List::Base.
|
|
10
|
+
#
|
|
11
|
+
# List::Definition does not extend Nestable because it has its own
|
|
12
|
+
# nesting model via List::DefinitionItem#nested.
|
|
13
|
+
class Nestable < Base
|
|
10
14
|
end
|
|
11
15
|
end
|
|
12
16
|
end
|
|
@@ -6,16 +6,18 @@ module Coradoc
|
|
|
6
6
|
# Namespace for all AsciiDoc list types and their items.
|
|
7
7
|
#
|
|
8
8
|
# List Architecture:
|
|
9
|
-
# - List::
|
|
9
|
+
# - List::Base - Universal list attributes (id, attrs)
|
|
10
|
+
# - List::Nestable - Marker class for lists nestable inside Item
|
|
11
|
+
# - List::Core - Ordered/unordered list base (marker, prefix, ol_count)
|
|
10
12
|
# - List::Ordered - Numbered lists (1., 2., 3., etc.)
|
|
11
13
|
# - List::Unordered - Bulleted lists (*, **, etc.)
|
|
12
14
|
# - List::Definition - Labeled/definition lists (term:: definition)
|
|
13
15
|
# - List::Item - Item for ordered/unordered lists
|
|
14
16
|
# - List::DefinitionItem - Item for definition lists
|
|
15
|
-
# - List::Nestable - Mixin for nesting support
|
|
16
17
|
#
|
|
17
18
|
module List
|
|
18
19
|
# Autoload list types lazily
|
|
20
|
+
autoload :Base, 'coradoc/asciidoc/model/list/base'
|
|
19
21
|
autoload :Core, 'coradoc/asciidoc/model/list/core'
|
|
20
22
|
autoload :Nestable, 'coradoc/asciidoc/model/list/nestable'
|
|
21
23
|
autoload :Ordered, 'coradoc/asciidoc/model/list/ordered'
|
|
@@ -11,6 +11,7 @@ module Coradoc
|
|
|
11
11
|
autoload :AttributeList, 'coradoc/asciidoc/parser/attribute_list'
|
|
12
12
|
autoload :Bibliography, 'coradoc/asciidoc/parser/bibliography'
|
|
13
13
|
autoload :Block, 'coradoc/asciidoc/parser/block'
|
|
14
|
+
autoload :BlockHeader, 'coradoc/asciidoc/parser/block_header'
|
|
14
15
|
autoload :Citation, 'coradoc/asciidoc/parser/citation'
|
|
15
16
|
autoload :Content, 'coradoc/asciidoc/parser/content'
|
|
16
17
|
autoload :DocumentAttributes, 'coradoc/asciidoc/parser/document_attributes'
|
|
@@ -18,6 +19,7 @@ module Coradoc
|
|
|
18
19
|
autoload :Inline, 'coradoc/asciidoc/parser/inline'
|
|
19
20
|
autoload :List, 'coradoc/asciidoc/parser/list'
|
|
20
21
|
autoload :Paragraph, 'coradoc/asciidoc/parser/paragraph'
|
|
22
|
+
autoload :RuleDispatcher, 'coradoc/asciidoc/parser/rule_dispatcher'
|
|
21
23
|
autoload :Section, 'coradoc/asciidoc/parser/section'
|
|
22
24
|
autoload :Table, 'coradoc/asciidoc/parser/table'
|
|
23
25
|
autoload :Term, 'coradoc/asciidoc/parser/term'
|
|
@@ -29,6 +31,7 @@ module Coradoc
|
|
|
29
31
|
include AttributeList
|
|
30
32
|
include Bibliography
|
|
31
33
|
include Block
|
|
34
|
+
include BlockHeader
|
|
32
35
|
include Citation
|
|
33
36
|
include Content
|
|
34
37
|
include DocumentAttributes
|
|
@@ -82,78 +85,15 @@ module Coradoc
|
|
|
82
85
|
warn e.parse_failure_cause.ascii_tree
|
|
83
86
|
end
|
|
84
87
|
|
|
85
|
-
def rule_dispatch(rule_name,
|
|
86
|
-
|
|
87
|
-
dispatch_key = [rule_name, args, kwargs.to_a.sort]
|
|
88
|
-
dispatch_hash = dispatch_key.hash.abs
|
|
89
|
-
unless @dispatch_data.key?(dispatch_hash)
|
|
90
|
-
alias_name = :"#{rule_name}_#{dispatch_hash}"
|
|
91
|
-
Coradoc::AsciiDoc::Parser::Base.class_exec do
|
|
92
|
-
rule(alias_name) do
|
|
93
|
-
public_send(rule_name, *args, **kwargs)
|
|
94
|
-
end
|
|
95
|
-
end
|
|
96
|
-
@dispatch_data[dispatch_hash] = alias_name
|
|
97
|
-
end
|
|
98
|
-
dispatch_method = @dispatch_data[dispatch_hash]
|
|
99
|
-
public_send(dispatch_method)
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def self.config(key)
|
|
103
|
-
# NOTE: These are internal dispatch configuration options for the parser:
|
|
104
|
-
# - add_dispatch: Enables automatic method dispatching
|
|
105
|
-
# - with_params: Supports parameterized rule invocation
|
|
106
|
-
c = {
|
|
107
|
-
add_dispatch: true,
|
|
108
|
-
with_params: true
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
raise ArgumentError, "Unknown config key: #{key}. Available keys: #{c.keys.join(', ')}" unless c.key?(key)
|
|
112
|
-
|
|
113
|
-
c[key]
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
# Collect parser methods from all parser modules (excluding Base, Cache, and FixFiles)
|
|
117
|
-
# Base is the parser class, Cache is a utility class, FixFiles is a utility module
|
|
118
|
-
parser_constants = Coradoc::AsciiDoc::Parser.constants - %i[Base Cache FixFiles]
|
|
119
|
-
parser_methods = parser_constants.each_with_object({}) do |const, acc|
|
|
120
|
-
rule_names = Coradoc::AsciiDoc::Parser.const_get(const).instance_methods
|
|
121
|
-
rule_names.each do |rule_name|
|
|
122
|
-
acc[rule_name] ||= []
|
|
123
|
-
acc[rule_name] << const
|
|
124
|
-
end
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
# Warn about duplicated parser methods:
|
|
128
|
-
parser_methods.each do |rule_name, defn_sites|
|
|
129
|
-
count = defn_sites.length
|
|
130
|
-
if count > 1
|
|
131
|
-
defn_site_constants = defn_sites.map { |const| Coradoc::AsciiDoc::Parser.const_get(const) }
|
|
132
|
-
Coradoc::Logger.warn "Parser method '#{rule_name}' is defined #{count} times in #{defn_site_constants.join(', ')}"
|
|
133
|
-
end
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
parser_methods.each_key do |rule_name|
|
|
137
|
-
params = Coradoc::AsciiDoc::Parser::Base.instance_method(rule_name).parameters
|
|
138
|
-
if config(:add_dispatch) && params == []
|
|
139
|
-
alias_name = :"alias_nondispatch_#{rule_name}"
|
|
140
|
-
Coradoc::AsciiDoc::Parser::Base.class_exec do
|
|
141
|
-
alias_method alias_name, rule_name
|
|
142
|
-
rule(rule_name) do
|
|
143
|
-
public_send(alias_name)
|
|
144
|
-
end
|
|
145
|
-
end
|
|
146
|
-
elsif config(:add_dispatch) && config(:with_params)
|
|
147
|
-
alias_name = :"alias_dispatch_#{rule_name}"
|
|
148
|
-
Coradoc::AsciiDoc::Parser::Base.class_exec do
|
|
149
|
-
alias_method alias_name, rule_name
|
|
150
|
-
define_method(rule_name) do |*args, **kwargs|
|
|
151
|
-
rule_dispatch(alias_name, *args, **kwargs)
|
|
152
|
-
end
|
|
153
|
-
end
|
|
154
|
-
end
|
|
88
|
+
def rule_dispatch(rule_name, *, **)
|
|
89
|
+
RuleDispatcher.dispatch(self, rule_name, *, **)
|
|
155
90
|
end
|
|
156
91
|
end
|
|
92
|
+
|
|
93
|
+
# Wrap every parser rule for Parslet memoization. Must run after all
|
|
94
|
+
# parser modules are included in Base so that instance_method(rule_name)
|
|
95
|
+
# finds the methods defined by every module.
|
|
96
|
+
RuleDispatcher.apply(Base)
|
|
157
97
|
end
|
|
158
98
|
end
|
|
159
99
|
end
|
|
@@ -30,14 +30,6 @@ module Coradoc
|
|
|
30
30
|
open_block(n_deep)).as(:block)
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
-
def reviewer_note_block(_n_deep = 3)
|
|
34
|
-
# Match blocks with reviewer attribute
|
|
35
|
-
# This should only match when attribute_list contains reviewer=
|
|
36
|
-
# For now, we'll make it not match anything specific
|
|
37
|
-
# The block() method will handle these cases
|
|
38
|
-
str('').absent? # Never matches - placeholder for future implementation
|
|
39
|
-
end
|
|
40
|
-
|
|
41
33
|
def example_block(n_deep)
|
|
42
34
|
block_style(n_deep, '=', 4)
|
|
43
35
|
end
|
|
@@ -83,6 +75,7 @@ module Coradoc
|
|
|
83
75
|
end
|
|
84
76
|
|
|
85
77
|
# Block delimiter: 4+ identical characters (or 2 for open block)
|
|
78
|
+
# Used by paragraph.rb to reject lines that look like block delimiters.
|
|
86
79
|
# NOTE: repeat(4,) means 4 or more (not exactly 4)
|
|
87
80
|
def block_delimiter
|
|
88
81
|
line_start? >>
|
|
@@ -95,16 +88,6 @@ module Coradoc
|
|
|
95
88
|
newline
|
|
96
89
|
end
|
|
97
90
|
|
|
98
|
-
def element_attributes
|
|
99
|
-
block_title.maybe >>
|
|
100
|
-
element_id.maybe >>
|
|
101
|
-
(attribute_list >> newline).maybe >>
|
|
102
|
-
block_title.maybe >>
|
|
103
|
-
newline.maybe >>
|
|
104
|
-
(attribute_list >> newline).maybe >>
|
|
105
|
-
element_id.maybe
|
|
106
|
-
end
|
|
107
|
-
|
|
108
91
|
# Block style parser with variable delimiter length
|
|
109
92
|
# @param n_deep [Integer] Nesting depth for nested blocks
|
|
110
93
|
# @param delimiter [String] The delimiter character ("=", "-", "_", "*", "+")
|
|
@@ -130,8 +113,7 @@ module Coradoc
|
|
|
130
113
|
(closing_pattern.absent? >> content).repeat(1)
|
|
131
114
|
end
|
|
132
115
|
|
|
133
|
-
|
|
134
|
-
(line_start? >> attribute_list >> newline).maybe >>
|
|
116
|
+
block_header >>
|
|
135
117
|
line_start? >>
|
|
136
118
|
current_delimiter.as(:delimiter) >> newline >>
|
|
137
119
|
if type == :pass
|
|
@@ -165,8 +147,7 @@ module Coradoc
|
|
|
165
147
|
(closing_pattern.absent? >> content).repeat(1)
|
|
166
148
|
end
|
|
167
149
|
|
|
168
|
-
|
|
169
|
-
(line_start? >> attribute_list >> newline).maybe >>
|
|
150
|
+
block_header >>
|
|
170
151
|
line_start? >>
|
|
171
152
|
current_delimiter.as(:delimiter) >> newline >>
|
|
172
153
|
if type == :pass
|
|
@@ -6,112 +6,31 @@ module Coradoc
|
|
|
6
6
|
# Single Responsibility: Assemble block AST from metadata hints
|
|
7
7
|
# Takes metadata analysis and input text, returns proper AST structure
|
|
8
8
|
class BlockAssembler
|
|
9
|
-
# Main entry point: assemble block AST from input and metadata
|
|
9
|
+
# Main entry point: assemble block AST from input and metadata.
|
|
10
|
+
#
|
|
11
|
+
# The result hash always includes :delimiter and :lines. :title and
|
|
12
|
+
# :attribute_list are included only when present in the metadata.
|
|
13
|
+
# The previous 4-arm `case pattern` switch collapsed into this single
|
|
14
|
+
# method once it became clear the only difference between arms was
|
|
15
|
+
# which optional keys appeared in the result hash.
|
|
16
|
+
#
|
|
10
17
|
# @param input [String] The input text to parse
|
|
11
|
-
# @param
|
|
12
|
-
# @return [Hash] AST hash
|
|
13
|
-
def self.assemble(input,
|
|
14
|
-
return nil unless
|
|
15
|
-
|
|
16
|
-
pattern = metadata_analysis[:pattern]
|
|
17
|
-
|
|
18
|
-
# Delegate to pattern-specific methods (Open/Closed Principle)
|
|
19
|
-
case pattern
|
|
20
|
-
when :title_attr_delim
|
|
21
|
-
assemble_title_attr_delim(input, metadata_analysis)
|
|
22
|
-
when :title_delim
|
|
23
|
-
assemble_title_delim(input, metadata_analysis)
|
|
24
|
-
when :attr_delim
|
|
25
|
-
assemble_attr_delim(input, metadata_analysis)
|
|
26
|
-
when :plain_delim
|
|
27
|
-
assemble_plain_delim(input, metadata_analysis)
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
# Handle: Title + Attribute + Delimiter pattern
|
|
32
|
-
# @param input [String] The input text
|
|
33
|
-
# @param metadata [Hash] Metadata analysis
|
|
34
|
-
# @return [Hash] Complete block hash
|
|
35
|
-
def self.assemble_title_attr_delim(input, metadata)
|
|
36
|
-
lines = input.lines
|
|
37
|
-
delimiter_line = metadata[:delimiter_line]
|
|
38
|
-
delimiter = metadata[:delimiter][:delimiter]
|
|
39
|
-
|
|
40
|
-
# Extract components
|
|
41
|
-
title_text = metadata[:title][:text]
|
|
42
|
-
attr_list = parse_attribute_list(metadata[:attributes])
|
|
43
|
-
|
|
44
|
-
# Extract block content
|
|
45
|
-
block_lines = extract_block_lines(lines, delimiter_line, delimiter)
|
|
46
|
-
|
|
47
|
-
{
|
|
48
|
-
title: title_text,
|
|
49
|
-
attribute_list: attr_list,
|
|
50
|
-
delimiter: delimiter,
|
|
51
|
-
lines: block_lines
|
|
52
|
-
}
|
|
53
|
-
end
|
|
18
|
+
# @param metadata [Hash] Analysis from MetadataDetector
|
|
19
|
+
# @return [Hash, nil] AST hash, or nil if metadata is nil
|
|
20
|
+
def self.assemble(input, metadata)
|
|
21
|
+
return nil unless metadata
|
|
54
22
|
|
|
55
|
-
# Handle: Title + Delimiter pattern (no attributes)
|
|
56
|
-
# @param input [String] The input text
|
|
57
|
-
# @param metadata [Hash] Metadata analysis
|
|
58
|
-
# @return [Hash] Block hash without attributes
|
|
59
|
-
def self.assemble_title_delim(input, metadata)
|
|
60
23
|
lines = input.lines
|
|
61
24
|
delimiter_line = metadata[:delimiter_line]
|
|
62
25
|
delimiter = metadata[:delimiter][:delimiter]
|
|
63
26
|
|
|
64
|
-
|
|
65
|
-
title_text = metadata[:title][:text]
|
|
66
|
-
|
|
67
|
-
# Extract block content
|
|
68
|
-
block_lines = extract_block_lines(lines, delimiter_line, delimiter)
|
|
69
|
-
|
|
70
|
-
{
|
|
71
|
-
title: title_text,
|
|
27
|
+
result = {
|
|
72
28
|
delimiter: delimiter,
|
|
73
|
-
lines:
|
|
74
|
-
}
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
# Handle: Attribute + Delimiter pattern (no title)
|
|
78
|
-
# @param input [String] The input text
|
|
79
|
-
# @param metadata [Hash] Metadata analysis
|
|
80
|
-
# @return [Hash] Block hash without title
|
|
81
|
-
def self.assemble_attr_delim(input, metadata)
|
|
82
|
-
lines = input.lines
|
|
83
|
-
delimiter_line = metadata[:delimiter_line]
|
|
84
|
-
delimiter = metadata[:delimiter][:delimiter]
|
|
85
|
-
|
|
86
|
-
# Extract components
|
|
87
|
-
attr_list = parse_attribute_list(metadata[:attributes])
|
|
88
|
-
|
|
89
|
-
# Extract block content
|
|
90
|
-
block_lines = extract_block_lines(lines, delimiter_line, delimiter)
|
|
91
|
-
|
|
92
|
-
{
|
|
93
|
-
attribute_list: attr_list,
|
|
94
|
-
delimiter: delimiter,
|
|
95
|
-
lines: block_lines
|
|
96
|
-
}
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
# Handle: Just Delimiter pattern
|
|
100
|
-
# @param input [String] The input text
|
|
101
|
-
# @param metadata [Hash] Metadata analysis
|
|
102
|
-
# @return [Hash] Minimal block hash
|
|
103
|
-
def self.assemble_plain_delim(input, metadata)
|
|
104
|
-
lines = input.lines
|
|
105
|
-
delimiter_line = metadata[:delimiter_line]
|
|
106
|
-
delimiter = metadata[:delimiter][:delimiter]
|
|
107
|
-
|
|
108
|
-
# Extract block content
|
|
109
|
-
block_lines = extract_block_lines(lines, delimiter_line, delimiter)
|
|
110
|
-
|
|
111
|
-
{
|
|
112
|
-
delimiter: delimiter,
|
|
113
|
-
lines: block_lines
|
|
29
|
+
lines: extract_block_lines(lines, delimiter_line, delimiter)
|
|
114
30
|
}
|
|
31
|
+
result[:title] = metadata[:title][:text] if metadata[:title]
|
|
32
|
+
result[:attribute_list] = parse_attribute_list(metadata[:attributes]) if metadata[:attributes]
|
|
33
|
+
result
|
|
115
34
|
end
|
|
116
35
|
|
|
117
36
|
# Helper: Extract content between delimiters
|
|
@@ -132,8 +51,16 @@ module Coradoc
|
|
|
132
51
|
# Check if this is the closing delimiter
|
|
133
52
|
break if line.strip == delimiter
|
|
134
53
|
|
|
135
|
-
|
|
136
|
-
if
|
|
54
|
+
stripped = line.strip
|
|
55
|
+
if nested_delimiter?(stripped) && stripped != delimiter
|
|
56
|
+
nested_delimiter = stripped
|
|
57
|
+
nested_lines = extract_block_lines(lines, i, nested_delimiter)
|
|
58
|
+
|
|
59
|
+
block_lines << { block: { delimiter: nested_delimiter, lines: nested_lines } }
|
|
60
|
+
|
|
61
|
+
i += nested_lines.length + 1
|
|
62
|
+
elsif line.strip.empty?
|
|
63
|
+
# Handle empty lines vs content lines
|
|
137
64
|
block_lines << { line_break: "\n" }
|
|
138
65
|
else
|
|
139
66
|
# Remove trailing newline for processing
|
|
@@ -147,6 +74,16 @@ module Coradoc
|
|
|
147
74
|
block_lines
|
|
148
75
|
end
|
|
149
76
|
|
|
77
|
+
def self.nested_delimiter?(str)
|
|
78
|
+
return false if str.length < 2
|
|
79
|
+
|
|
80
|
+
char = str[0]
|
|
81
|
+
return false unless ['-', '*', '=', '_', '+'].include?(char)
|
|
82
|
+
return false unless str.chars.all? { |c| c == char }
|
|
83
|
+
|
|
84
|
+
(char == '-' && str.length == 2) || str.length >= 4
|
|
85
|
+
end
|
|
86
|
+
|
|
150
87
|
# Parse attribute list to match expected AST structure
|
|
151
88
|
# @param attr_meta [Hash] Attribute metadata from detector
|
|
152
89
|
# @return [Hash] Properly formatted attribute_list hash
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module AsciiDoc
|
|
5
|
+
module Parser
|
|
6
|
+
# Single Responsibility: parse the optional header that precedes a block.
|
|
7
|
+
#
|
|
8
|
+
# Encapsulates the canonical AsciiDoc block-header grammar in one place
|
|
9
|
+
# (DRY, MECE, single source of truth). Before this module existed, every
|
|
10
|
+
# block-like rule (block, table, section, paragraph, block_image) inlined
|
|
11
|
+
# its own header rule with subtly different slot orderings — and several
|
|
12
|
+
# of them captured the same Parslet key more than once in a single
|
|
13
|
+
# sequence, which triggered Parslet's "Duplicate subtrees while merging
|
|
14
|
+
# result" warning and silently discarded one of the captured values.
|
|
15
|
+
#
|
|
16
|
+
# Canonical header shape, each component at most once:
|
|
17
|
+
#
|
|
18
|
+
# block_title? >> element_id? >> attribute_blocks?
|
|
19
|
+
#
|
|
20
|
+
# `attribute_blocks` accepts one or more consecutive `[...]` attribute
|
|
21
|
+
# lists, captured as a Parslet sequence under the :attribute_list key.
|
|
22
|
+
# Real-world AsciiDoc often stacks attribute lists before a block:
|
|
23
|
+
#
|
|
24
|
+
# [role=quote]
|
|
25
|
+
# [source, ruby]
|
|
26
|
+
# ----
|
|
27
|
+
# code
|
|
28
|
+
# ----
|
|
29
|
+
#
|
|
30
|
+
# Capturing the sequence (rather than one slot per `[...]`) preserves
|
|
31
|
+
# every attribute and lets the transformer merge them into a single
|
|
32
|
+
# Coradoc::AsciiDoc::Model::AttributeList downstream.
|
|
33
|
+
module BlockHeader
|
|
34
|
+
# Canonical block header rule. Single canonical order; each of title,
|
|
35
|
+
# id, and attribute_blocks is optional and matched at most once.
|
|
36
|
+
# @return [Parslet::Atoms::Base]
|
|
37
|
+
def block_header
|
|
38
|
+
block_title.maybe >>
|
|
39
|
+
element_id.maybe >>
|
|
40
|
+
attribute_blocks.maybe
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# One or more consecutive attribute_list + newline sequences, captured
|
|
44
|
+
# as a Parslet sequence under :attribute_list. When multiple `[...]`
|
|
45
|
+
# blocks precede a delimiter, all of them reach the transformer; when
|
|
46
|
+
# only one appears, the sequence has a single element and the existing
|
|
47
|
+
# transformer rule handles it the same way as before.
|
|
48
|
+
# @return [Parslet::Atoms::Base]
|
|
49
|
+
def attribute_blocks
|
|
50
|
+
(attribute_list >> newline).repeat(1).as(:attribute_list)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module AsciiDoc
|
|
5
|
+
module Parser
|
|
6
|
+
# AsciiDoc frontmatter extractor.
|
|
7
|
+
#
|
|
8
|
+
# Delegates to the shared +Coradoc::CoreModel::FrontmatterBlock::TextSplitter+
|
|
9
|
+
# — the single source of truth for the `---\n...\n---\n` convention
|
|
10
|
+
# (DRY). AsciiDoc retains a local parser module for discoverability
|
|
11
|
+
# and as the seam for any format-specific extensions should they arise
|
|
12
|
+
# (e.g., recognizing AsciiDoc-style front matter variants).
|
|
13
|
+
module FrontmatterParser
|
|
14
|
+
class << self
|
|
15
|
+
def call(text)
|
|
16
|
+
Coradoc::CoreModel::FrontmatterBlock::TextSplitter.call(text)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
Result = Coradoc::CoreModel::FrontmatterBlock::TextSplitter::Result
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -40,9 +40,7 @@ module Coradoc
|
|
|
40
40
|
# rubocop:enable Style/OptionalBooleanParameter, Style/NumericPredicate
|
|
41
41
|
|
|
42
42
|
def paragraph
|
|
43
|
-
(
|
|
44
|
-
block_title.maybe >>
|
|
45
|
-
(attribute_list >> newline).maybe >>
|
|
43
|
+
(block_header >>
|
|
46
44
|
((paragraph_text_line(0).repeat(1, 1) >>
|
|
47
45
|
(newline.repeat(1).as(:line_break) | eof?)) |
|
|
48
46
|
(paragraph_text_line(false).repeat(1) >>
|