coradoc-adoc 2.0.0
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 +7 -0
- data/.rspec +3 -0
- data/lib/coradoc/asciidoc/model/admonition.rb +37 -0
- data/lib/coradoc/asciidoc/model/anchorable.rb +64 -0
- data/lib/coradoc/asciidoc/model/attached.rb +26 -0
- data/lib/coradoc/asciidoc/model/attribute.rb +22 -0
- data/lib/coradoc/asciidoc/model/attribute_list/matchers.rb +45 -0
- data/lib/coradoc/asciidoc/model/attribute_list.rb +230 -0
- data/lib/coradoc/asciidoc/model/attribute_list_attribute.rb +11 -0
- data/lib/coradoc/asciidoc/model/audio.rb +44 -0
- data/lib/coradoc/asciidoc/model/author.rb +36 -0
- data/lib/coradoc/asciidoc/model/base.rb +141 -0
- data/lib/coradoc/asciidoc/model/bibliography.rb +37 -0
- data/lib/coradoc/asciidoc/model/bibliography_entry.rb +38 -0
- data/lib/coradoc/asciidoc/model/block/core.rb +139 -0
- data/lib/coradoc/asciidoc/model/block/example.rb +14 -0
- data/lib/coradoc/asciidoc/model/block/listing.rb +14 -0
- data/lib/coradoc/asciidoc/model/block/literal.rb +14 -0
- data/lib/coradoc/asciidoc/model/block/open.rb +14 -0
- data/lib/coradoc/asciidoc/model/block/pass.rb +14 -0
- data/lib/coradoc/asciidoc/model/block/quote.rb +14 -0
- data/lib/coradoc/asciidoc/model/block/reviewer_comment.rb +14 -0
- data/lib/coradoc/asciidoc/model/block/side.rb +14 -0
- data/lib/coradoc/asciidoc/model/block/source_code.rb +14 -0
- data/lib/coradoc/asciidoc/model/block.rb +21 -0
- data/lib/coradoc/asciidoc/model/break.rb +33 -0
- data/lib/coradoc/asciidoc/model/comment_block.rb +33 -0
- data/lib/coradoc/asciidoc/model/comment_line.rb +30 -0
- data/lib/coradoc/asciidoc/model/content_list.rb +334 -0
- data/lib/coradoc/asciidoc/model/document.rb +197 -0
- data/lib/coradoc/asciidoc/model/document_attributes.rb +43 -0
- data/lib/coradoc/asciidoc/model/glossaries.rb +11 -0
- data/lib/coradoc/asciidoc/model/header.rb +57 -0
- data/lib/coradoc/asciidoc/model/highlight.rb +11 -0
- data/lib/coradoc/asciidoc/model/image/block_image/attribute_list.rb +23 -0
- data/lib/coradoc/asciidoc/model/image/block_image.rb +25 -0
- data/lib/coradoc/asciidoc/model/image/core/attribute_list.rb +43 -0
- data/lib/coradoc/asciidoc/model/image/core.rb +72 -0
- data/lib/coradoc/asciidoc/model/image/inline_image.rb +17 -0
- data/lib/coradoc/asciidoc/model/image.rb +14 -0
- data/lib/coradoc/asciidoc/model/include.rb +66 -0
- data/lib/coradoc/asciidoc/model/inline/anchor.rb +41 -0
- data/lib/coradoc/asciidoc/model/inline/attribute_reference.rb +25 -0
- data/lib/coradoc/asciidoc/model/inline/base.rb +15 -0
- data/lib/coradoc/asciidoc/model/inline/bold.rb +38 -0
- data/lib/coradoc/asciidoc/model/inline/cross_reference.rb +29 -0
- data/lib/coradoc/asciidoc/model/inline/cross_reference_arg.rb +15 -0
- data/lib/coradoc/asciidoc/model/inline/footnote.rb +34 -0
- data/lib/coradoc/asciidoc/model/inline/hard_line_break.rb +24 -0
- data/lib/coradoc/asciidoc/model/inline/highlight.rb +36 -0
- data/lib/coradoc/asciidoc/model/inline/italic.rb +38 -0
- data/lib/coradoc/asciidoc/model/inline/link.rb +46 -0
- data/lib/coradoc/asciidoc/model/inline/monospace.rb +39 -0
- data/lib/coradoc/asciidoc/model/inline/quotation.rb +25 -0
- data/lib/coradoc/asciidoc/model/inline/small.rb +25 -0
- data/lib/coradoc/asciidoc/model/inline/span.rb +38 -0
- data/lib/coradoc/asciidoc/model/inline/stem.rb +24 -0
- data/lib/coradoc/asciidoc/model/inline/strikethrough.rb +39 -0
- data/lib/coradoc/asciidoc/model/inline/subscript.rb +33 -0
- data/lib/coradoc/asciidoc/model/inline/superscript.rb +33 -0
- data/lib/coradoc/asciidoc/model/inline/underline.rb +25 -0
- data/lib/coradoc/asciidoc/model/inline.rb +31 -0
- data/lib/coradoc/asciidoc/model/line_break.rb +11 -0
- data/lib/coradoc/asciidoc/model/list/core.rb +61 -0
- data/lib/coradoc/asciidoc/model/list/definition.rb +27 -0
- data/lib/coradoc/asciidoc/model/list/definition_item.rb +43 -0
- data/lib/coradoc/asciidoc/model/list/item.rb +72 -0
- data/lib/coradoc/asciidoc/model/list/nestable.rb +14 -0
- data/lib/coradoc/asciidoc/model/list/ordered.rb +34 -0
- data/lib/coradoc/asciidoc/model/list/unordered.rb +34 -0
- data/lib/coradoc/asciidoc/model/list.rb +29 -0
- data/lib/coradoc/asciidoc/model/named_attribute.rb +12 -0
- data/lib/coradoc/asciidoc/model/paragraph.rb +59 -0
- data/lib/coradoc/asciidoc/model/rejected_positional_attribute.rb +12 -0
- data/lib/coradoc/asciidoc/model/resolvable.rb +71 -0
- data/lib/coradoc/asciidoc/model/resolver.rb +430 -0
- data/lib/coradoc/asciidoc/model/reviewer_note.rb +54 -0
- data/lib/coradoc/asciidoc/model/revision.rb +47 -0
- data/lib/coradoc/asciidoc/model/section.rb +109 -0
- data/lib/coradoc/asciidoc/model/serialization/asciidoc_adapter.rb +28 -0
- data/lib/coradoc/asciidoc/model/serialization/asciidoc_mapping.rb +42 -0
- data/lib/coradoc/asciidoc/model/serialization/asciidoc_mapping_rule.rb +41 -0
- data/lib/coradoc/asciidoc/model/serialization/asciidoc_transform.rb +211 -0
- data/lib/coradoc/asciidoc/model/serialization/errors.rb +57 -0
- data/lib/coradoc/asciidoc/model/serialization.rb +39 -0
- data/lib/coradoc/asciidoc/model/spacing.rb +282 -0
- data/lib/coradoc/asciidoc/model/table.rb +44 -0
- data/lib/coradoc/asciidoc/model/table_cell.rb +122 -0
- data/lib/coradoc/asciidoc/model/table_row.rb +26 -0
- data/lib/coradoc/asciidoc/model/tag.rb +36 -0
- data/lib/coradoc/asciidoc/model/term.rb +48 -0
- data/lib/coradoc/asciidoc/model/text_element.rb +66 -0
- data/lib/coradoc/asciidoc/model/title.rb +85 -0
- data/lib/coradoc/asciidoc/model/video/attribute_list.rb +43 -0
- data/lib/coradoc/asciidoc/model/video.rb +49 -0
- data/lib/coradoc/asciidoc/model.rb +75 -0
- data/lib/coradoc/asciidoc/parse_error.rb +161 -0
- data/lib/coradoc/asciidoc/parser/admonition.rb +26 -0
- data/lib/coradoc/asciidoc/parser/attribute_list.rb +110 -0
- data/lib/coradoc/asciidoc/parser/base.rb +159 -0
- data/lib/coradoc/asciidoc/parser/bibliography.rb +31 -0
- data/lib/coradoc/asciidoc/parser/block.rb +186 -0
- data/lib/coradoc/asciidoc/parser/block_assembler.rb +183 -0
- data/lib/coradoc/asciidoc/parser/cache.rb +155 -0
- data/lib/coradoc/asciidoc/parser/citation.rb +32 -0
- data/lib/coradoc/asciidoc/parser/content.rb +76 -0
- data/lib/coradoc/asciidoc/parser/document_attributes.rb +27 -0
- data/lib/coradoc/asciidoc/parser/fix_files.rb +76 -0
- data/lib/coradoc/asciidoc/parser/header.rb +31 -0
- data/lib/coradoc/asciidoc/parser/inline.rb +199 -0
- data/lib/coradoc/asciidoc/parser/list.rb +130 -0
- data/lib/coradoc/asciidoc/parser/metadata_detector.rb +164 -0
- data/lib/coradoc/asciidoc/parser/paragraph.rb +64 -0
- data/lib/coradoc/asciidoc/parser/section.rb +62 -0
- data/lib/coradoc/asciidoc/parser/stem.rb +19 -0
- data/lib/coradoc/asciidoc/parser/table.rb +166 -0
- data/lib/coradoc/asciidoc/parser/term.rb +70 -0
- data/lib/coradoc/asciidoc/parser/text.rb +156 -0
- data/lib/coradoc/asciidoc/parser.rb +10 -0
- data/lib/coradoc/asciidoc/serializer/adoc_serializer.rb +86 -0
- data/lib/coradoc/asciidoc/serializer/element_registry.rb +95 -0
- data/lib/coradoc/asciidoc/serializer/fallback_serializer.rb +21 -0
- data/lib/coradoc/asciidoc/serializer/formatter.rb +144 -0
- data/lib/coradoc/asciidoc/serializer/registrations.rb +108 -0
- data/lib/coradoc/asciidoc/serializer/serialization_context.rb +238 -0
- data/lib/coradoc/asciidoc/serializer/serializers/admonition.rb +19 -0
- data/lib/coradoc/asciidoc/serializer/serializers/attribute.rb +23 -0
- data/lib/coradoc/asciidoc/serializer/serializers/attribute_list.rb +40 -0
- data/lib/coradoc/asciidoc/serializer/serializers/attribute_list_attribute.rb +18 -0
- data/lib/coradoc/asciidoc/serializer/serializers/audio.rb +33 -0
- data/lib/coradoc/asciidoc/serializer/serializers/author.rb +20 -0
- data/lib/coradoc/asciidoc/serializer/serializers/base.rb +152 -0
- data/lib/coradoc/asciidoc/serializer/serializers/bibliography.rb +35 -0
- data/lib/coradoc/asciidoc/serializer/serializers/bibliography_entry.rb +24 -0
- data/lib/coradoc/asciidoc/serializer/serializers/block/core.rb +70 -0
- data/lib/coradoc/asciidoc/serializer/serializers/block/example.rb +17 -0
- data/lib/coradoc/asciidoc/serializer/serializers/block/listing.rb +22 -0
- data/lib/coradoc/asciidoc/serializer/serializers/block/literal.rb +17 -0
- data/lib/coradoc/asciidoc/serializer/serializers/block/open.rb +22 -0
- data/lib/coradoc/asciidoc/serializer/serializers/block/pass.rb +17 -0
- data/lib/coradoc/asciidoc/serializer/serializers/block/quote.rb +17 -0
- data/lib/coradoc/asciidoc/serializer/serializers/block/reviewer_comment.rb +17 -0
- data/lib/coradoc/asciidoc/serializer/serializers/block/side.rb +22 -0
- data/lib/coradoc/asciidoc/serializer/serializers/block/source_code.rb +22 -0
- data/lib/coradoc/asciidoc/serializer/serializers/block.rb +23 -0
- data/lib/coradoc/asciidoc/serializer/serializers/break.rb +18 -0
- data/lib/coradoc/asciidoc/serializer/serializers/comment_block.rb +22 -0
- data/lib/coradoc/asciidoc/serializer/serializers/comment_line.rb +22 -0
- data/lib/coradoc/asciidoc/serializer/serializers/document.rb +65 -0
- data/lib/coradoc/asciidoc/serializer/serializers/document_attributes.rb +21 -0
- data/lib/coradoc/asciidoc/serializer/serializers/header.rb +24 -0
- data/lib/coradoc/asciidoc/serializer/serializers/highlight.rb +23 -0
- data/lib/coradoc/asciidoc/serializer/serializers/image/core.rb +30 -0
- data/lib/coradoc/asciidoc/serializer/serializers/image.rb +14 -0
- data/lib/coradoc/asciidoc/serializer/serializers/include.rb +19 -0
- data/lib/coradoc/asciidoc/serializer/serializers/inline/anchor.rb +20 -0
- data/lib/coradoc/asciidoc/serializer/serializers/inline/attribute_reference.rb +20 -0
- data/lib/coradoc/asciidoc/serializer/serializers/inline/bold.rb +26 -0
- data/lib/coradoc/asciidoc/serializer/serializers/inline/cross_reference.rb +30 -0
- data/lib/coradoc/asciidoc/serializer/serializers/inline/cross_reference_arg.rb +20 -0
- data/lib/coradoc/asciidoc/serializer/serializers/inline/footnote.rb +24 -0
- data/lib/coradoc/asciidoc/serializer/serializers/inline/hard_line_break.rb +20 -0
- data/lib/coradoc/asciidoc/serializer/serializers/inline/highlight.rb +26 -0
- data/lib/coradoc/asciidoc/serializer/serializers/inline/italic.rb +26 -0
- data/lib/coradoc/asciidoc/serializer/serializers/inline/link.rb +38 -0
- data/lib/coradoc/asciidoc/serializer/serializers/inline/monospace.rb +26 -0
- data/lib/coradoc/asciidoc/serializer/serializers/inline/quotation.rb +21 -0
- data/lib/coradoc/asciidoc/serializer/serializers/inline/small.rb +20 -0
- data/lib/coradoc/asciidoc/serializer/serializers/inline/span.rb +35 -0
- data/lib/coradoc/asciidoc/serializer/serializers/inline/stem.rb +23 -0
- data/lib/coradoc/asciidoc/serializer/serializers/inline/strikethrough.rb +29 -0
- data/lib/coradoc/asciidoc/serializer/serializers/inline/subscript.rb +29 -0
- data/lib/coradoc/asciidoc/serializer/serializers/inline/superscript.rb +26 -0
- data/lib/coradoc/asciidoc/serializer/serializers/inline/underline.rb +20 -0
- data/lib/coradoc/asciidoc/serializer/serializers/inline.rb +32 -0
- data/lib/coradoc/asciidoc/serializer/serializers/line_break.rb +18 -0
- data/lib/coradoc/asciidoc/serializer/serializers/list/core.rb +47 -0
- data/lib/coradoc/asciidoc/serializer/serializers/list/definition.rb +35 -0
- data/lib/coradoc/asciidoc/serializer/serializers/list/definition_item.rb +38 -0
- data/lib/coradoc/asciidoc/serializer/serializers/list/item.rb +120 -0
- data/lib/coradoc/asciidoc/serializer/serializers/list/ordered.rb +24 -0
- data/lib/coradoc/asciidoc/serializer/serializers/list/unordered.rb +29 -0
- data/lib/coradoc/asciidoc/serializer/serializers/list.rb +19 -0
- data/lib/coradoc/asciidoc/serializer/serializers/named_attribute.rb +22 -0
- data/lib/coradoc/asciidoc/serializer/serializers/paragraph.rb +65 -0
- data/lib/coradoc/asciidoc/serializer/serializers/reviewer_note.rb +28 -0
- data/lib/coradoc/asciidoc/serializer/serializers/revision.rb +26 -0
- data/lib/coradoc/asciidoc/serializer/serializers/section.rb +37 -0
- data/lib/coradoc/asciidoc/serializer/serializers/table.rb +24 -0
- data/lib/coradoc/asciidoc/serializer/serializers/table_cell.rb +75 -0
- data/lib/coradoc/asciidoc/serializer/serializers/table_row.rb +24 -0
- data/lib/coradoc/asciidoc/serializer/serializers/tag.rb +19 -0
- data/lib/coradoc/asciidoc/serializer/serializers/term.rb +20 -0
- data/lib/coradoc/asciidoc/serializer/serializers/text_element.rb +23 -0
- data/lib/coradoc/asciidoc/serializer/serializers/title.rb +55 -0
- data/lib/coradoc/asciidoc/serializer/serializers/video.rb +33 -0
- data/lib/coradoc/asciidoc/serializer/spacing_strategy.rb +70 -0
- data/lib/coradoc/asciidoc/serializer.rb +75 -0
- data/lib/coradoc/asciidoc/transform/from_core_model.rb +502 -0
- data/lib/coradoc/asciidoc/transform/from_core_model_registrations.rb +126 -0
- data/lib/coradoc/asciidoc/transform/registry.rb +146 -0
- data/lib/coradoc/asciidoc/transform/to_core_model.rb +564 -0
- data/lib/coradoc/asciidoc/transform/to_core_model_registrations.rb +257 -0
- data/lib/coradoc/asciidoc/transform.rb +13 -0
- data/lib/coradoc/asciidoc/transformer/block_rules.rb +101 -0
- data/lib/coradoc/asciidoc/transformer/header_rules.rb +91 -0
- data/lib/coradoc/asciidoc/transformer/inline_rules.rb +179 -0
- data/lib/coradoc/asciidoc/transformer/list_rules.rb +131 -0
- data/lib/coradoc/asciidoc/transformer/misc_rules.rb +196 -0
- data/lib/coradoc/asciidoc/transformer/structural_rules.rb +216 -0
- data/lib/coradoc/asciidoc/transformer/text_rules.rb +107 -0
- data/lib/coradoc/asciidoc/transformer.rb +406 -0
- data/lib/coradoc/asciidoc/version.rb +7 -0
- data/lib/coradoc/asciidoc.rb +148 -0
- data/lib/coradoc/util/asciidoc.rb +71 -0
- data/lib/coradoc/util.rb +8 -0
- metadata +343 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module AsciiDoc
|
|
5
|
+
module Model
|
|
6
|
+
# Module for nodes that reference external resources.
|
|
7
|
+
#
|
|
8
|
+
# Any model element that references an external file or resource
|
|
9
|
+
# should include this module to provide a unified interface for
|
|
10
|
+
# resolution operations.
|
|
11
|
+
#
|
|
12
|
+
# @example Implementing Resolvable in a class
|
|
13
|
+
# class Include < Base
|
|
14
|
+
# include Resolvable
|
|
15
|
+
#
|
|
16
|
+
# def reference_path
|
|
17
|
+
# path
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# def reference_type
|
|
21
|
+
# :include
|
|
22
|
+
# end
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
module Resolvable
|
|
26
|
+
# Returns the path to the external resource.
|
|
27
|
+
#
|
|
28
|
+
# @return [String] the path or URL to the external resource
|
|
29
|
+
# @raise [NotImplementedError] if not implemented by including class
|
|
30
|
+
def reference_path
|
|
31
|
+
raise NotImplementedError,
|
|
32
|
+
"#{self.class} must implement #reference_path"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Returns the type of reference.
|
|
36
|
+
#
|
|
37
|
+
# @return [Symbol] the reference type (:include, :image, :video, :audio, :link)
|
|
38
|
+
# @raise [NotImplementedError] if not implemented by including class
|
|
39
|
+
def reference_type
|
|
40
|
+
raise NotImplementedError,
|
|
41
|
+
"#{self.class} must implement #reference_type"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Returns additional options for reference resolution.
|
|
45
|
+
#
|
|
46
|
+
# @return [Hash] options for resolution (e.g., leveloffset, lines, tags)
|
|
47
|
+
def reference_options
|
|
48
|
+
{}
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Checks if the reference is to a local file.
|
|
52
|
+
#
|
|
53
|
+
# @return [Boolean] true if the reference is a local file path
|
|
54
|
+
def local_reference?
|
|
55
|
+
path = reference_path
|
|
56
|
+
return false if path.nil?
|
|
57
|
+
|
|
58
|
+
# Not a URL if it doesn't start with a scheme
|
|
59
|
+
!path.match?(%r{^[a-z][a-z0-9+.-]*://}i)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Checks if the reference is to a remote URL.
|
|
63
|
+
#
|
|
64
|
+
# @return [Boolean] true if the reference is a URL
|
|
65
|
+
def remote_reference?
|
|
66
|
+
!local_reference?
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
|
|
5
|
+
module Coradoc
|
|
6
|
+
module AsciiDoc
|
|
7
|
+
module Model
|
|
8
|
+
# Resolver classes for handling external document references.
|
|
9
|
+
#
|
|
10
|
+
# The resolver infrastructure provides a unified way to handle different
|
|
11
|
+
# types of external references (includes, images, media) with configurable
|
|
12
|
+
# resolution strategies.
|
|
13
|
+
#
|
|
14
|
+
# @example Resolving includes only
|
|
15
|
+
# resolver = Resolver.new(includes: true, images: :reference, media: :reference)
|
|
16
|
+
# resolved = resolver.resolve_document(doc, base_dir)
|
|
17
|
+
#
|
|
18
|
+
# @example Creating a self-contained document
|
|
19
|
+
# resolver = Resolver.new(
|
|
20
|
+
# includes: true,
|
|
21
|
+
# images: :embed,
|
|
22
|
+
# media: :copy,
|
|
23
|
+
# output_dir: "/path/to/output"
|
|
24
|
+
# )
|
|
25
|
+
# resolved = resolver.resolve_document(doc, base_dir)
|
|
26
|
+
#
|
|
27
|
+
class Resolver
|
|
28
|
+
# @return [IncludeResolver, nil] the include resolver (nil if disabled)
|
|
29
|
+
attr_reader :include_resolver
|
|
30
|
+
|
|
31
|
+
# @return [ImageResolver] the image resolver
|
|
32
|
+
attr_reader :image_resolver
|
|
33
|
+
|
|
34
|
+
# @return [MediaResolver] the media resolver
|
|
35
|
+
attr_reader :media_resolver
|
|
36
|
+
|
|
37
|
+
# @return [String, nil] the output directory for copied files
|
|
38
|
+
attr_reader :output_dir
|
|
39
|
+
|
|
40
|
+
# Create a new Resolver with specified options.
|
|
41
|
+
#
|
|
42
|
+
# @param options [Hash] resolution options
|
|
43
|
+
# @option options [Boolean] :includes Whether to resolve include directives
|
|
44
|
+
# @option options [Symbol] :images Image resolution strategy (:reference, :copy, :embed)
|
|
45
|
+
# @option options [Symbol] :media Media resolution strategy (:reference, :copy, :embed)
|
|
46
|
+
# @option options [String] :output_dir Output directory for :copy mode
|
|
47
|
+
# @option options [Integer] :max_recursion Maximum recursion depth for includes
|
|
48
|
+
#
|
|
49
|
+
def initialize(options = {})
|
|
50
|
+
@include_resolver = options[:includes] ? IncludeResolver.new : nil
|
|
51
|
+
@image_resolver = ImageResolver.new(strategy: options[:images] || :reference)
|
|
52
|
+
@media_resolver = MediaResolver.new(strategy: options[:media] || :reference)
|
|
53
|
+
@output_dir = options[:output_dir]
|
|
54
|
+
@max_recursion = options[:max_recursion] || 10
|
|
55
|
+
@resolved_paths = {} # Track resolved paths to prevent infinite recursion
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Resolve a single node.
|
|
59
|
+
#
|
|
60
|
+
# @param node [Base] the node to resolve
|
|
61
|
+
# @param base_dir [String] base directory for relative paths
|
|
62
|
+
# @param depth [Integer] current recursion depth
|
|
63
|
+
# @return [Base, Array<Base>] the resolved node(s)
|
|
64
|
+
#
|
|
65
|
+
def resolve(node, base_dir, depth = 0)
|
|
66
|
+
return node if depth > @max_recursion
|
|
67
|
+
|
|
68
|
+
case node
|
|
69
|
+
when Include
|
|
70
|
+
resolve_include(node, base_dir, depth)
|
|
71
|
+
when Image::BlockImage, Image::InlineImage
|
|
72
|
+
resolve_image(node, base_dir)
|
|
73
|
+
when Video
|
|
74
|
+
resolve_media(node, base_dir, :video)
|
|
75
|
+
when Audio
|
|
76
|
+
resolve_media(node, base_dir, :audio)
|
|
77
|
+
else
|
|
78
|
+
node
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Resolve an entire document.
|
|
83
|
+
#
|
|
84
|
+
# @param document [Document] the document to resolve
|
|
85
|
+
# @param base_dir [String] base directory for relative paths
|
|
86
|
+
# @return [Document] a NEW document with resolved references
|
|
87
|
+
#
|
|
88
|
+
def resolve_document(document, base_dir)
|
|
89
|
+
resolved_sections = document.sections.map do |section|
|
|
90
|
+
resolve_node_recursive(section, base_dir, 0)
|
|
91
|
+
end.flatten.compact
|
|
92
|
+
|
|
93
|
+
document.class.new(
|
|
94
|
+
document_attributes: document.document_attributes,
|
|
95
|
+
header: document.header,
|
|
96
|
+
sections: resolved_sections
|
|
97
|
+
)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
private
|
|
101
|
+
|
|
102
|
+
def resolve_include(include_node, base_dir, depth)
|
|
103
|
+
return [include_node] unless @include_resolver
|
|
104
|
+
|
|
105
|
+
path = include_node.reference_path
|
|
106
|
+
full_path = File.expand_path(path, base_dir)
|
|
107
|
+
|
|
108
|
+
# Check for recursion
|
|
109
|
+
if @resolved_paths[full_path]
|
|
110
|
+
warn "[Coradoc] Warning: Circular include detected: #{path}"
|
|
111
|
+
return [include_node]
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
@resolved_paths[full_path] = true
|
|
115
|
+
|
|
116
|
+
result = @include_resolver.resolve(include_node, base_dir)
|
|
117
|
+
|
|
118
|
+
# Recursively resolve the included content
|
|
119
|
+
if result.is_a?(Array)
|
|
120
|
+
result.map { |node| resolve_node_recursive(node, File.dirname(full_path), depth + 1) }.flatten
|
|
121
|
+
else
|
|
122
|
+
resolve_node_recursive(result, File.dirname(full_path), depth + 1)
|
|
123
|
+
end
|
|
124
|
+
ensure
|
|
125
|
+
@resolved_paths.delete(full_path) if full_path
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def resolve_image(image_node, base_dir)
|
|
129
|
+
@image_resolver.resolve(image_node, base_dir, @output_dir)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def resolve_media(media_node, base_dir, _type)
|
|
133
|
+
@media_resolver.resolve(media_node, base_dir, @output_dir)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def resolve_node_recursive(node, base_dir, depth)
|
|
137
|
+
return node if depth > @max_recursion
|
|
138
|
+
|
|
139
|
+
resolved = resolve(node, base_dir, depth)
|
|
140
|
+
|
|
141
|
+
# If resolution returns an array (e.g., expanded includes), process each
|
|
142
|
+
return resolved.map { |n| resolve_node_recursive(n, base_dir, depth) }.flatten if resolved.is_a?(Array)
|
|
143
|
+
|
|
144
|
+
# Handle nodes with nested content
|
|
145
|
+
if resolved.is_a?(Coradoc::AsciiDoc::Model::Base) && resolved.class.attributes.key?(:contents) && resolved.contents
|
|
146
|
+
resolved = deep_copy_node(resolved)
|
|
147
|
+
resolved.contents = resolved.contents.map do |child|
|
|
148
|
+
resolve_node_recursive(child, base_dir, depth + 1)
|
|
149
|
+
end.flatten.compact
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Handle sections with nested content
|
|
153
|
+
if resolved.is_a?(Coradoc::AsciiDoc::Model::Base) && resolved.class.attributes.key?(:sections) && resolved.sections && !resolved.is_a?(Document)
|
|
154
|
+
resolved = deep_copy_node(resolved)
|
|
155
|
+
resolved.sections = resolved.sections.map do |child|
|
|
156
|
+
resolve_node_recursive(child, base_dir, depth + 1)
|
|
157
|
+
end.flatten.compact
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
resolved
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def deep_copy_node(node)
|
|
164
|
+
# Create a new instance with the same attributes
|
|
165
|
+
node.class.new(node.to_h)
|
|
166
|
+
rescue StandardError
|
|
167
|
+
# If to_h doesn't work, return the original node
|
|
168
|
+
node
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Resolves include directives by parsing and including file contents.
|
|
173
|
+
class IncludeResolver
|
|
174
|
+
# Resolve an include directive.
|
|
175
|
+
#
|
|
176
|
+
# @param include_node [Include] the include directive
|
|
177
|
+
# @param base_dir [String] base directory for relative paths
|
|
178
|
+
# @return [Array<Base>, Include] the included content or original node if not found
|
|
179
|
+
#
|
|
180
|
+
def resolve(include_node, base_dir)
|
|
181
|
+
path = include_node.reference_path
|
|
182
|
+
full_path = File.expand_path(path, base_dir)
|
|
183
|
+
|
|
184
|
+
unless File.exist?(full_path)
|
|
185
|
+
warn "[Coradoc] Warning: Include file not found: #{path}"
|
|
186
|
+
return [include_node]
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
content = File.read(full_path)
|
|
190
|
+
|
|
191
|
+
# Apply include options (lines, tags, etc.)
|
|
192
|
+
content = apply_include_options(content, include_node.reference_options)
|
|
193
|
+
|
|
194
|
+
# Parse the included content
|
|
195
|
+
included_doc = Coradoc::AsciiDoc.parse(content)
|
|
196
|
+
|
|
197
|
+
# Return the sections from the included document
|
|
198
|
+
included_doc.sections || []
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
private
|
|
202
|
+
|
|
203
|
+
def apply_include_options(content, options)
|
|
204
|
+
return content if options.empty?
|
|
205
|
+
|
|
206
|
+
lines = content.lines
|
|
207
|
+
|
|
208
|
+
# Apply lines filter
|
|
209
|
+
if options[:lines]
|
|
210
|
+
range_match = options[:lines].match(/(\d+)\.\.(\d+)/)
|
|
211
|
+
if range_match
|
|
212
|
+
start_line = [1, range_match[1].to_i].max
|
|
213
|
+
end_line = [lines.length, range_match[2].to_i].min
|
|
214
|
+
lines = lines[(start_line - 1)...end_line] || []
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Apply tags filter
|
|
219
|
+
lines = filter_by_tags(lines, options[:tags]) if options[:tags]
|
|
220
|
+
|
|
221
|
+
lines.join
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def filter_by_tags(lines, tags)
|
|
225
|
+
# Simple tag filtering - find lines between tag markers
|
|
226
|
+
result = []
|
|
227
|
+
in_tag = false
|
|
228
|
+
current_tags = []
|
|
229
|
+
|
|
230
|
+
lines.each do |line|
|
|
231
|
+
# Check for tag start: // tag::tagname[]
|
|
232
|
+
if line =~ %r{//\s*tag::(\w+)\[\]}
|
|
233
|
+
tag_name = Regexp.last_match(1)
|
|
234
|
+
if tags.include?(tag_name)
|
|
235
|
+
in_tag = true
|
|
236
|
+
current_tags << tag_name
|
|
237
|
+
end
|
|
238
|
+
next
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Check for tag end: // end::tagname[]
|
|
242
|
+
if line =~ %r{//\s*end::(\w+)\[\]}
|
|
243
|
+
tag_name = Regexp.last_match(1)
|
|
244
|
+
if current_tags.include?(tag_name)
|
|
245
|
+
current_tags.delete(tag_name)
|
|
246
|
+
in_tag = false if current_tags.empty?
|
|
247
|
+
end
|
|
248
|
+
next
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
result << line if in_tag
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
result
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# Resolves image references with configurable strategies.
|
|
259
|
+
class ImageResolver
|
|
260
|
+
# @return [Symbol] the resolution strategy
|
|
261
|
+
attr_reader :strategy
|
|
262
|
+
|
|
263
|
+
# Create a new ImageResolver.
|
|
264
|
+
#
|
|
265
|
+
# @param strategy [Symbol] resolution strategy (:reference, :copy, :embed)
|
|
266
|
+
#
|
|
267
|
+
def initialize(strategy: :reference)
|
|
268
|
+
@strategy = strategy
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
# Resolve an image reference.
|
|
272
|
+
#
|
|
273
|
+
# @param image_node [Image::Core] the image node
|
|
274
|
+
# @param base_dir [String] base directory for relative paths
|
|
275
|
+
# @param output_dir [String, nil] output directory for :copy mode
|
|
276
|
+
# @return [Image::Core] the resolved image node
|
|
277
|
+
#
|
|
278
|
+
def resolve(image_node, base_dir, output_dir = nil)
|
|
279
|
+
case strategy
|
|
280
|
+
when :reference
|
|
281
|
+
image_node
|
|
282
|
+
when :copy
|
|
283
|
+
copy_image(image_node, base_dir, output_dir)
|
|
284
|
+
when :embed
|
|
285
|
+
embed_image(image_node, base_dir)
|
|
286
|
+
else
|
|
287
|
+
image_node
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
private
|
|
292
|
+
|
|
293
|
+
def copy_image(image_node, base_dir, output_dir)
|
|
294
|
+
return image_node unless output_dir
|
|
295
|
+
|
|
296
|
+
src = image_node.src
|
|
297
|
+
return image_node if src.nil? || src.start_with?('data:')
|
|
298
|
+
|
|
299
|
+
full_path = File.expand_path(src, base_dir)
|
|
300
|
+
|
|
301
|
+
unless File.exist?(full_path)
|
|
302
|
+
warn "[Coradoc] Warning: Image file not found: #{src}"
|
|
303
|
+
return image_node
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# Create images subdirectory in output
|
|
307
|
+
images_dir = File.join(output_dir, 'images')
|
|
308
|
+
FileUtils.mkdir_p(images_dir)
|
|
309
|
+
|
|
310
|
+
# Copy the file
|
|
311
|
+
dest_path = File.join(images_dir, File.basename(src))
|
|
312
|
+
FileUtils.cp(full_path, dest_path)
|
|
313
|
+
|
|
314
|
+
# Update the image node with new path
|
|
315
|
+
new_node = image_node.class.new(image_node.to_h)
|
|
316
|
+
new_node.src = "images/#{File.basename(src)}"
|
|
317
|
+
new_node
|
|
318
|
+
rescue StandardError => e
|
|
319
|
+
warn "[Coradoc] Warning: Failed to copy image #{src}: #{e.message}"
|
|
320
|
+
image_node
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def embed_image(image_node, base_dir)
|
|
324
|
+
src = image_node.src
|
|
325
|
+
return image_node if src.nil? || src.start_with?('data:')
|
|
326
|
+
|
|
327
|
+
full_path = File.expand_path(src, base_dir)
|
|
328
|
+
|
|
329
|
+
unless File.exist?(full_path)
|
|
330
|
+
warn "[Coradoc] Warning: Image file not found: #{src}"
|
|
331
|
+
return image_node
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
# Read and encode the image
|
|
335
|
+
image_data = File.read(full_path, mode: 'rb')
|
|
336
|
+
base64_data = Base64.strict_encode64(image_data)
|
|
337
|
+
|
|
338
|
+
# Determine MIME type from extension
|
|
339
|
+
mime_type = mime_type_for(File.extname(src))
|
|
340
|
+
|
|
341
|
+
# Create data URI
|
|
342
|
+
data_uri = "data:#{mime_type};base64,#{base64_data}"
|
|
343
|
+
|
|
344
|
+
# Update the image node
|
|
345
|
+
new_node = image_node.class.new(image_node.to_h)
|
|
346
|
+
new_node.src = data_uri
|
|
347
|
+
new_node
|
|
348
|
+
rescue StandardError => e
|
|
349
|
+
warn "[Coradoc] Warning: Failed to embed image #{src}: #{e.message}"
|
|
350
|
+
image_node
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
def mime_type_for(extension)
|
|
354
|
+
case extension.downcase
|
|
355
|
+
when '.png' then 'image/png'
|
|
356
|
+
when '.jpg', '.jpeg' then 'image/jpeg'
|
|
357
|
+
when '.gif' then 'image/gif'
|
|
358
|
+
when '.svg' then 'image/svg+xml'
|
|
359
|
+
when '.webp' then 'image/webp'
|
|
360
|
+
else 'application/octet-stream'
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
# Resolves media (video/audio) references with configurable strategies.
|
|
366
|
+
class MediaResolver
|
|
367
|
+
# @return [Symbol] the resolution strategy
|
|
368
|
+
attr_reader :strategy
|
|
369
|
+
|
|
370
|
+
# Create a new MediaResolver.
|
|
371
|
+
#
|
|
372
|
+
# @param strategy [Symbol] resolution strategy (:reference, :copy)
|
|
373
|
+
#
|
|
374
|
+
def initialize(strategy: :reference)
|
|
375
|
+
@strategy = strategy
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
# Resolve a media reference.
|
|
379
|
+
#
|
|
380
|
+
# @param media_node [Video, Audio] the media node
|
|
381
|
+
# @param base_dir [String] base directory for relative paths
|
|
382
|
+
# @param output_dir [String, nil] output directory for :copy mode
|
|
383
|
+
# @return [Video, Audio] the resolved media node
|
|
384
|
+
#
|
|
385
|
+
def resolve(media_node, base_dir, output_dir = nil)
|
|
386
|
+
case strategy
|
|
387
|
+
when :reference
|
|
388
|
+
media_node
|
|
389
|
+
when :copy
|
|
390
|
+
copy_media(media_node, base_dir, output_dir)
|
|
391
|
+
else
|
|
392
|
+
media_node
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
private
|
|
397
|
+
|
|
398
|
+
def copy_media(media_node, base_dir, output_dir)
|
|
399
|
+
return media_node unless output_dir
|
|
400
|
+
|
|
401
|
+
src = media_node.src
|
|
402
|
+
return media_node if src.nil? || src.match?(%r{^[a-z][a-z0-9+.-]*://}i)
|
|
403
|
+
|
|
404
|
+
full_path = File.expand_path(src, base_dir)
|
|
405
|
+
|
|
406
|
+
unless File.exist?(full_path)
|
|
407
|
+
warn "[Coradoc] Warning: Media file not found: #{src}"
|
|
408
|
+
return media_node
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
# Create media subdirectory in output
|
|
412
|
+
media_dir = File.join(output_dir, 'media')
|
|
413
|
+
FileUtils.mkdir_p(media_dir)
|
|
414
|
+
|
|
415
|
+
# Copy the file
|
|
416
|
+
dest_path = File.join(media_dir, File.basename(src))
|
|
417
|
+
FileUtils.cp(full_path, dest_path)
|
|
418
|
+
|
|
419
|
+
# Update the media node with new path
|
|
420
|
+
new_node = media_node.class.new(media_node.to_h)
|
|
421
|
+
new_node.src = "media/#{File.basename(src)}"
|
|
422
|
+
new_node
|
|
423
|
+
rescue StandardError => e
|
|
424
|
+
warn "[Coradoc] Warning: Failed to copy media #{src}: #{e.message}"
|
|
425
|
+
media_node
|
|
426
|
+
end
|
|
427
|
+
end
|
|
428
|
+
end
|
|
429
|
+
end
|
|
430
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module AsciiDoc
|
|
5
|
+
module Model
|
|
6
|
+
# Reviewer note block element for AsciiDoc documents.
|
|
7
|
+
#
|
|
8
|
+
# Reviewer notes capture feedback from document reviewers with
|
|
9
|
+
# metadata about who provided the feedback and when.
|
|
10
|
+
#
|
|
11
|
+
# @!attribute [r] reviewer
|
|
12
|
+
# @return [String, nil] Name of the reviewer
|
|
13
|
+
#
|
|
14
|
+
# @!attribute [r] date
|
|
15
|
+
# @return [String, nil] Date of the review
|
|
16
|
+
#
|
|
17
|
+
# @!attribute [r] from
|
|
18
|
+
# @return [String, nil] Review start indicator
|
|
19
|
+
#
|
|
20
|
+
# @!attribute [r] to
|
|
21
|
+
# @return [String, nil] Review end indicator
|
|
22
|
+
#
|
|
23
|
+
# @!attribute [r] content
|
|
24
|
+
# @return [Array<Coradoc::AsciiDoc::Model::Base>] Polymorphic content (paragraphs, lists, admonitions, etc.)
|
|
25
|
+
#
|
|
26
|
+
# @example Create a reviewer note
|
|
27
|
+
# note = Coradoc::AsciiDoc::Model::ReviewerNote.new
|
|
28
|
+
# note.reviewer = "John Doe"
|
|
29
|
+
# note.date = "2024-01-15"
|
|
30
|
+
# note.content = [Coradoc::AsciiDoc::Model::Paragraph.new]
|
|
31
|
+
#
|
|
32
|
+
class ReviewerNote < Base
|
|
33
|
+
# Reviewer note attributes
|
|
34
|
+
attribute :reviewer, :string
|
|
35
|
+
attribute :date, :string
|
|
36
|
+
attribute :from, :string
|
|
37
|
+
attribute :to, :string
|
|
38
|
+
|
|
39
|
+
# Content can be any AsciiDoc elements (paragraphs, lists, etc.)
|
|
40
|
+
attribute :content,
|
|
41
|
+
Coradoc::AsciiDoc::Model::Base,
|
|
42
|
+
collection: true,
|
|
43
|
+
initialize_empty: true,
|
|
44
|
+
polymorphic: [
|
|
45
|
+
Coradoc::AsciiDoc::Model::TextElement,
|
|
46
|
+
Coradoc::AsciiDoc::Model::Paragraph,
|
|
47
|
+
Coradoc::AsciiDoc::Model::Admonition,
|
|
48
|
+
Coradoc::AsciiDoc::Model::LineBreak,
|
|
49
|
+
Coradoc::AsciiDoc::Model::List::Core
|
|
50
|
+
]
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Coradoc
|
|
4
|
+
module AsciiDoc
|
|
5
|
+
module Model
|
|
6
|
+
# Revision information for document headers.
|
|
7
|
+
#
|
|
8
|
+
# Revision metadata tracks document version numbers, dates, and remarks.
|
|
9
|
+
#
|
|
10
|
+
# @!attribute [r] number
|
|
11
|
+
# @return [String, nil] The revision number (e.g., "1.0", "2.1")
|
|
12
|
+
#
|
|
13
|
+
# @!attribute [r] date
|
|
14
|
+
# @return [Date, nil] The revision date
|
|
15
|
+
#
|
|
16
|
+
# @!attribute [r] remark
|
|
17
|
+
# @return [String, nil] Optional revision notes
|
|
18
|
+
#
|
|
19
|
+
# @example Create a revision
|
|
20
|
+
# rev = Coradoc::AsciiDoc::Model::Revision.new
|
|
21
|
+
# rev.number = "1.0"
|
|
22
|
+
# rev.date = Date.new(2024, 1, 15)
|
|
23
|
+
# rev.remark = "Initial release"
|
|
24
|
+
#
|
|
25
|
+
# @raise [TypeError] if date is not a Date object
|
|
26
|
+
#
|
|
27
|
+
class Revision < Base
|
|
28
|
+
attribute :number, :string
|
|
29
|
+
attribute :date, :date
|
|
30
|
+
attribute :remark, :string
|
|
31
|
+
|
|
32
|
+
def validate
|
|
33
|
+
super
|
|
34
|
+
validate_date_type
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def validate_date_type
|
|
40
|
+
return if date.nil? || date.is_a?(Date)
|
|
41
|
+
|
|
42
|
+
raise TypeError, "date must be a Date, got #{date.class}"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|