coradoc-mirror 0.1.1 → 0.1.2

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: 6ac58d41a9214d72f32b3bcc107ddaf463c772598cd9be21e267c56b4dcfee8e
4
- data.tar.gz: bfd5bce725a65f86cf449cd4e59e9c78539968742237b002d2726dfdc3b37f52
3
+ metadata.gz: f3f7f901c9228258999b71326b31ee0af3950fd176c0c5f7c4701e0e0790ca00
4
+ data.tar.gz: 3135226a74f13af90415fe808e1320199276112d0bb1adb7e147600604c36f00
5
5
  SHA512:
6
- metadata.gz: 264dba99b8eecd0e218f52d30dd6498a21b5248c640e837dc5ee4140df6b6f64e7416f75b6c8905c032d4c5fa9a078fbfdfc026c1b3160f0f943f930e5ff6e61
7
- data.tar.gz: 51639279c428a6e1d1707bb922bd6da2d59576e96d0cfc7d266eef3de96281e3b2dca61fa63afee3fd3bf920799c7258fc1e40ab59782ee6ec06a5dbe2626340
6
+ metadata.gz: 62ea64db65b369ebd32a473e2bd43db96a53b883ca0d30c506660f13f22baa21b08968706ebace768799907cd68b120eb2292774f68cd3272f84865174f77395
7
+ data.tar.gz: e277c3ef10c119f6e2ecb2899b96f94140ccf155abba5588dab6ab58d405c66ce7bd1e0e0f5ac1dc85eae0c448d1c470a95b9b51e9d0166019b42dfe20dd2adf
@@ -48,12 +48,17 @@ module Coradoc
48
48
  )
49
49
  end
50
50
 
51
- # Partitions flat doc children into [preface?, sections?, *bibliography,
52
- # *trailing] per the @metanorma/mirror JS structural contract. See
53
- # Partitioner for the bucketing rules.
51
+ # Partitions flat doc children into [*metadata, preface?, sections?,
52
+ # *bibliography, *trailing] per the @metanorma/mirror JS structural
53
+ # contract. See Partitioner for the bucketing rules.
54
+ #
55
+ # Metadata blocks (frontmatter) are prepended verbatim so consumers
56
+ # like FrontmatterQuery can find them by walking content[0..n], and
57
+ # renderers can skip them via their type ('frontmatter').
54
58
  def wrap_structural(children)
55
59
  partitioned = Partitioner.partition(children)
56
60
  wrapped = []
61
+ wrapped.concat(partitioned[:metadata])
57
62
  wrapped << Node::Preamble.new(content: partitioned[:preface]) if partitioned[:preface].any?
58
63
  wrapped << Node::Sections.new(content: partitioned[:sections]) if partitioned[:sections].any?
59
64
  wrapped.concat(partitioned[:bibliography])
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module Mirror
5
+ # Public read-API: extract a flat Ruby Hash from a Mirror document's
6
+ # frontmatter node.
7
+ #
8
+ # Why this exists: site generators (e.g. metanorma.org's convert-adoc.rb)
9
+ # need frontmatter as a plain Hash for templating VitePress/Jekyll
10
+ # frontmatter output. Without this, callers would either (a) re-parse
11
+ # the source YAML — violating FrontmatterBlock::Codec's "single source
12
+ # of truth" rule — or (b) hand-walk the typed FrontmatterEntry /
13
+ # FrontmatterValue tree themselves, duplicating the reverse builder's
14
+ # logic. This module is the single entry point for both problems.
15
+ #
16
+ # Reuses FrontmatterTreeToHash — same translator the reverse builder
17
+ # uses — so the read-path is shared (DRY/MECE).
18
+ module FrontmatterQuery
19
+ module_function
20
+
21
+ # @param mirror_doc [Mirror::Node::Document, nil]
22
+ # @return [Hash{String,Object}] flat key→value mapping; empty Hash
23
+ # if the document has no frontmatter node or no entries
24
+ def to_hash(mirror_doc)
25
+ frontmatter = find_frontmatter(mirror_doc)
26
+ return {} unless frontmatter
27
+
28
+ entries = frontmatter.attrs&.entries || []
29
+ FrontmatterTreeToHash.to_hash(entries)
30
+ end
31
+
32
+ # @param mirror_doc [Mirror::Node::Document, nil] (see #to_hash)
33
+ # @return [Boolean] true if the document carries a frontmatter node
34
+ # with at least one entry
35
+ def has_frontmatter?(mirror_doc)
36
+ frontmatter = find_frontmatter(mirror_doc)
37
+ !frontmatter.nil? && !(frontmatter.attrs&.entries || []).empty?
38
+ end
39
+
40
+ def find_frontmatter(mirror_doc)
41
+ return nil if mirror_doc.nil?
42
+
43
+ content = mirror_doc.content
44
+ return nil unless content
45
+
46
+ content.find { |node| node.is_a?(Node) && node.type == 'frontmatter' }
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coradoc
4
+ module Mirror
5
+ # Walks a typed FrontmatterEntry / FrontmatterValue tree (the Mirror
6
+ # representation of CoreModel::FrontmatterBlock#data) and rebuilds the
7
+ # flat Ruby Hash. Inverse of Handlers::Frontmatter.build_value.
8
+ #
9
+ # Single source of truth for tree → Hash translation. Consumed by:
10
+ # - ReverseBuilder::Frontmatter (mirror → CoreModel round-trip)
11
+ # - FrontmatterQuery (mirror doc → flat Hash for downstream readers)
12
+ #
13
+ # Extracted from ReverseBuilder so the read-path is shared (DRY/MECE)
14
+ # and FrontmatterQuery does not depend on the reverse-builder constant.
15
+ module FrontmatterTreeToHash
16
+ module_function
17
+
18
+ def to_hash(entries)
19
+ entries.each_with_object({}) do |entry, result|
20
+ result[entry.key] = unwrap_value(entry.value)
21
+ end
22
+ end
23
+
24
+ def unwrap_value(value)
25
+ case value.value_type
26
+ when 'map' then to_hash(value.entries || [])
27
+ when 'array' then (value.items || []).map { |v| unwrap_value(v) }
28
+ when 'integer' then value.integer_value
29
+ when 'float' then value.float_value
30
+ when 'boolean' then value.boolean_value
31
+ when 'date' then value.date_value
32
+ when 'datetime' then value.datetime_value
33
+ when 'symbol' then value.symbol_value&.to_sym
34
+ when 'nil' then nil
35
+ else value.string_value
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -43,24 +43,26 @@ module Coradoc
43
43
  end
44
44
 
45
45
  def build_description(item, context)
46
- definitions = item.definitions
47
- return nil unless definitions && !definitions.empty?
46
+ desc_nodes = description_nodes(item, context)
47
+ return nil if desc_nodes.empty?
48
48
 
49
- desc_nodes = definitions.map do |defn|
50
- context.text_node(defn.to_s)
51
- end
49
+ Node::DefinitionDescription.new(content: desc_nodes)
50
+ end
52
51
 
53
- def_children = item.definition_children if item.is_a?(CoreModel::DefinitionItem)
54
- if def_children && !def_children.empty?
55
- def_children.each do |child|
56
- nodes = Handlers::Inline.process_child(child, context)
57
- desc_nodes.concat(Array(nodes)) if nodes && !nodes.empty?
58
- end
52
+ # Emit one text node per source — never both. Rich
53
+ # definition_children (inline nodes) win over plain definitions
54
+ # (strings) because they preserve formatting; falling back to
55
+ # the plain string keeps this handler robust for DefinitionItem
56
+ # instances populated by paths that don't build inline children.
57
+ def description_nodes(item, context)
58
+ unless item.is_a?(CoreModel::DefinitionItem)
59
+ return Array(item.definitions).map { |defn| context.text_node(defn.to_s) }
59
60
  end
60
61
 
61
- return nil if desc_nodes.empty?
62
+ children = item.definition_children
63
+ return Array(item.definitions).map { |defn| context.text_node(defn.to_s) } if children.empty?
62
64
 
63
- Node::DefinitionDescription.new(content: desc_nodes)
65
+ children.flat_map { |child| Handlers::Inline.process_child(child, context) }
64
66
  end
65
67
  end
66
68
  end
@@ -15,9 +15,15 @@ module Coradoc
15
15
  # (clause, annex, abstract, foreword, introduction, terms,
16
16
  # definitions, references, content_section, acknowledgements).
17
17
  SECTION_TYPES = Set.new(%w[
18
- clause annex content_section abstract foreword introduction
19
- acknowledgements terms definitions references section
20
- ]).freeze
18
+ clause annex content_section abstract foreword introduction
19
+ acknowledgements terms definitions references section
20
+ ]).freeze
21
+
22
+ # Mirror node types that are doc-level metadata, not body content.
23
+ # They pass through the partitioner untouched (in document order)
24
+ # so the renderer can choose to skip them — they never land in the
25
+ # preface bucket where they would render as visible body.
26
+ METADATA_TYPES = Set.new(%w[frontmatter]).freeze
21
27
 
22
28
  module_function
23
29
 
@@ -29,11 +35,16 @@ module Coradoc
29
35
  # Once a bibliography appears → :trailing.
30
36
  # Footnotes blocks always go into :trailing regardless of state.
31
37
  #
38
+ # Metadata blocks (frontmatter) are returned in their own bucket,
39
+ # outside the preface/sections/trailing flow, so they survive the
40
+ # partition round-trip but are never rendered as body content.
41
+ #
32
42
  # @param children [Array<Node>] flat list of built Mirror nodes
33
43
  # @return [Hash{Symbol=>Array<Node>}] buckets under :preface,
34
- # :sections, :bibliography, :trailing
44
+ # :sections, :bibliography, :trailing, :metadata
35
45
  def partition(children)
36
- buckets = { preface: [], sections: [], bibliography: [], trailing: [] }
46
+ buckets = { preface: [], sections: [], bibliography: [],
47
+ trailing: [], metadata: [] }
37
48
  state = :preface
38
49
 
39
50
  children.each do |child|
@@ -44,7 +55,9 @@ module Coradoc
44
55
  buckets[:bibliography] << child
45
56
  state = :trailing
46
57
  else
47
- if SECTION_TYPES.include?(child.type)
58
+ if METADATA_TYPES.include?(child.type)
59
+ buckets[:metadata] << child
60
+ elsif SECTION_TYPES.include?(child.type)
48
61
  buckets[:sections] << child
49
62
  state = :sections
50
63
  elsif child.type == 'footnotes'
@@ -26,6 +26,9 @@ module Coradoc
26
26
  # full before any caller references it. Mirror-level mark dispatch
27
27
  # lives in MarkReverseBuilder (mark_reverse_builder.rb).
28
28
  module ReverseBuilder
29
+ # Not frozen: subclasses call `register` from their class body at
30
+ # load time, and `registers` may fire late via autoload. Freezing
31
+ # here breaks the first mirror-to-core round-trip after load.
29
32
  REGISTRY = {}
30
33
 
31
34
  module_function
@@ -567,34 +570,6 @@ module Coradoc
567
570
 
568
571
  LIST_TYPES = %w[bullet_list ordered_list].freeze
569
572
  private_constant :LIST_TYPES
570
-
571
- # Walks a typed FrontmatterEntry / FrontmatterValue tree and
572
- # rebuilds the CoreModel `data` hash. Inverse of
573
- # Handlers::Frontmatter.build_value.
574
- module FrontmatterTreeToHash
575
- module_function
576
-
577
- def to_hash(entries)
578
- entries.each_with_object({}) do |entry, result|
579
- result[entry.key] = unwrap_value(entry.value)
580
- end
581
- end
582
-
583
- def unwrap_value(value)
584
- case value.value_type
585
- when 'map' then to_hash(value.entries || [])
586
- when 'array' then (value.items || []).map { |v| unwrap_value(v) }
587
- when 'integer' then value.integer_value
588
- when 'float' then value.float_value
589
- when 'boolean' then value.boolean_value
590
- when 'date' then value.date_value
591
- when 'datetime' then value.datetime_value
592
- when 'symbol' then value.symbol_value&.to_sym
593
- when 'nil' then nil
594
- else value.string_value
595
- end
596
- end
597
- end
598
573
  end
599
574
  end
600
575
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Coradoc
4
4
  module Mirror
5
- VERSION = '0.1.1'
5
+ VERSION = '0.1.2'
6
6
  end
7
7
  end
@@ -14,6 +14,15 @@ module Coradoc
14
14
  autoload :CoreModelToMirror, "#{__dir__}/mirror/core_model_to_mirror"
15
15
  autoload :MirrorToCoreModel, "#{__dir__}/mirror/mirror_to_core_model"
16
16
  autoload :Partitioner, "#{__dir__}/mirror/partitioner"
17
+ # Shared tree→Hash translator for the frontmatter typed-tree. Read by
18
+ # ReverseBuilder::Frontmatter and FrontmatterQuery — single source of
19
+ # truth for the inverse of Handlers::Frontmatter.build_value.
20
+ autoload :FrontmatterTreeToHash, "#{__dir__}/mirror/frontmatter_tree_to_hash"
21
+ # Public read-API for downstream consumers (e.g. site generators) that
22
+ # need a flat Ruby Hash of a Mirror doc's frontmatter without re-parsing
23
+ # the source YAML. Frontmatter lives in the CoreModel and the Mirror
24
+ # doc, not in a parallel YAML parse.
25
+ autoload :FrontmatterQuery, "#{__dir__}/mirror/frontmatter_query"
17
26
  # ReverseBuilder's REGISTRY is populated by the built-in builder
18
27
  # subclasses (defined inside reverse_builder.rb) at load time. The
19
28
  # file is the autoload target, so the registry is full by the time
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: coradoc-mirror
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -77,6 +77,8 @@ files:
77
77
  - lib/coradoc-mirror.rb
78
78
  - lib/coradoc/mirror.rb
79
79
  - lib/coradoc/mirror/core_model_to_mirror.rb
80
+ - lib/coradoc/mirror/frontmatter_query.rb
81
+ - lib/coradoc/mirror/frontmatter_tree_to_hash.rb
80
82
  - lib/coradoc/mirror/handler_registry.rb
81
83
  - lib/coradoc/mirror/handlers.rb
82
84
  - lib/coradoc/mirror/handlers/admonition.rb