Module: Coradoc::Pipeline

Defined in:
lib/coradoc/pipeline.rb

Overview

Parse / serialize / convert pipeline. Single source of truth for the document transformation flow, extracted from the top-level Coradoc façade so pipeline logic has its own home and its own spec surface. Public API on Coradoc delegates here.

Class Method Summary collapse

Class Method Details

.buildObject



134
135
136
# File 'lib/coradoc/pipeline.rb', line 134

def build(&)
  CoreModel::DocumentElement.build(children: [], &)
end

.convert(text, from:, to:) ⇒ Object



106
107
108
109
# File 'lib/coradoc/pipeline.rb', line 106

def convert(text, from:, to:, **)
  core = parse(text, format: from)
  serialize(core, to: to, **)
end

.convert_file(path, to:, from: nil) ⇒ Object



160
161
162
163
164
165
166
# File 'lib/coradoc/pipeline.rb', line 160

def convert_file(path, to:, from: nil, **)
  source_format = from || FormatCatalog.detect_format(path)
  raise UnsupportedFormatError, "Could not detect format for: #{path}" unless source_format

  core = parse_file(path, format: source_format)
  serialize(core, to: to, **)
end

.parse(text, format:) ⇒ Object

Parse text to a document model. Graph mode: include:: directives survive as CoreModel::Include link nodes — no file I/O happens during parse. Splicing included content is a separate, explicit step (see Coradoc.resolve_includes).



14
15
16
17
18
19
20
21
22
23
24
# File 'lib/coradoc/pipeline.rb', line 14

def parse(text, format:)
  format_module = FormatCatalog.get_format(format)
  unless format_module
    raise UnsupportedFormatError.new(format,
                                     available: FormatCatalog.registered_formats)
  end

  text = Hooks.invoke(:before_parse, text, format: format)
  result = format_module.parse_to_core(text)
  Hooks.invoke(:after_parse, result, format: format)
end

.parse_file(path, format: nil) ⇒ Object

Raises:



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/coradoc/pipeline.rb', line 138

def parse_file(path, format: nil)
  raise FileNotFoundError, path unless File.exist?(path)

  source_format = format || FormatCatalog.detect_format(path)
  raise UnsupportedFormatError, "Could not detect format for: #{path}" unless source_format

  format_module = FormatCatalog.get_format(source_format)
  unless format_module
    raise UnsupportedFormatError.new(source_format,
                                     available: FormatCatalog.registered_formats)
  end

  if FormatCatalog.binary_format?(source_format)
    format_module.parse_to_core(path)
  else
    content = File.read(path)
    content = Hooks.invoke(:before_parse, content, format: source_format)
    result = format_module.parse_file_to_core(path, content)
    Hooks.invoke(:after_parse, result, format: source_format)
  end
end

.resolve_includes(document, base_dir:, missing_include: :error, max_depth: Coradoc::ResolveIncludes::DEFAULT_MAX_DEPTH, allow_unsafe: false, resolver: nil) ⇒ Object



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/coradoc/pipeline.rb', line 26

def resolve_includes(document, base_dir:,
                     missing_include: :error,
                     max_depth: Coradoc::ResolveIncludes::DEFAULT_MAX_DEPTH,
                     allow_unsafe: false,
                     resolver: nil)
  resolver = Coradoc::IncludeResolver.coerce(
    resolver,
    base_dir: base_dir,
    allow_unsafe: allow_unsafe
  )
  Coradoc::ResolveIncludes.call(
    document,
    resolver: resolver,
    base_dir: base_dir,
    missing_include: missing_include,
    max_depth: max_depth
  )
end

.resolve_references(document, catalog:, presentation:, resolver: nil, missing: :warn, ambiguous: :disambiguate, materialize: false, format: nil) ⇒ CoreModel::Base

Resolve every reference (xref, citation, link, include, image, footnote) in a parsed document using a unified content-graph model. Mirrors resolve_includes in shape: two-step, immutable.

Step one always runs: every Edge is resolved through the Resolver and the +missing+/+ambiguous+ policies are enforced (raise or warn). Step two is opt-in: with materialize: true the tree is rebuilt with each Edge replaced by the output of the Materializer registered for its [kind, presentation, format] tuple.

The input document is never mutated. With materialize: false the input document itself is returned; with materialize: true a new document is returned that structurally shares untouched subtrees with the input (treat both as immutable). Nodes whose kind has no registered materializer are preserved unchanged.

Examples:

Resolve cross-references into HTML links

doc = Coradoc.parse(text, format: :asciidoc)
catalog = Coradoc::Reference::Catalog::Local.from_doc(doc)
presentation = Coradoc::Reference::Presentation::SingleDocument.new
resolved = Coradoc.resolve_references(
  doc,
  catalog: catalog,
  presentation: presentation,
  materialize: true,
  format: :html
)

Parameters:

  • document (CoreModel::Base)

    parsed document

  • catalog (Reference::Catalog::*)

    index of addressable Content

  • presentation (Reference::Presentation::Base)

    slicing and ordering

  • resolver (Reference::Resolver::Base, nil) (defaults to: nil)

    defaults to CatalogResolver

  • missing (Symbol) (defaults to: :warn)

    :warn (default), :silent, :error, :passthrough

  • ambiguous (Symbol) (defaults to: :disambiguate)

    :disambiguate (default), :first, :error

  • materialize (Boolean) (defaults to: false)

    when true, replace edges with rendered inlines

  • format (Symbol, nil) (defaults to: nil)

    target format for materializer lookup (:html, :asciidoc, ...); nil matches format-agnostic materializers

Returns:

  • (CoreModel::Base)

    the input document (validation only) or a new materialized document



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/coradoc/pipeline.rb', line 89

def resolve_references(document, catalog:, presentation:,
                       resolver: nil,
                       missing: :warn,
                       ambiguous: :disambiguate,
                       materialize: false,
                       format: nil)
  Coradoc::Reference::Resolution.new(
    catalog: catalog,
    presentation: presentation,
    resolver: resolver,
    missing: missing,
    ambiguous: ambiguous,
    materialize: materialize,
    format: format
  ).call(document)
end


45
46
47
# File 'lib/coradoc/pipeline.rb', line 45

def rewrite_links(document, rewriter: nil, &)
  Coradoc::LinkRewriter.rewrite(document, rewriter: rewriter, &)
end

.serialize(model, to:, allow_unresolved_includes: false) ⇒ Object



123
124
125
126
127
128
129
130
131
132
# File 'lib/coradoc/pipeline.rb', line 123

def serialize(model, to:, allow_unresolved_includes: false, **)
  format_module = FormatCatalog.get_format(to)
  raise UnsupportedFormatError.new(to, available: FormatCatalog.registered_formats) unless format_module

  Coradoc::Validation.guard_unresolved_includes!(model, format_module) unless allow_unresolved_includes

  model = Hooks.invoke(:before_serialize, model, format: to)
  result = format_module.serialize(model, **)
  Hooks.invoke(:after_serialize, result, format: to)
end

.to_core(model) ⇒ Object



111
112
113
114
115
116
117
118
119
120
121
# File 'lib/coradoc/pipeline.rb', line 111

def to_core(model)
  return model if model.is_a?(CoreModel::Base)

  FormatCatalog.registry.each_value do |format_module|
    next unless format_module.handles_model?(model)

    return format_module.to_core(model)
  end

  raise TransformationError, "No transformer found for #{model.class}"
end