Class: Uniword::CLI

Inherits:
Thor
  • Object
show all
Includes:
CLIHelpers
Defined in:
lib/uniword/cli/main.rb

Overview

Command-line interface for Uniword.

Provides commands for document conversion, inspection, validation, building, checking, batch processing, metadata, and verification.

Subcommands:

  • theme → ThemeCLI
  • styleset → StyleSetCLI
  • resources → ResourcesCLI

Constant Summary collapse

BASELINE_FIX_CODES =

Fix codes that are routine normalization, applied on every save regardless of document health (package structure, properties, default styles) — not evidence of a broken document.

%w[R1 R6 R7 R8 R14 R24].freeze

Instance Method Summary collapse

Methods included from CLIHelpers

included

Instance Method Details

#batch(pattern, output_dir) ⇒ Object



249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
# File 'lib/uniword/cli/main.rb', line 249

def batch(pattern, output_dir)
  require "fileutils"
  FileUtils.mkdir_p(output_dir)

  files = Dir.glob(pattern)
  if files.empty?
    say("No files found matching '#{pattern}'", :yellow)
    return
  end

  say("Processing #{files.count} files...", :green) if options[:verbose]

  success = 0
  errors = []

  files.each do |input_path|
    basename = File.basename(input_path, ".*")
    ext = options[:format] == "mhtml" ? ".mhtml" : ".docx"
    output_path = File.join(output_dir, "#{basename}#{ext}")

    begin
      doc = DocumentFactory.from_file(input_path)
      doc.save(output_path, format: options[:format].to_sym)
      say("  #{File.basename(input_path)} -> #{File.basename(output_path)}") if options[:verbose]
      success += 1
    rescue StandardError => e
      errors << "#{File.basename(input_path)}: #{e.message}"
      if options[:verbose]
        say("  Error: #{File.basename(input_path)}: #{e.message}",
            :red)
      end
    end
  end

  say("\nBatch complete: #{success} converted, #{errors.count} errors",
      :green)
  return if errors.empty?

  say("Errors:", :red)
  errors.each { |e| say("  - #{e}", :red) }
rescue StandardError => e
  say "Error: #{e.message}", :red
  exit 1
end

#build(template_path, output_path) ⇒ Object



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/uniword/cli/main.rb', line 166

def build(template_path, output_path)
  unless File.exist?(template_path)
    say("Template not found: #{template_path}", :red)
    exit 1
  end

  template = Uniword::Template::Template.load(template_path)

  if options[:verbose]
    say("Loaded template: #{template_path}", :cyan)
    say("  Markers found: #{template.markers.count}")
  end

  data = load_build_data
  document = template.render(data)
  document.save(output_path)

  say("Built document: #{output_path}", :green)
rescue Uniword::Error => e
  handle_error(e)
rescue StandardError => e
  handle_error(e, verbose: options[:verbose])
end

#check(path) ⇒ Object



204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/uniword/cli/main.rb', line 204

def check(path)
  doc = load_document(path)
  check_type = options[:type]

  reports = {}

  if %w[all quality].include?(check_type)
    checker = Quality::DocumentChecker.new
    reports[:quality] = checker.check(doc)
  end

  if %w[all accessibility].include?(check_type)
    checker = Accessibility::AccessibilityChecker.new
    reports[:accessibility] = checker.check(doc)
  end

  if options[:json]
    require "json"
    output = reports.transform_values do |r|
      { valid: r.valid?,
        issues: r.is_a?(Uniword::Quality::CheckReport) ? r.issues.count : 0 }
    end
    puts JSON.pretty_generate(output)
  else
    display_check_reports(reports)
  end
rescue Uniword::Error => e
  handle_error(e)
rescue StandardError => e
  handle_error(e)
end

#convert(input_path, output_path) ⇒ Object



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/uniword/cli/main.rb', line 35

def convert(input_path, output_path)
  from_format = options[:from]&.to_sym || :auto
  to_format = options[:to]&.to_sym || :auto

  if options[:verbose]
    say "Converting #{input_path} to #{output_path}...",
        :green
  end

  doc = DocumentFactory.from_file(input_path, format: from_format)

  if options[:verbose]
    say "  Loaded document:", :cyan
    say "    Paragraphs: #{doc.paragraphs.count}"
    say "    Tables: #{doc.tables.count}"
    say "    Styles: #{doc.styles_configuration&.styles&.count || 0}"
  end

  doc.save(output_path, format: to_format)
  say "Conversion complete!", :green if options[:verbose]
rescue Uniword::Error => e
  handle_error(e)
rescue StandardError => e
  handle_error(e, verbose: options[:verbose])
end

#info(path) ⇒ Object



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/uniword/cli/main.rb', line 71

def info(path)
  say "Analyzing #{path}...", :green

  detector = ::Uniword::FormatDetector.new
  format = detector.detect(path)
  say "\nFormat: #{format.to_s.upcase}", :cyan

  doc = load_document(path)

  say "\nDocument Statistics:", :cyan
  say "  Paragraphs: #{doc.paragraphs.count}"
  say "  Tables: #{doc.tables.count}"
  say "  Text length: #{doc.text.length} characters"
  say "  Styles: #{doc.styles_configuration.styles.count}" if doc.styles_configuration&.styles&.any?

  display_verbose_info(doc) if options[:verbose]
  say "\nAnalysis complete!", :green
rescue Uniword::Error => e
  handle_error(e)
rescue StandardError => e
  handle_error(e)
end

#metadata(path) ⇒ Object



311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
# File 'lib/uniword/cli/main.rb', line 311

def (path)
  doc = load_document(path)
  cp = doc.core_properties

  updated = (cp)

  if updated
    output_path = options[:output]
    unless output_path
      say("Error: --output is required when setting metadata", :red)
      exit 1
    end
    doc.save(output_path)
    say("Metadata updated and saved to #{output_path}", :green)
    return
  end

  (cp)
rescue Uniword::Error => e
  handle_error(e)
rescue StandardError => e
  handle_error(e)
end

#repair(input_path, output_path) ⇒ Object



108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/uniword/cli/main.rb', line 108

def repair(input_path, output_path)
  say "Loading #{input_path}...", :green

  fixes = repair_file(input_path, output_path)
  report_repairs(fixes)
  say "Saved to #{output_path}", :green
rescue Uniword::ValidationError => e
  say "Repair failed — the document has issues the Reconciler " \
      "cannot fix:", :red
  e.issues.each { |issue| say "  #{issue.code}: #{issue.message}", :red }
  exit 1
rescue Uniword::Error, StandardError => e
  handle_error(e)
end

#validate(path) ⇒ Object



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/uniword/cli/main.rb', line 133

def validate(path)
  say "Validating #{path}...", :green

  doc = load_document(path)
  say "File format is valid", :green

  issues = Uniword::Validation::Engine.run(
    Uniword::Validation::Rules::ModelContext.new(doc),
  )
  report_validation_issues(issues)
  report_content_presence(doc)

  display_detailed_validation(doc) if options[:verbose]
  say "\nValidation complete!", :green
  exit 1 if issues.any?(&:error?)
rescue Uniword::Error => e
  handle_error(e)
rescue StandardError => e
  handle_error(e)
end

#verify(path) ⇒ Object



354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
# File 'lib/uniword/cli/main.rb', line 354

def verify(path)
  unless File.exist?(path)
    say(Rainbow("  File not found: #{path}").red.bright)
    exit 1
  end

  orchestrator = Uniword::Validation::VerifyOrchestrator.new(
    xsd_validation: options[:xsd],
  )
  report = orchestrator.verify(path)

  if options[:json]
    puts report.to_json
  elsif options[:yaml]
    require "yaml"
    puts YAML.dump(JSON.parse(report.to_json))
  else
    formatter = Uniword::Validation::Report::TerminalFormatter.new
    puts formatter.format(report, verbose: options[:verbose])
  end

  exit report.valid ? 0 : 1
rescue StandardError => e
  say(Rainbow("  Error: #{e.message}").red.bright)
  exit 1
end

#versionObject



382
383
384
# File 'lib/uniword/cli/main.rb', line 382

def version
  say "Uniword version #{Uniword::VERSION}", :green
end