metanorma-plugin-plantuml 1.0.14 → 1.0.15
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/.rubocop_todo.yml +2 -3
- data/lib/metanorma/plugin/plantuml/backend.rb +39 -82
- data/lib/metanorma/plugin/plantuml/block_processor.rb +6 -10
- data/lib/metanorma/plugin/plantuml/block_processor_base.rb +5 -10
- data/lib/metanorma/plugin/plantuml/filename_resolver.rb +42 -0
- data/lib/metanorma/plugin/plantuml/image_block_macro_processor.rb +23 -14
- data/lib/metanorma/plugin/plantuml/version.rb +1 -1
- data/lib/metanorma/plugin/plantuml/wrapper.rb +1 -38
- data/lib/metanorma-plugin-plantuml.rb +2 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2c4b579643b6072bf87b205256a322daee254f200b829539d5f972da33ac46c0
|
|
4
|
+
data.tar.gz: 3ba87fc795b42832ee26a4dce6450d83c53c5625d24e0ab17fa51cf056b60feb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d602eddb79c725717821bdeaeb1bc9eba1a7c67f2e1096ce282b288d5f958cd8af9432fbeecddf55ae285eb0d4e608cca4f844a582f56d384f1b236ad109e153
|
|
7
|
+
data.tar.gz: 1eb514f53f64d1782efd2d5e9b375478e08501bea47394450f34001fadbb7494ccfda070022fb9b5ea7a56e994e0b80bb06be1ffc441ef9ce90b59b49a27b156
|
data/.rubocop_todo.yml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# This configuration was generated by
|
|
2
2
|
# `rubocop --auto-gen-config`
|
|
3
|
-
# on 2026-05-
|
|
3
|
+
# on 2026-05-12 02:38:27 UTC using RuboCop version 1.86.1.
|
|
4
4
|
# The point is for the user to remove these configuration records
|
|
5
5
|
# one by one as the offenses are removed from the code base.
|
|
6
6
|
# Note that changes in the inspected code, or installation of new
|
|
@@ -11,14 +11,13 @@ Gemspec/RequiredRubyVersion:
|
|
|
11
11
|
Exclude:
|
|
12
12
|
- 'metanorma-plugin-plantuml.gemspec'
|
|
13
13
|
|
|
14
|
-
# Offense count:
|
|
14
|
+
# Offense count: 6
|
|
15
15
|
# This cop supports safe autocorrection (--autocorrect).
|
|
16
16
|
# Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, AllowRBSInlineAnnotation, AllowCopDirectives, AllowedPatterns, SplitStrings.
|
|
17
17
|
# URISchemes: http, https
|
|
18
18
|
Layout/LineLength:
|
|
19
19
|
Exclude:
|
|
20
20
|
- 'Rakefile'
|
|
21
|
-
- 'lib/metanorma/plugin/plantuml/backend.rb'
|
|
22
21
|
- 'lib/metanorma/plugin/plantuml/config.rb'
|
|
23
22
|
- 'lib/metanorma/plugin/plantuml/wrapper.rb'
|
|
24
23
|
- 'spec/metanorma/plugin/plantuml/plantuml_integration_spec.rb'
|
|
@@ -6,14 +6,12 @@ require "fileutils"
|
|
|
6
6
|
module Metanorma
|
|
7
7
|
module Plugin
|
|
8
8
|
module Plantuml
|
|
9
|
-
# Backend class for PlantUML diagram generation
|
|
10
|
-
# Adapted from metanorma-standoc's PlantUMLBlockMacroBackend
|
|
11
9
|
class Backend
|
|
12
10
|
class << self
|
|
13
11
|
def plantuml_installed?
|
|
14
12
|
return true if plantuml_available?
|
|
15
13
|
|
|
16
|
-
raise "PlantUML not installed"
|
|
14
|
+
raise PlantumlError, "PlantUML not installed"
|
|
17
15
|
end
|
|
18
16
|
|
|
19
17
|
def plantuml_available?
|
|
@@ -21,53 +19,64 @@ module Metanorma
|
|
|
21
19
|
end
|
|
22
20
|
|
|
23
21
|
def generate_file( # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
24
|
-
parent,
|
|
22
|
+
parent, source, format_override: nil, options: {}
|
|
25
23
|
)
|
|
26
24
|
ldir, imagesdir, fmt = generate_file_prep(parent)
|
|
27
25
|
fmt = format_override if format_override
|
|
28
|
-
|
|
26
|
+
validate_source(source)
|
|
29
27
|
|
|
30
|
-
# Extract filename from PlantUML source if specified
|
|
31
28
|
filename = generate_unique_filename(fmt)
|
|
32
|
-
extracted_filename =
|
|
29
|
+
extracted_filename = FilenameResolver.extract(source)
|
|
33
30
|
|
|
34
|
-
|
|
31
|
+
if extracted_filename
|
|
32
|
+
safe_filename = FilenameResolver.sanitize(extracted_filename)
|
|
33
|
+
filename = "#{safe_filename}.#{fmt}"
|
|
34
|
+
end
|
|
35
35
|
|
|
36
36
|
absolute_path, relative_path = path_prep(ldir, imagesdir)
|
|
37
37
|
output_file = File.join(absolute_path, filename)
|
|
38
38
|
|
|
39
39
|
result = Wrapper.generate(
|
|
40
|
-
|
|
40
|
+
source,
|
|
41
41
|
format: fmt,
|
|
42
42
|
output_file: output_file,
|
|
43
43
|
includedirs: options[:includedirs],
|
|
44
44
|
layout: options[:layout],
|
|
45
45
|
)
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
unless result[:success]
|
|
48
|
+
raise GenerationError,
|
|
49
|
+
"No image output from PlantUML: #{result[:error].message}"
|
|
50
|
+
end
|
|
48
51
|
|
|
49
52
|
File.join(relative_path, filename)
|
|
50
53
|
end
|
|
51
54
|
|
|
52
55
|
def generate_multiple_files(
|
|
53
|
-
parent,
|
|
56
|
+
parent, source, formats, attrs, options: {}
|
|
54
57
|
)
|
|
55
|
-
# Generate files for each format
|
|
56
58
|
filenames = formats.map do |format|
|
|
57
|
-
generate_file(parent,
|
|
59
|
+
generate_file(parent, source,
|
|
60
|
+
format_override: format, options: options)
|
|
58
61
|
end
|
|
59
62
|
|
|
60
|
-
# Return data for BlockProcessor to create image block
|
|
61
63
|
through_attrs = generate_attrs attrs
|
|
62
64
|
through_attrs["target"] = filenames.first
|
|
63
|
-
|
|
64
|
-
# Store additional formats for potential future use
|
|
65
65
|
through_attrs["data-formats"] = formats.join(",")
|
|
66
66
|
through_attrs["data-files"] = filenames.join(",")
|
|
67
67
|
|
|
68
68
|
through_attrs
|
|
69
69
|
end
|
|
70
70
|
|
|
71
|
+
def validate_source(source)
|
|
72
|
+
diagram_type = extract_diagram_type(source)
|
|
73
|
+
return if diagram_type && matching_end?(source, diagram_type)
|
|
74
|
+
|
|
75
|
+
raise GenerationError,
|
|
76
|
+
"@start#{diagram_type} without matching " \
|
|
77
|
+
"@end#{diagram_type} in PlantUML!"
|
|
78
|
+
end
|
|
79
|
+
|
|
71
80
|
def generate_file_prep(parent) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
|
72
81
|
imagesdir = if parent.document.attr("docfile") &&
|
|
73
82
|
parent.document.attr("imagesdir")
|
|
@@ -92,11 +101,11 @@ module Metanorma
|
|
|
92
101
|
ret = Utils.localdir(parent.document)
|
|
93
102
|
return ret if File.writable?(ret)
|
|
94
103
|
|
|
95
|
-
raise
|
|
104
|
+
raise GenerationError,
|
|
105
|
+
"Destination directory #{ret} not writable for PlantUML!"
|
|
96
106
|
end
|
|
97
107
|
|
|
98
108
|
def path_prep(localdir, imagesdir) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
|
99
|
-
# Determine source path
|
|
100
109
|
sourcepath = if imagesdir.nil?
|
|
101
110
|
localdir
|
|
102
111
|
elsif Pathname.new(imagesdir).absolute?
|
|
@@ -105,14 +114,14 @@ module Metanorma
|
|
|
105
114
|
File.join(localdir, imagesdir)
|
|
106
115
|
end
|
|
107
116
|
|
|
108
|
-
# Determine PlantUML images destination absolute path
|
|
109
117
|
path = Pathname.new(
|
|
110
118
|
File.expand_path(File.join(localdir, "_plantuml_images")),
|
|
111
119
|
)
|
|
112
120
|
path.mkpath
|
|
113
121
|
|
|
114
122
|
unless File.writable?(path)
|
|
115
|
-
raise
|
|
123
|
+
raise GenerationError,
|
|
124
|
+
"Destination path #{path} not writable for PlantUML!"
|
|
116
125
|
end
|
|
117
126
|
|
|
118
127
|
[
|
|
@@ -122,28 +131,6 @@ module Metanorma
|
|
|
122
131
|
]
|
|
123
132
|
end
|
|
124
133
|
|
|
125
|
-
def prep_source(parent, reader) # rubocop:disable Metrics/MethodLength
|
|
126
|
-
src = if reader.respond_to?(:source)
|
|
127
|
-
# get content from BlockProcessor
|
|
128
|
-
reader.source
|
|
129
|
-
else
|
|
130
|
-
# get content from ImageBlockMacroProcessor
|
|
131
|
-
docfile_directory = File.dirname(
|
|
132
|
-
parent.document.attributes["docfile"] || ".",
|
|
133
|
-
)
|
|
134
|
-
|
|
135
|
-
resolved_path = parent.document
|
|
136
|
-
.path_resolver
|
|
137
|
-
.system_path(reader, docfile_directory)
|
|
138
|
-
|
|
139
|
-
File.read(resolved_path, encoding: "UTF-8")
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
# Validate that we have matching start/end pairs
|
|
143
|
-
validate_plantuml_delimiters(src)
|
|
144
|
-
src
|
|
145
|
-
end
|
|
146
|
-
|
|
147
134
|
def generate_attrs(attrs)
|
|
148
135
|
%w[id align float title role width height alt]
|
|
149
136
|
.each_with_object({}) do |key, memo|
|
|
@@ -153,49 +140,19 @@ module Metanorma
|
|
|
153
140
|
|
|
154
141
|
private
|
|
155
142
|
|
|
156
|
-
def
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
diagram_type = start_match[1].downcase
|
|
163
|
-
end_pattern = "@end#{diagram_type}"
|
|
164
|
-
|
|
165
|
-
# Look for matching @end... directive (case insensitive)
|
|
166
|
-
return if /#{Regexp.escape(end_pattern)}\s*$/mi.match?(src)
|
|
167
|
-
|
|
168
|
-
raise "@start#{diagram_type} without matching #{end_pattern} " \
|
|
169
|
-
"in PlantUML!"
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
def extract_plantuml_filename(plantuml_content)
|
|
173
|
-
# Extract filename from any @start... line if specified
|
|
174
|
-
# Only match when filename is on the same line as @start...
|
|
175
|
-
# (no newlines)
|
|
176
|
-
lines = plantuml_content.lines
|
|
177
|
-
first_line = lines.first&.strip
|
|
178
|
-
return nil unless first_line
|
|
179
|
-
|
|
180
|
-
match = first_line.match(/^@start\w+\s+(.+)$/i)
|
|
181
|
-
return nil unless match
|
|
182
|
-
|
|
183
|
-
filename = match[1].strip
|
|
184
|
-
return nil if filename.nil? || filename.empty?
|
|
143
|
+
def extract_diagram_type(source)
|
|
144
|
+
match = source.match(/^@start(\w+)/i)
|
|
145
|
+
unless match
|
|
146
|
+
raise GenerationError,
|
|
147
|
+
"PlantUML content must start with @start... directive!"
|
|
148
|
+
end
|
|
185
149
|
|
|
186
|
-
|
|
187
|
-
sanitize_filename(filename)
|
|
150
|
+
match[1].downcase
|
|
188
151
|
end
|
|
189
152
|
|
|
190
|
-
def
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
# Replace any non-alphanumeric characters
|
|
194
|
-
# (except dash, underscore, dot) with underscore
|
|
195
|
-
filename.gsub(/[^\w\-.]/, "_")
|
|
196
|
-
.gsub(/\.{2,}/, "_") # Replace multiple dots with underscore
|
|
197
|
-
.gsub(/_{2,}/, "_") # Replace multiple underscores with single
|
|
198
|
-
.gsub(/^[_\-.]+|[_\-.]+$/, "") # Remove leading/trailing chars
|
|
153
|
+
def matching_end?(source, diagram_type)
|
|
154
|
+
end_directive = "@end#{diagram_type}"
|
|
155
|
+
source.downcase.include?(end_directive.downcase)
|
|
199
156
|
end
|
|
200
157
|
|
|
201
158
|
def generate_unique_filename(format)
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
module Metanorma
|
|
4
4
|
module Plugin
|
|
5
5
|
module Plantuml
|
|
6
|
-
# PlantUML block processor
|
|
7
6
|
class BlockProcessor < ::Asciidoctor::Extensions::BlockProcessor
|
|
8
7
|
include ::Metanorma::Plugin::Plantuml::BlockProcessorBase
|
|
9
8
|
|
|
@@ -13,25 +12,23 @@ module Metanorma
|
|
|
13
12
|
parse_content_as :raw
|
|
14
13
|
|
|
15
14
|
def process(parent, reader, attrs)
|
|
16
|
-
|
|
15
|
+
source = reader.source
|
|
16
|
+
|
|
17
17
|
if parent.document.attr("plantuml-disabled")
|
|
18
|
-
return abort(parent,
|
|
18
|
+
return abort(parent, source, attrs,
|
|
19
19
|
"PlantUML processing disabled")
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
# Check PlantUML availability explicitly
|
|
23
22
|
unless Backend.plantuml_available?
|
|
24
|
-
return abort(parent,
|
|
25
|
-
"PlantUML not installed")
|
|
23
|
+
return abort(parent, source, attrs, "PlantUML not installed")
|
|
26
24
|
end
|
|
27
25
|
|
|
28
|
-
# Parse format specifications
|
|
29
26
|
formats = parse_formats(attrs, parent.document)
|
|
30
27
|
options = parse_options(parent, reader, attrs)
|
|
31
28
|
|
|
32
|
-
process_image_block(parent,
|
|
29
|
+
process_image_block(parent, source, attrs, formats, options)
|
|
33
30
|
rescue StandardError => e
|
|
34
|
-
abort(parent,
|
|
31
|
+
abort(parent, source, attrs, e.message)
|
|
35
32
|
end
|
|
36
33
|
|
|
37
34
|
private
|
|
@@ -39,7 +36,6 @@ module Metanorma
|
|
|
39
36
|
def parse_options(parent, _reader, attrs)
|
|
40
37
|
options = {}
|
|
41
38
|
|
|
42
|
-
# Parse include directory
|
|
43
39
|
options[:includedirs] = add_attrs_to_includedirs(
|
|
44
40
|
parent.document, attrs, parse_doc_includedirs(parent.document)
|
|
45
41
|
)
|
|
@@ -7,12 +7,12 @@ module Metanorma
|
|
|
7
7
|
module Plugin
|
|
8
8
|
module Plantuml
|
|
9
9
|
module BlockProcessorBase
|
|
10
|
-
def abort(parent,
|
|
10
|
+
def abort(parent, text, attrs, msg)
|
|
11
11
|
warn msg
|
|
12
12
|
attrs["language"] = "plantuml"
|
|
13
13
|
create_listing_block(
|
|
14
14
|
parent,
|
|
15
|
-
|
|
15
|
+
text,
|
|
16
16
|
attrs.reject { |k, _v| k.to_s.match?(/^\d+$/) },
|
|
17
17
|
)
|
|
18
18
|
end
|
|
@@ -35,19 +35,16 @@ module Metanorma
|
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
def parse_formats(attrs, document) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize, Metrics/MethodLength
|
|
38
|
-
# Check for formats attribute (multiple formats)
|
|
39
38
|
if attrs["formats"]
|
|
40
39
|
formats = attrs["formats"].split(",").map(&:strip).map(&:downcase)
|
|
41
40
|
return formats.select { |f| valid_format?(f) }
|
|
42
41
|
end
|
|
43
42
|
|
|
44
|
-
# Check for format attribute (single format override)
|
|
45
43
|
if attrs["format"]
|
|
46
44
|
format = attrs["format"].strip.downcase
|
|
47
45
|
return [format] if valid_format?(format)
|
|
48
46
|
end
|
|
49
47
|
|
|
50
|
-
# Fall back to document attribute or default
|
|
51
48
|
default_format = document
|
|
52
49
|
.attr("plantuml-image-format")&.strip&.downcase ||
|
|
53
50
|
Wrapper::DEFAULT_FORMAT
|
|
@@ -74,19 +71,17 @@ module Metanorma
|
|
|
74
71
|
includedirs.compact.uniq
|
|
75
72
|
end
|
|
76
73
|
|
|
77
|
-
def process_image_block(parent,
|
|
74
|
+
def process_image_block(parent, source, attrs, formats, options) # rubocop:disable Metrics/MethodLength
|
|
78
75
|
if formats.length == 1
|
|
79
|
-
# Single format - original behavior
|
|
80
76
|
filename = Backend.generate_file(
|
|
81
|
-
parent,
|
|
77
|
+
parent, source, format_override: formats.first, options: options
|
|
82
78
|
)
|
|
83
79
|
through_attrs = Backend.generate_attrs(attrs)
|
|
84
80
|
through_attrs["target"] = filename
|
|
85
81
|
else
|
|
86
|
-
# Multiple formats - generate multiple files
|
|
87
82
|
through_attrs = Backend
|
|
88
83
|
.generate_multiple_files(
|
|
89
|
-
parent,
|
|
84
|
+
parent, source, formats, attrs, options: options
|
|
90
85
|
)
|
|
91
86
|
end
|
|
92
87
|
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Plugin
|
|
5
|
+
module Plantuml
|
|
6
|
+
class FilenameResolver
|
|
7
|
+
class << self
|
|
8
|
+
def extract(content)
|
|
9
|
+
first_line = content.lines.first&.strip
|
|
10
|
+
return nil unless first_line
|
|
11
|
+
|
|
12
|
+
match = first_line.match(/^@start\w+\s+([^\n]+)/i)
|
|
13
|
+
return nil unless match
|
|
14
|
+
|
|
15
|
+
filename = match[1].strip
|
|
16
|
+
return nil if filename.empty?
|
|
17
|
+
|
|
18
|
+
filename.gsub(/^["']|["']$/, "")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def sanitize(filename)
|
|
22
|
+
result = filename.gsub(/[^\w\-.]/, "_")
|
|
23
|
+
result = result.tr_s("._", "._")
|
|
24
|
+
result = result.gsub(/\.{2,}/, ".")
|
|
25
|
+
result = result.gsub(/_{2,}/, "_")
|
|
26
|
+
strip_special_edges(result)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def strip_special_edges(str)
|
|
32
|
+
result = str
|
|
33
|
+
trim = { "_" => true, "-" => true, "." => true }
|
|
34
|
+
result = result[1..] while result.length > 1 && trim[result[0]]
|
|
35
|
+
result = result[..-2] while result.length > 1 && trim[result[-1]]
|
|
36
|
+
result
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -3,37 +3,47 @@
|
|
|
3
3
|
module Metanorma
|
|
4
4
|
module Plugin
|
|
5
5
|
module Plantuml
|
|
6
|
-
# PlantUML block processor
|
|
7
6
|
class ImageBlockMacroProcessor < ::Asciidoctor::Extensions::BlockMacroProcessor
|
|
8
7
|
include ::Metanorma::Plugin::Plantuml::BlockProcessorBase
|
|
9
8
|
|
|
10
9
|
use_dsl
|
|
11
10
|
named :plantuml_image
|
|
12
11
|
|
|
13
|
-
def process(parent,
|
|
14
|
-
|
|
12
|
+
def process(parent, target, attrs)
|
|
13
|
+
display_text = target
|
|
14
|
+
|
|
15
15
|
if parent.document.attr("plantuml-disabled")
|
|
16
|
-
return abort(parent,
|
|
16
|
+
return abort(parent, display_text, attrs,
|
|
17
17
|
"PlantUML processing disabled")
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
# Check PlantUML availability explicitly
|
|
21
20
|
unless Backend.plantuml_available?
|
|
22
|
-
return abort(parent,
|
|
23
|
-
"PlantUML not installed")
|
|
21
|
+
return abort(parent, display_text, attrs, "PlantUML not installed")
|
|
24
22
|
end
|
|
25
23
|
|
|
26
|
-
|
|
24
|
+
source = read_source_file(parent, target)
|
|
27
25
|
formats = parse_formats(attrs, parent.document)
|
|
28
|
-
options = parse_options(parent,
|
|
26
|
+
options = parse_options(parent, target, attrs)
|
|
29
27
|
|
|
30
|
-
process_image_block(parent,
|
|
28
|
+
process_image_block(parent, source, attrs, formats, options)
|
|
31
29
|
rescue StandardError => e
|
|
32
|
-
abort(parent,
|
|
30
|
+
abort(parent, display_text, attrs, e.message)
|
|
33
31
|
end
|
|
34
32
|
|
|
35
33
|
private
|
|
36
34
|
|
|
35
|
+
def read_source_file(parent, target)
|
|
36
|
+
docfile_directory = File.dirname(
|
|
37
|
+
parent.document.attributes["docfile"] || ".",
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
resolved_path = parent.document
|
|
41
|
+
.path_resolver
|
|
42
|
+
.system_path(target, docfile_directory)
|
|
43
|
+
|
|
44
|
+
File.read(resolved_path, encoding: "UTF-8")
|
|
45
|
+
end
|
|
46
|
+
|
|
37
47
|
def add_image_path_to_includedirs(document, image_path, includedirs)
|
|
38
48
|
docdir = document.attributes["docdir"]
|
|
39
49
|
includedirs << File.dirname(
|
|
@@ -42,16 +52,15 @@ module Metanorma
|
|
|
42
52
|
includedirs.compact.uniq
|
|
43
53
|
end
|
|
44
54
|
|
|
45
|
-
def parse_options(parent,
|
|
55
|
+
def parse_options(parent, target, attrs) # rubocop:disable Metrics/AbcSize
|
|
46
56
|
options = {}
|
|
47
57
|
|
|
48
|
-
# Parse include directory
|
|
49
58
|
options[:includedirs] = parse_doc_includedirs(parent.document)
|
|
50
59
|
options[:includedirs] = add_attrs_to_includedirs(
|
|
51
60
|
parent.document, attrs, options[:includedirs]
|
|
52
61
|
)
|
|
53
62
|
options[:includedirs] = add_image_path_to_includedirs(
|
|
54
|
-
parent.document,
|
|
63
|
+
parent.document, target, options[:includedirs]
|
|
55
64
|
)
|
|
56
65
|
|
|
57
66
|
if attrs["layout"]
|
|
@@ -67,7 +67,6 @@ module Metanorma
|
|
|
67
67
|
output, _, status = Open3.capture3(*cmd)
|
|
68
68
|
|
|
69
69
|
if status.success?
|
|
70
|
-
# Extract version from output
|
|
71
70
|
version_match = output.match(/PlantUML version ([\d.]+)/)
|
|
72
71
|
version_match ? version_match[1] : PLANTUML_JAR_VERSION
|
|
73
72
|
end
|
|
@@ -141,24 +140,16 @@ module Metanorma
|
|
|
141
140
|
end
|
|
142
141
|
|
|
143
142
|
def execute_plantuml(content, format, output_file, options) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize, Metrics/MethodLength
|
|
144
|
-
# PlantUML generates output files based on filename specified in
|
|
145
|
-
# @start... line
|
|
146
|
-
# We need to use a temp directory and then move the file
|
|
147
143
|
Dir.mktmpdir do |temp_dir| # rubocop:disable Metrics/BlockLength
|
|
148
|
-
# create input file
|
|
149
144
|
File.write("#{temp_dir}/plantuml_input.puml", content)
|
|
150
145
|
|
|
151
|
-
# Handle include files
|
|
152
146
|
if options[:include_files] && !options[:include_files].empty?
|
|
153
147
|
if options[:includedirs].empty?
|
|
154
|
-
# raise error when include files are found but includedirs
|
|
155
|
-
# is nil
|
|
156
148
|
raise PlantumlError,
|
|
157
149
|
"includedirs is required when include files are specified"
|
|
158
150
|
end
|
|
159
151
|
|
|
160
152
|
options[:include_files].each do |include_file|
|
|
161
|
-
# find local include file in includedirs
|
|
162
153
|
found_include_file = nil
|
|
163
154
|
options[:includedirs].each do |includedir|
|
|
164
155
|
include_file_path = Pathname.new(includedir)
|
|
@@ -172,7 +163,6 @@ module Metanorma
|
|
|
172
163
|
|
|
173
164
|
next unless found_include_file
|
|
174
165
|
|
|
175
|
-
# create include file in temp directory
|
|
176
166
|
temp_include_file = Pathname.new(temp_dir)
|
|
177
167
|
.join(include_file).to_s
|
|
178
168
|
|
|
@@ -202,14 +192,12 @@ module Metanorma
|
|
|
202
192
|
raise GenerationError.new(error_message, error)
|
|
203
193
|
end
|
|
204
194
|
|
|
205
|
-
# Find the generated file and move it to the desired location
|
|
206
195
|
if output_file
|
|
207
196
|
generated_file = find_generated_file(temp_dir, content,
|
|
208
197
|
format)
|
|
209
198
|
if generated_file && File.exist?(generated_file)
|
|
210
199
|
FileUtils.mv(generated_file, output_file)
|
|
211
200
|
else
|
|
212
|
-
# Debug: List what files were actually generated
|
|
213
201
|
generated_files = Dir.glob(File.join(temp_dir, "*"))
|
|
214
202
|
error_msg = "Generated file not found in temp directory. "
|
|
215
203
|
error_msg += "Expected: #{generated_file}. "
|
|
@@ -225,37 +213,19 @@ module Metanorma
|
|
|
225
213
|
end
|
|
226
214
|
|
|
227
215
|
def find_generated_file(temp_dir, content, format)
|
|
228
|
-
# PlantUML generates files based on the filename specified in
|
|
229
|
-
# @start... line
|
|
230
216
|
extension = format.to_s.downcase
|
|
231
217
|
|
|
232
|
-
|
|
233
|
-
plantuml_filename = extract_plantuml_filename_from_content(content)
|
|
218
|
+
plantuml_filename = FilenameResolver.extract(content)
|
|
234
219
|
|
|
235
220
|
if plantuml_filename
|
|
236
|
-
# Look for file with PlantUML-specified name
|
|
237
221
|
generated_file = File.join(temp_dir,
|
|
238
222
|
"#{plantuml_filename}.#{extension}")
|
|
239
223
|
return generated_file if File.exist?(generated_file)
|
|
240
224
|
end
|
|
241
225
|
|
|
242
|
-
# Fallback: scan directory for any generated files with
|
|
243
|
-
# the correct extension
|
|
244
226
|
Dir.glob(File.join(temp_dir, "*.#{extension}")).first
|
|
245
227
|
end
|
|
246
228
|
|
|
247
|
-
def extract_plantuml_filename_from_content(content)
|
|
248
|
-
# Extract the raw filename from @start... line (don't sanitize)
|
|
249
|
-
match = content.match(/^@start\w+\s+(.{1,999})$/mi)
|
|
250
|
-
return nil unless match
|
|
251
|
-
|
|
252
|
-
filename = match[1].strip.split("\n").first&.strip
|
|
253
|
-
return nil if filename.nil? || filename.empty?
|
|
254
|
-
|
|
255
|
-
# Remove quotes but keep the original name as PlantUML will use it
|
|
256
|
-
filename.gsub(/^["']|["']$/, "")
|
|
257
|
-
end
|
|
258
|
-
|
|
259
229
|
def build_command(input_file, format, output_dir, options) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
|
|
260
230
|
cmd = [
|
|
261
231
|
configuration.java_path,
|
|
@@ -263,21 +233,14 @@ module Metanorma
|
|
|
263
233
|
"-jar", PLANTUML_JAR_PATH
|
|
264
234
|
]
|
|
265
235
|
|
|
266
|
-
# Add format-specific options
|
|
267
236
|
format_str = format.to_s.downcase
|
|
268
237
|
cmd << "-t#{format_str}" if SUPPORTED_FORMATS.include?(format_str)
|
|
269
238
|
|
|
270
|
-
# Use 'smetana' layout engine for pragma
|
|
271
239
|
layout = options[:layout] || "smetana"
|
|
272
240
|
cmd << "-Playout=#{layout}"
|
|
273
241
|
|
|
274
|
-
# Add output directory option
|
|
275
242
|
cmd << "-o" << output_dir
|
|
276
|
-
|
|
277
|
-
# Add charset option for better compatibility
|
|
278
243
|
cmd << "-charset" << "UTF-8"
|
|
279
|
-
|
|
280
|
-
# Add input file
|
|
281
244
|
cmd << input_file
|
|
282
245
|
|
|
283
246
|
cmd
|
|
@@ -27,6 +27,8 @@ module Metanorma
|
|
|
27
27
|
"metanorma/plugin/plantuml/invalid_format_error"
|
|
28
28
|
autoload :Config, "metanorma/plugin/plantuml/config"
|
|
29
29
|
autoload :Utils, "metanorma/plugin/plantuml/utils"
|
|
30
|
+
autoload :FilenameResolver,
|
|
31
|
+
"metanorma/plugin/plantuml/filename_resolver"
|
|
30
32
|
autoload :Wrapper, "metanorma/plugin/plantuml/wrapper"
|
|
31
33
|
autoload :Backend, "metanorma/plugin/plantuml/backend"
|
|
32
34
|
autoload :BlockProcessorBase,
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: metanorma-plugin-plantuml
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.15
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-05-
|
|
11
|
+
date: 2026-05-12 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: asciidoctor
|
|
@@ -49,6 +49,7 @@ files:
|
|
|
49
49
|
- lib/metanorma/plugin/plantuml/block_processor.rb
|
|
50
50
|
- lib/metanorma/plugin/plantuml/block_processor_base.rb
|
|
51
51
|
- lib/metanorma/plugin/plantuml/config.rb
|
|
52
|
+
- lib/metanorma/plugin/plantuml/filename_resolver.rb
|
|
52
53
|
- lib/metanorma/plugin/plantuml/generation_error.rb
|
|
53
54
|
- lib/metanorma/plugin/plantuml/image_block_macro_processor.rb
|
|
54
55
|
- lib/metanorma/plugin/plantuml/invalid_format_error.rb
|