metanorma-plugin-plantuml 1.0.0 → 1.0.1

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: 82db5cce760911ac43ad666a3d25e800341ff7b4f591d0905562f2e8bccb99e2
4
- data.tar.gz: 170923ac6b7ace25020922e57904417ced9a3ef6d6f311a83be9ce0ab65b281f
3
+ metadata.gz: 07e9ee8fdf5e99900198be1e8ea51dd948957c62475694b0b40660fd25bff4d2
4
+ data.tar.gz: c6b49a7ec9b20d96e45c6600f2a368c3b5df542e6ee3bcc3f75ed17dd7d929bc
5
5
  SHA512:
6
- metadata.gz: 1347f4e1aab08371499c23dd2b39dfdc3aa3371f1db258ffa36ad557513a915e30b70e470df5f37d5766df38ca106dc54cbaad146f09cb135dfa1c41cab5e5f7
7
- data.tar.gz: 1bb6c111325ab23cbe83f3c1903701740c31573f1baa80760c03f467559c07111b81ab80fc99afbda6de0445e5dab9e1c4cf58f96fb92e24a8552f7e1946a1d6
6
+ metadata.gz: f9903747e63488c1af15f954441acbdaaa17c715935b1d0d390d515173f5e052df6defc0828de335e6491e019c69b9fa915365850a91a91155e5d68e3ee60376
7
+ data.tar.gz: 73e6bac1cfc02b057d52eb39913bfb833f0b70237fb8d00323424ef17a6b5ea302baf57faae486dda1779f0254035a4b449e0abb9648c21ceb18e30a39234778
data/Gemfile CHANGED
@@ -13,9 +13,11 @@ rescue StandardError
13
13
  end
14
14
 
15
15
  gem "byebug"
16
+ gem "canon"
16
17
  gem "debug"
17
18
  gem "metanorma"
18
- gem "metanorma-standoc", github: "metanorma/metanorma-standoc", branch: "feature/extract-plantuml"
19
+ gem "metanorma-standoc", github: "metanorma/metanorma-standoc",
20
+ branch: "feature/extract-plantuml"
19
21
  gem "rake"
20
22
  gem "rspec"
21
23
  gem "rspec-html-matchers"
@@ -25,4 +27,3 @@ gem "simplecov"
25
27
  gem "timecop"
26
28
  gem "vcr"
27
29
  gem "webmock"
28
- gem "canon"
data/Rakefile CHANGED
@@ -10,7 +10,7 @@ task default: [
10
10
  ]
11
11
 
12
12
  def uri_open(url)
13
- require 'open-uri'
13
+ require "open-uri"
14
14
  URI.parse(url).open
15
15
  end
16
16
 
@@ -29,7 +29,7 @@ file "data/plantuml.jar" do |file|
29
29
  end
30
30
 
31
31
  desc "Download PlantUML JAR file"
32
- task :download_jar => "data/plantuml.jar"
32
+ task download_jar: "data/plantuml.jar"
33
33
 
34
34
  desc "Clean downloaded JAR file"
35
35
  task :clean_jar do
@@ -23,22 +23,26 @@ module Metanorma
23
23
  Wrapper.available?
24
24
  end
25
25
 
26
- def generate_file(parent, reader, format_override = nil)
26
+ def generate_file(parent, reader, format_override = nil) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
27
27
  ldir, imagesdir, fmt = generate_file_prep(parent)
28
28
  fmt = format_override if format_override
29
29
  plantuml_content = prep_source(reader)
30
30
 
31
31
  # Extract filename from PlantUML source if specified
32
+ filename = generate_unique_filename(fmt)
32
33
  extracted_filename = extract_plantuml_filename(plantuml_content)
33
34
 
35
+ if extracted_filename
36
+ filename = "#{extracted_filename}.#{fmt}"
37
+ end
38
+
34
39
  absolute_path, relative_path = path_prep(ldir, imagesdir)
35
- filename = extracted_filename ? "#{extracted_filename}.#{fmt}" : generate_unique_filename(fmt)
36
40
  output_file = File.join(absolute_path, filename)
37
41
 
38
42
  result = Wrapper.generate(
39
43
  plantuml_content,
40
44
  format: fmt,
41
- output_file: output_file
45
+ output_file: output_file,
42
46
  )
43
47
 
44
48
  unless result[:success]
@@ -68,24 +72,32 @@ module Metanorma
68
72
  def generate_file_prep(parent)
69
73
  ldir = localdir(parent)
70
74
  imagesdir = parent.document.attr("imagesdir")
71
- fmt = parent.document.attr("plantuml-image-format")&.strip&.downcase || "png"
75
+ fmt = parent
76
+ .document.attr("plantuml-image-format")&.strip&.downcase || "png"
72
77
  [ldir, imagesdir, fmt]
73
78
  end
74
79
 
75
80
  def localdir(parent)
76
81
  ret = Utils.localdir(parent.document)
77
- File.writable?(ret) or
78
- raise "Destination directory #{ret} not writable for PlantUML!"
79
- ret
82
+ return ret if File.writable?(ret)
83
+
84
+ raise "Destination directory #{ret} not writable for PlantUML!"
80
85
  end
81
86
 
82
87
  def path_prep(localdir, imagesdir)
83
88
  path = Pathname.new(File.join(localdir, "_plantuml_images"))
84
89
  sourcepath = imagesdir ? File.join(localdir, imagesdir) : localdir
85
90
  path.mkpath
86
- File.writable?(path) or
91
+
92
+ unless File.writable?(path)
87
93
  raise "Destination path #{path} not writable for PlantUML!"
88
- [path, Pathname.new(path).relative_path_from(Pathname.new(sourcepath)).to_s]
94
+ end
95
+
96
+ [
97
+ path,
98
+ Pathname.new(path)
99
+ .relative_path_from(Pathname.new(sourcepath)).to_s,
100
+ ]
89
101
  end
90
102
 
91
103
  def prep_source(reader)
@@ -106,8 +118,9 @@ module Metanorma
106
118
 
107
119
  private
108
120
 
109
- def validate_plantuml_delimiters(src)
110
- # Find @start... pattern (case insensitive, support all PlantUML diagram types)
121
+ def validate_plantuml_delimiters(src) # rubocop:disable Metrics/MethodLength
122
+ # Find @start... pattern
123
+ # (case insensitive, support all PlantUML diagram types)
111
124
  start_match = src.match(/^@start(\w+)/i)
112
125
  unless start_match
113
126
  raise "PlantUML content must start with @start... directive!"
@@ -117,14 +130,16 @@ module Metanorma
117
130
  end_pattern = "@end#{diagram_type}"
118
131
 
119
132
  # Look for matching @end... directive (case insensitive)
120
- unless src.match(/#{Regexp.escape(end_pattern)}\s*$/mi)
121
- raise "@start#{diagram_type} without matching #{end_pattern} in PlantUML!"
133
+ unless /#{Regexp.escape(end_pattern)}\s*$/mi.match?(src)
134
+ raise "@start#{diagram_type} without matching #{end_pattern} " \
135
+ "in PlantUML!"
122
136
  end
123
137
  end
124
138
 
125
139
  def extract_plantuml_filename(plantuml_content)
126
140
  # Extract filename from any @start... line if specified
127
- # Only match when filename is on the same line as @start... (no newlines)
141
+ # Only match when filename is on the same line as @start...
142
+ # (no newlines)
128
143
  lines = plantuml_content.lines
129
144
  first_line = lines.first&.strip
130
145
  return nil unless first_line
@@ -142,11 +157,12 @@ module Metanorma
142
157
  def sanitize_filename(filename)
143
158
  # Remove quotes and sanitize for filesystem
144
159
  filename = filename.gsub(/^["']|["']$/, "")
145
- # Replace any non-alphanumeric characters (except dash, underscore, dot) with underscore
160
+ # Replace any non-alphanumeric characters
161
+ # (except dash, underscore, dot) with underscore
146
162
  filename.gsub(/[^\w\-.]/, "_")
147
- .gsub(/\.{2,}/, "_") # Replace multiple dots with underscore
148
- .gsub(/_{2,}/, "_") # Replace multiple underscores with single
149
- .gsub(/^[_\-\.]+|[_\-\.]+$/, "") # Remove leading/trailing special chars
163
+ .gsub(/\.{2,}/, "_") # Replace multiple dots with underscore
164
+ .gsub(/_{2,}/, "_") # Replace multiple underscores with single
165
+ .gsub(/^[_\-.]+|[_\-.]+$/, "") # Remove leading/trailing chars
150
166
  end
151
167
 
152
168
  def generate_unique_filename(format)
@@ -20,11 +20,11 @@ module Metanorma
20
20
  create_listing_block(
21
21
  parent,
22
22
  reader.source,
23
- (attrs.reject { |k, _v| k.to_s.match?(/^\d+$/) })
23
+ attrs.reject { |k, _v| k.to_s.match?(/^\d+$/) },
24
24
  )
25
25
  end
26
26
 
27
- def process(parent, reader, attrs)
27
+ def process(parent, reader, attrs) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
28
28
  # Check for document-level disable flag
29
29
  if parent.document.attr("plantuml-disabled")
30
30
  return abort(parent, reader, attrs, "PlantUML processing disabled")
@@ -41,21 +41,21 @@ module Metanorma
41
41
  if formats.length == 1
42
42
  # Single format - original behavior
43
43
  filename = Backend.generate_file(parent, reader, formats.first)
44
- through_attrs = Backend.generate_attrs attrs
44
+ through_attrs = Backend.generate_attrs(attrs)
45
45
  through_attrs["target"] = filename
46
- create_image_block parent, through_attrs
47
46
  else
48
47
  # Multiple formats - generate multiple files
49
- through_attrs = Backend.generate_multiple_files(parent, reader, formats, attrs)
50
- create_image_block parent, through_attrs
48
+ through_attrs = Backend
49
+ .generate_multiple_files(parent, reader, formats, attrs)
51
50
  end
51
+ create_image_block parent, through_attrs
52
52
  rescue StandardError => e
53
53
  abort(parent, reader, attrs, e.message)
54
54
  end
55
55
 
56
56
  private
57
57
 
58
- def parse_formats(attrs, document)
58
+ def parse_formats(attrs, document) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize, Metrics/MethodLength
59
59
  # Check for formats attribute (multiple formats)
60
60
  if attrs["formats"]
61
61
  formats = attrs["formats"].split(",").map(&:strip).map(&:downcase)
@@ -69,7 +69,8 @@ module Metanorma
69
69
  end
70
70
 
71
71
  # Fall back to document attribute or default
72
- default_format = document.attr("plantuml-image-format")&.strip&.downcase || "png"
72
+ default_format = document
73
+ .attr("plantuml-image-format")&.strip&.downcase || "png"
73
74
  [default_format]
74
75
  end
75
76
 
@@ -17,7 +17,7 @@ module Metanorma
17
17
  options = [
18
18
  "-Xss5m",
19
19
  "-Xmx#{memory_limit}",
20
- "-Djava.awt.headless=true"
20
+ "-Djava.awt.headless=true",
21
21
  ]
22
22
 
23
23
  if RbConfig::CONFIG["host_os"].match?(/darwin|mac os/)
@@ -12,16 +12,21 @@ module Metanorma
12
12
 
13
13
  class JarNotFoundError < PlantumlError
14
14
  def initialize(jar_path = nil)
15
- message = jar_path ?
16
- "PlantUML JAR file not found at: #{jar_path}" :
17
- "PlantUML JAR file not found"
15
+ message = if jar_path
16
+ "PlantUML JAR file not found at: #{jar_path}"
17
+ else
18
+ "PlantUML JAR file not found"
19
+ end
18
20
  super(message)
19
21
  end
20
22
  end
21
23
 
22
24
  class JavaNotFoundError < PlantumlError
23
25
  def initialize
24
- super("Java runtime not found. Please ensure Java is installed and available in PATH")
26
+ super(
27
+ "Java runtime not found. Please ensure Java is installed and " \
28
+ "available in PATH",
29
+ )
25
30
  end
26
31
  end
27
32
 
@@ -34,7 +39,10 @@ module Metanorma
34
39
 
35
40
  class InvalidFormatError < PlantumlError
36
41
  def initialize(format, available_formats)
37
- super("Invalid format '#{format}'. Available formats: #{available_formats.join(', ')}")
42
+ super(
43
+ "Invalid format '#{format}'. Available formats: " \
44
+ "#{available_formats.join(', ')}",
45
+ )
38
46
  end
39
47
  end
40
48
  end
@@ -13,14 +13,6 @@ module Metanorma
13
13
  document.attributes["docdir"] ||
14
14
  File.dirname(document.attributes["docfile"] || ".")
15
15
  end
16
-
17
- def relative_file_path(document, file_path)
18
- return file_path if Pathname.new(file_path).absolute?
19
-
20
- docdir = document.attributes["docdir"] || Dir.pwd
21
- File.expand_path(file_path, docdir)
22
- end
23
-
24
16
  end
25
17
  end
26
18
  end
@@ -1,8 +1,8 @@
1
1
  module Metanorma
2
2
  module Plugin
3
3
  module Plantuml
4
- VERSION = "1.0.0"
5
- PLANTUML_JAR_VERSION = "1.2025.4"
4
+ VERSION = "1.0.1".freeze
5
+ PLANTUML_JAR_VERSION = "1.2025.4".freeze
6
6
  end
7
7
  end
8
8
  end
@@ -12,11 +12,14 @@ module Metanorma
12
12
  module Plugin
13
13
  module Plantuml
14
14
  class Wrapper
15
- PLANTUML_JAR_NAME = "plantuml.jar"
16
- PLANTUML_JAR_PATH = File.join(File.dirname(__FILE__), "..", "..", "..", "..", "data", PLANTUML_JAR_NAME)
15
+ PLANTUML_JAR_NAME = "plantuml.jar".freeze
16
+ PLANTUML_JAR_PATH = File.join(
17
+ File.dirname(__FILE__), "..", "..", "..",
18
+ "..", "data", PLANTUML_JAR_NAME
19
+ )
17
20
 
18
21
  SUPPORTED_FORMATS = %w[png svg pdf txt eps].freeze
19
- DEFAULT_FORMAT = "png"
22
+ DEFAULT_FORMAT = "png".freeze
20
23
 
21
24
  class << self
22
25
  def jvm_options
@@ -29,7 +32,12 @@ module Metanorma
29
32
  options
30
33
  end
31
34
 
32
- def generate(content, format: DEFAULT_FORMAT, output_file: nil, base64: false, **options)
35
+ def generate( # rubocop:disable Metrics/MethodLength
36
+ content,
37
+ format: DEFAULT_FORMAT,
38
+ output_file: nil,
39
+ base64: false, **options
40
+ )
33
41
  validate_format!(format)
34
42
  ensure_jar_available!
35
43
  ensure_java_available!
@@ -47,27 +55,30 @@ module Metanorma
47
55
  { success: false, error: e }
48
56
  end
49
57
 
50
- def generate_from_file(input_file, format: DEFAULT_FORMAT, output_file: nil, base64: false, **options)
58
+ def generate_from_file(
59
+ input_file, format: DEFAULT_FORMAT,
60
+ output_file: nil, base64: false, **options
61
+ )
51
62
  unless File.exist?(input_file)
52
63
  raise GenerationError.new("Input file not found: #{input_file}")
53
64
  end
54
65
 
55
66
  content = File.read(input_file)
56
- generate(content, format: format, output_file: output_file, base64: base64, **options)
67
+ generate(content, format: format, output_file: output_file,
68
+ base64: base64, **options)
57
69
  end
58
70
 
59
71
  def version
60
72
  return nil unless available?
61
73
 
62
- cmd = [configuration.java_path, *jvm_options, "-jar", PLANTUML_JAR_PATH, "-version"]
63
- output, error, status = Open3.capture3(*cmd)
74
+ cmd = [configuration.java_path, *jvm_options, "-jar",
75
+ PLANTUML_JAR_PATH, "-version"]
76
+ output, _, status = Open3.capture3(*cmd)
64
77
 
65
78
  if status.success?
66
79
  # Extract version from output
67
80
  version_match = output.match(/PlantUML version ([\d.]+)/)
68
81
  version_match ? version_match[1] : PLANTUML_JAR_VERSION
69
- else
70
- nil
71
82
  end
72
83
  rescue StandardError
73
84
  nil
@@ -75,6 +86,7 @@ module Metanorma
75
86
 
76
87
  def available?
77
88
  return false if ENV["PLANTUML_DISABLED"] == "true"
89
+
78
90
  File.exist?(PLANTUML_JAR_PATH) && java_available?
79
91
  end
80
92
 
@@ -122,23 +134,28 @@ module Metanorma
122
134
  execute_plantuml(content, format, output_file, options)
123
135
 
124
136
  unless File.exist?(output_file)
125
- raise GenerationError.new("Output file was not created: #{output_file}")
137
+ raise GenerationError.new(
138
+ "Output file was not created: #{output_file}",
139
+ )
126
140
  end
127
141
 
128
142
  { output_path: File.expand_path(output_file) }
129
143
  end
130
144
 
131
- def generate_to_base64(content, format, options)
132
- Tempfile.create(['plantuml_output', ".#{format}"]) do |temp_file|
145
+ def generate_to_base64(content, format, options) # rubocop:disable Metrics/MethodLength
146
+ Tempfile.create(["plantuml_output", ".#{format}"]) do |temp_file|
133
147
  temp_file.close
134
148
 
135
149
  execute_plantuml(content, format, temp_file.path, options)
136
150
 
137
151
  unless File.exist?(temp_file.path)
138
- raise GenerationError.new("Temporary output file was not created")
152
+ raise GenerationError.new(
153
+ "Temporary output file was not created",
154
+ )
139
155
  end
140
156
 
141
- encoded_content = Base64.strict_encode64(File.read(temp_file.path))
157
+ encoded_content = Base64
158
+ .strict_encode64(File.read(temp_file.path))
142
159
  { base64: encoded_content }
143
160
  end
144
161
  end
@@ -151,26 +168,32 @@ module Metanorma
151
168
  generate_to_file(content, format, output_file, options)
152
169
  end
153
170
 
154
- def execute_plantuml(content, format, output_file, options)
155
- Tempfile.create(['plantuml_input', '.puml']) do |input_file|
171
+ def execute_plantuml(content, format, output_file, options) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize, Metrics/MethodLength
172
+ Tempfile.create(["plantuml_input", ".puml"]) do |input_file| # rubocop:disable Metrics/BlockLength
156
173
  input_file.write(content)
157
174
  input_file.close
158
175
 
159
- # PlantUML generates output files based on filename specified in @start... line
176
+ # PlantUML generates output files based on filename specified in
177
+ # @start... line
160
178
  # We need to use a temp directory and then move the file
161
- Dir.mktmpdir do |temp_dir|
179
+ Dir.mktmpdir do |temp_dir| # rubocop:disable Metrics/BlockLength
162
180
  cmd = build_command(input_file.path, format, temp_dir, options)
163
181
 
164
182
  output, error, status = Open3.capture3(*cmd)
165
183
 
166
184
  unless status.success?
167
- error_message = error.empty? ? "Unknown PlantUML error" : error.strip
185
+ error_message = if error.empty?
186
+ "Unknown PlantUML error"
187
+ else
188
+ error.strip
189
+ end
168
190
  raise GenerationError.new(error_message, error)
169
191
  end
170
192
 
171
193
  # Find the generated file and move it to the desired location
172
194
  if output_file
173
- generated_file = find_generated_file(temp_dir, content, format)
195
+ generated_file = find_generated_file(temp_dir, content,
196
+ format)
174
197
  if generated_file && File.exist?(generated_file)
175
198
  FileUtils.mv(generated_file, output_file)
176
199
  else
@@ -178,7 +201,9 @@ module Metanorma
178
201
  generated_files = Dir.glob(File.join(temp_dir, "*"))
179
202
  error_msg = "Generated file not found in temp directory. "
180
203
  error_msg += "Expected: #{generated_file}. "
181
- error_msg += "Found files: #{generated_files.map { |f| File.basename(f) }.join(', ')}"
204
+ error_msg += "Found files: #{generated_files.map do |f|
205
+ File.basename(f)
206
+ end.join(', ')}"
182
207
  raise GenerationError.new(error_msg)
183
208
  end
184
209
  end
@@ -188,8 +213,9 @@ module Metanorma
188
213
  end
189
214
  end
190
215
 
191
- def find_generated_file(temp_dir, content, format)
192
- # PlantUML generates files based on the filename specified in @start... line
216
+ def find_generated_file(temp_dir, content, format) # rubocop:disable Metrics/MethodLength
217
+ # PlantUML generates files based on the filename specified in
218
+ # @start... line
193
219
  extension = format.to_s.downcase
194
220
 
195
221
  # First, try to extract filename from PlantUML content
@@ -197,17 +223,19 @@ module Metanorma
197
223
 
198
224
  if plantuml_filename
199
225
  # Look for file with PlantUML-specified name
200
- generated_file = File.join(temp_dir, "#{plantuml_filename}.#{extension}")
226
+ generated_file = File.join(temp_dir,
227
+ "#{plantuml_filename}.#{extension}")
201
228
  return generated_file if File.exist?(generated_file)
202
229
  end
203
230
 
204
- # Fallback: scan directory for any generated files with correct extension
231
+ # Fallback: scan directory for any generated files with
232
+ # the correct extension
205
233
  Dir.glob(File.join(temp_dir, "*.#{extension}")).first
206
234
  end
207
235
 
208
236
  def extract_plantuml_filename_from_content(content)
209
237
  # Extract the raw filename from @start... line (don't sanitize)
210
- match = content.match(/^@start\w+\s+(.+)$/mi)
238
+ match = content.match(/^@start\w+\s+(.{1,999})$/mi)
211
239
  return nil unless match
212
240
 
213
241
  filename = match[1].strip.split("\n").first&.strip
@@ -217,7 +245,7 @@ module Metanorma
217
245
  filename.gsub(/^["']|["']$/, "")
218
246
  end
219
247
 
220
- def build_command(input_file, format, output_dir, options)
248
+ def build_command(input_file, format, output_dir, _options) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
221
249
  cmd = [
222
250
  configuration.java_path,
223
251
  *jvm_options,
@@ -225,17 +253,9 @@ module Metanorma
225
253
  ]
226
254
 
227
255
  # Add format-specific options
228
- case format.to_s.downcase
229
- when "png"
230
- cmd << "-tpng"
231
- when "svg"
232
- cmd << "-tsvg"
233
- when "pdf"
234
- cmd << "-tpdf"
235
- when "txt"
236
- cmd << "-ttxt"
237
- when "eps"
238
- cmd << "-teps"
256
+ format_str = format.to_s.downcase
257
+ if SUPPORTED_FORMATS.include?(format_str)
258
+ cmd << "-t#{format_str}"
239
259
  end
240
260
 
241
261
  # Add output directory option
@@ -1,4 +1,3 @@
1
-
2
1
  module Metanorma
3
2
  module Plugin
4
3
  module Plantuml
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.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-08-26 00:00:00.000000000 Z
11
+ date: 2025-08-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: asciidoctor