fontisan 0.2.4 → 0.2.6
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 +168 -32
- data/README.adoc +673 -1091
- data/lib/fontisan/cli.rb +94 -13
- data/lib/fontisan/collection/dfont_builder.rb +315 -0
- data/lib/fontisan/commands/convert_command.rb +118 -7
- data/lib/fontisan/commands/pack_command.rb +129 -22
- data/lib/fontisan/commands/validate_command.rb +107 -151
- data/lib/fontisan/config/conversion_matrix.yml +175 -1
- data/lib/fontisan/constants.rb +8 -0
- data/lib/fontisan/converters/collection_converter.rb +438 -0
- data/lib/fontisan/converters/woff2_encoder.rb +7 -29
- data/lib/fontisan/dfont_collection.rb +185 -0
- data/lib/fontisan/font_loader.rb +91 -6
- data/lib/fontisan/models/validation_report.rb +227 -0
- data/lib/fontisan/parsers/dfont_parser.rb +192 -0
- data/lib/fontisan/pipeline/transformation_pipeline.rb +4 -8
- data/lib/fontisan/tables/cmap.rb +82 -2
- data/lib/fontisan/tables/glyf.rb +118 -0
- data/lib/fontisan/tables/head.rb +60 -0
- data/lib/fontisan/tables/hhea.rb +74 -0
- data/lib/fontisan/tables/maxp.rb +60 -0
- data/lib/fontisan/tables/name.rb +76 -0
- data/lib/fontisan/tables/os2.rb +113 -0
- data/lib/fontisan/tables/post.rb +57 -0
- data/lib/fontisan/true_type_font.rb +8 -46
- data/lib/fontisan/validation/collection_validator.rb +265 -0
- data/lib/fontisan/validators/basic_validator.rb +85 -0
- data/lib/fontisan/validators/font_book_validator.rb +130 -0
- data/lib/fontisan/validators/opentype_validator.rb +112 -0
- data/lib/fontisan/validators/profile_loader.rb +139 -0
- data/lib/fontisan/validators/validator.rb +484 -0
- data/lib/fontisan/validators/web_font_validator.rb +102 -0
- data/lib/fontisan/version.rb +1 -1
- data/lib/fontisan.rb +78 -6
- metadata +13 -12
- data/lib/fontisan/config/validation_rules.yml +0 -149
- data/lib/fontisan/validation/checksum_validator.rb +0 -170
- data/lib/fontisan/validation/consistency_validator.rb +0 -197
- data/lib/fontisan/validation/structure_validator.rb +0 -198
- data/lib/fontisan/validation/table_validator.rb +0 -158
- data/lib/fontisan/validation/validator.rb +0 -152
- data/lib/fontisan/validation/variable_font_validator.rb +0 -218
- data/lib/fontisan/validation/woff2_header_validator.rb +0 -278
- data/lib/fontisan/validation/woff2_table_validator.rb +0 -270
- data/lib/fontisan/validation/woff2_validator.rb +0 -248
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "base_command"
|
|
4
4
|
require_relative "../collection/builder"
|
|
5
|
+
require_relative "../collection/dfont_builder"
|
|
5
6
|
require_relative "../font_loader"
|
|
6
7
|
|
|
7
8
|
module Fontisan
|
|
@@ -11,6 +12,7 @@ module Fontisan
|
|
|
11
12
|
# This command provides CLI access to font collection creation functionality.
|
|
12
13
|
# It loads multiple font files and combines them into a single TTC (TrueType Collection)
|
|
13
14
|
# or OTC (OpenType Collection) file with shared table deduplication to save space.
|
|
15
|
+
# It also supports creating dfont (Apple Data Fork Font) suitcases.
|
|
14
16
|
#
|
|
15
17
|
# @example Pack fonts into TTC
|
|
16
18
|
# command = PackCommand.new(
|
|
@@ -30,13 +32,21 @@ module Fontisan
|
|
|
30
32
|
# analyze: true
|
|
31
33
|
# )
|
|
32
34
|
# result = command.run
|
|
35
|
+
#
|
|
36
|
+
# @example Pack into dfont
|
|
37
|
+
# command = PackCommand.new(
|
|
38
|
+
# ['font1.ttf', 'font2.otf'],
|
|
39
|
+
# output: 'family.dfont',
|
|
40
|
+
# format: :dfont
|
|
41
|
+
# )
|
|
42
|
+
# result = command.run
|
|
33
43
|
class PackCommand
|
|
34
44
|
# Initialize pack command
|
|
35
45
|
#
|
|
36
46
|
# @param font_paths [Array<String>] Paths to input font files
|
|
37
47
|
# @param options [Hash] Command options
|
|
38
48
|
# @option options [String] :output Output file path (required)
|
|
39
|
-
# @option options [Symbol, String] :format Format type (:ttc or :
|
|
49
|
+
# @option options [Symbol, String] :format Format type (:ttc, :otc, or :dfont, default: auto-detect)
|
|
40
50
|
# @option options [Boolean] :optimize Enable table sharing optimization (default: true)
|
|
41
51
|
# @option options [Boolean] :analyze Show analysis report before building (default: false)
|
|
42
52
|
# @option options [Boolean] :verbose Enable verbose output (default: false)
|
|
@@ -45,7 +55,7 @@ module Fontisan
|
|
|
45
55
|
@font_paths = font_paths
|
|
46
56
|
@options = options
|
|
47
57
|
@output_path = options[:output]
|
|
48
|
-
@format = parse_format(options[:format]
|
|
58
|
+
@format = options[:format] ? parse_format(options[:format]) : nil
|
|
49
59
|
@optimize = options.fetch(:optimize, true)
|
|
50
60
|
@analyze = options.fetch(:analyze, false)
|
|
51
61
|
@verbose = options.fetch(:verbose, false)
|
|
@@ -55,16 +65,16 @@ module Fontisan
|
|
|
55
65
|
|
|
56
66
|
# Execute the pack command
|
|
57
67
|
#
|
|
58
|
-
# Loads all fonts, analyzes tables, and creates a TTC/OTC collection.
|
|
68
|
+
# Loads all fonts, analyzes tables, and creates a TTC/OTC/dfont collection.
|
|
59
69
|
# Optionally displays analysis before building.
|
|
60
70
|
#
|
|
61
71
|
# @return [Hash] Result information with:
|
|
62
72
|
# - :output [String] - Output file path
|
|
63
73
|
# - :output_size [Integer] - Output file size in bytes
|
|
64
74
|
# - :num_fonts [Integer] - Number of fonts packed
|
|
65
|
-
# - :format [Symbol] - Collection format (:ttc or :
|
|
66
|
-
# - :space_savings [Integer] - Bytes saved through sharing
|
|
67
|
-
# - :sharing_percentage [Float] - Percentage of tables shared
|
|
75
|
+
# - :format [Symbol] - Collection format (:ttc, :otc, or :dfont)
|
|
76
|
+
# - :space_savings [Integer] - Bytes saved through sharing (TTC/OTC only)
|
|
77
|
+
# - :sharing_percentage [Float] - Percentage of tables shared (TTC/OTC only)
|
|
68
78
|
# - :analysis [Hash] - Analysis report (if analyze option enabled)
|
|
69
79
|
# @raise [ArgumentError] if options are invalid
|
|
70
80
|
# @raise [Fontisan::Error] if packing fails
|
|
@@ -74,6 +84,46 @@ module Fontisan
|
|
|
74
84
|
# Load all fonts
|
|
75
85
|
fonts = load_fonts
|
|
76
86
|
|
|
87
|
+
# Auto-detect format if not specified
|
|
88
|
+
@format ||= auto_detect_format(fonts)
|
|
89
|
+
puts "Auto-detected format: #{@format}" if @verbose && !@options[:format]
|
|
90
|
+
|
|
91
|
+
# Build collection based on format
|
|
92
|
+
if @format == :dfont
|
|
93
|
+
build_dfont(fonts)
|
|
94
|
+
else
|
|
95
|
+
build_ttc_otc(fonts)
|
|
96
|
+
end
|
|
97
|
+
rescue Fontisan::Error => e
|
|
98
|
+
raise Fontisan::Error, "Collection packing failed: #{e.message}"
|
|
99
|
+
rescue StandardError => e
|
|
100
|
+
raise Fontisan::Error, "Unexpected error during packing: #{e.message}"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
private
|
|
104
|
+
|
|
105
|
+
# Build dfont collection
|
|
106
|
+
#
|
|
107
|
+
# @param fonts [Array] Loaded fonts
|
|
108
|
+
# @return [Hash] Build result
|
|
109
|
+
def build_dfont(fonts)
|
|
110
|
+
puts "Building dfont suitcase..." if @verbose
|
|
111
|
+
|
|
112
|
+
builder = Collection::DfontBuilder.new(fonts)
|
|
113
|
+
result = builder.build_to_file(@output_path)
|
|
114
|
+
|
|
115
|
+
if @verbose
|
|
116
|
+
display_dfont_results(result)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
result
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Build TTC/OTC collection
|
|
123
|
+
#
|
|
124
|
+
# @param fonts [Array] Loaded fonts
|
|
125
|
+
# @return [Hash] Build result
|
|
126
|
+
def build_ttc_otc(fonts)
|
|
77
127
|
# Create builder
|
|
78
128
|
builder = Collection::Builder.new(fonts, {
|
|
79
129
|
format: @format,
|
|
@@ -98,13 +148,53 @@ module Fontisan
|
|
|
98
148
|
end
|
|
99
149
|
|
|
100
150
|
result
|
|
101
|
-
rescue Fontisan::Error => e
|
|
102
|
-
raise Fontisan::Error, "Collection packing failed: #{e.message}"
|
|
103
|
-
rescue StandardError => e
|
|
104
|
-
raise Fontisan::Error, "Unexpected error during packing: #{e.message}"
|
|
105
151
|
end
|
|
106
152
|
|
|
107
|
-
|
|
153
|
+
# Auto-detect collection format based on fonts
|
|
154
|
+
#
|
|
155
|
+
# @param fonts [Array<TrueTypeFont, OpenTypeFont>] Loaded fonts
|
|
156
|
+
# @return [Symbol] Detected format (:ttc, :otc, or :dfont)
|
|
157
|
+
def auto_detect_format(fonts)
|
|
158
|
+
# Check output extension first
|
|
159
|
+
ext = File.extname(@output_path).downcase
|
|
160
|
+
return :ttc if ext == ".ttc"
|
|
161
|
+
return :otc if ext == ".otc"
|
|
162
|
+
return :dfont if ext == ".dfont"
|
|
163
|
+
|
|
164
|
+
# Detect based on font types
|
|
165
|
+
has_truetype = fonts.any? { |f| truetype_font?(f) }
|
|
166
|
+
has_opentype = fonts.any? { |f| opentype_font?(f) }
|
|
167
|
+
|
|
168
|
+
if has_truetype && !has_opentype
|
|
169
|
+
:ttc # All TrueType
|
|
170
|
+
elsif has_opentype
|
|
171
|
+
:otc # Has OpenType/CFF fonts
|
|
172
|
+
else
|
|
173
|
+
:ttc # Default to TTC
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Check if font is TrueType
|
|
178
|
+
#
|
|
179
|
+
# @param font [Object] Font object
|
|
180
|
+
# @return [Boolean]
|
|
181
|
+
def truetype_font?(font)
|
|
182
|
+
return false unless font.respond_to?(:header)
|
|
183
|
+
|
|
184
|
+
sfnt = font.header.sfnt_version
|
|
185
|
+
[0x00010000, 0x74727565].include?(sfnt) # 0x74727565 = 'true'
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Check if font is OpenType/CFF
|
|
189
|
+
#
|
|
190
|
+
# @param font [Object] Font object
|
|
191
|
+
# @return [Boolean]
|
|
192
|
+
def opentype_font?(font)
|
|
193
|
+
return false unless font.respond_to?(:header)
|
|
194
|
+
|
|
195
|
+
sfnt = font.header.sfnt_version
|
|
196
|
+
sfnt == 0x4F54544F # 'OTTO'
|
|
197
|
+
end
|
|
108
198
|
|
|
109
199
|
# Validate command options
|
|
110
200
|
#
|
|
@@ -125,17 +215,19 @@ module Fontisan
|
|
|
125
215
|
"Collection requires at least 2 fonts, got #{@font_paths.size}"
|
|
126
216
|
end
|
|
127
217
|
|
|
128
|
-
# Validate format
|
|
129
|
-
|
|
218
|
+
# Validate format if specified
|
|
219
|
+
if @format && !%i[ttc otc dfont].include?(@format)
|
|
130
220
|
raise ArgumentError,
|
|
131
|
-
"Invalid format: #{@format}. Must be :ttc or :
|
|
221
|
+
"Invalid format: #{@format}. Must be :ttc, :otc, or :dfont"
|
|
132
222
|
end
|
|
133
223
|
|
|
134
|
-
#
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
224
|
+
# Warn if output extension doesn't match format (if format specified)
|
|
225
|
+
if @format
|
|
226
|
+
ext = File.extname(@output_path).downcase
|
|
227
|
+
expected_ext = ".#{@format}"
|
|
228
|
+
if ext != expected_ext
|
|
229
|
+
warn "Warning: Output extension '#{ext}' doesn't match format '#{@format}' (expected '#{expected_ext}')"
|
|
230
|
+
end
|
|
139
231
|
end
|
|
140
232
|
end
|
|
141
233
|
|
|
@@ -165,19 +257,21 @@ module Fontisan
|
|
|
165
257
|
# Parse format option
|
|
166
258
|
#
|
|
167
259
|
# @param format [Symbol, String] Format option
|
|
168
|
-
# @return [Symbol] Parsed format (:ttc or :
|
|
260
|
+
# @return [Symbol] Parsed format (:ttc, :otc, or :dfont)
|
|
169
261
|
# @raise [ArgumentError] if format is invalid
|
|
170
262
|
def parse_format(format)
|
|
171
|
-
return format if format.is_a?(Symbol) && %i[ttc otc].include?(format)
|
|
263
|
+
return format if format.is_a?(Symbol) && %i[ttc otc dfont].include?(format)
|
|
172
264
|
|
|
173
265
|
case format.to_s.downcase
|
|
174
266
|
when "ttc"
|
|
175
267
|
:ttc
|
|
176
268
|
when "otc"
|
|
177
269
|
:otc
|
|
270
|
+
when "dfont"
|
|
271
|
+
:dfont
|
|
178
272
|
else
|
|
179
273
|
raise ArgumentError,
|
|
180
|
-
"Invalid format: #{format}. Must be 'ttc' or '
|
|
274
|
+
"Invalid format: #{format}. Must be 'ttc', 'otc', or 'dfont'"
|
|
181
275
|
end
|
|
182
276
|
end
|
|
183
277
|
|
|
@@ -223,6 +317,19 @@ module Fontisan
|
|
|
223
317
|
puts ""
|
|
224
318
|
end
|
|
225
319
|
|
|
320
|
+
# Display dfont build results
|
|
321
|
+
#
|
|
322
|
+
# @param result [Hash] Build result
|
|
323
|
+
# @return [void]
|
|
324
|
+
def display_dfont_results(result)
|
|
325
|
+
puts "\n=== dfont Suitcase Created ==="
|
|
326
|
+
puts "Output: #{result[:output_path]}"
|
|
327
|
+
puts "Format: #{result[:format].upcase}"
|
|
328
|
+
puts "Fonts: #{result[:num_fonts]}"
|
|
329
|
+
puts "Size: #{format_bytes(result[:total_size])}"
|
|
330
|
+
puts ""
|
|
331
|
+
end
|
|
332
|
+
|
|
226
333
|
# Format bytes for display
|
|
227
334
|
#
|
|
228
335
|
# @param bytes [Integer] Byte count
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "base_command"
|
|
4
|
-
require_relative "../
|
|
5
|
-
require_relative "../validation/variable_font_validator"
|
|
4
|
+
require_relative "../validators/profile_loader"
|
|
6
5
|
require_relative "../font_loader"
|
|
7
6
|
|
|
8
7
|
module Fontisan
|
|
@@ -11,195 +10,152 @@ module Fontisan
|
|
|
11
10
|
#
|
|
12
11
|
# This command validates fonts against quality checks, structural integrity,
|
|
13
12
|
# and OpenType specification compliance. It supports different validation
|
|
14
|
-
#
|
|
13
|
+
# profiles and output formats, with ftxvalidator-compatible options.
|
|
15
14
|
#
|
|
16
|
-
# @example Validating a font
|
|
15
|
+
# @example Validating a font with default profile
|
|
16
|
+
# command = ValidateCommand.new(input: "font.ttf")
|
|
17
|
+
# exit_code = command.run
|
|
18
|
+
#
|
|
19
|
+
# @example Validating with specific profile
|
|
17
20
|
# command = ValidateCommand.new(
|
|
18
21
|
# input: "font.ttf",
|
|
19
|
-
#
|
|
20
|
-
# format: :
|
|
22
|
+
# profile: :web,
|
|
23
|
+
# format: :json
|
|
21
24
|
# )
|
|
22
25
|
# exit_code = command.run
|
|
23
26
|
class ValidateCommand < BaseCommand
|
|
24
27
|
# Initialize validate command
|
|
25
28
|
#
|
|
26
29
|
# @param input [String] Path to font file
|
|
27
|
-
# @param
|
|
30
|
+
# @param profile [Symbol, String, nil] Validation profile (default: :default)
|
|
31
|
+
# @param exclude [Array<String>] Tests to exclude
|
|
32
|
+
# @param output [String, nil] Output file path
|
|
28
33
|
# @param format [Symbol] Output format (:text, :yaml, :json)
|
|
29
|
-
# @param
|
|
30
|
-
# @param
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
+
# @param full_report [Boolean] Generate full detailed report
|
|
35
|
+
# @param summary_report [Boolean] Generate brief summary report
|
|
36
|
+
# @param table_report [Boolean] Generate tabular format report
|
|
37
|
+
# @param verbose [Boolean] Show verbose output
|
|
38
|
+
# @param suppress_warnings [Boolean] Suppress warning output
|
|
39
|
+
# @param return_value_results [Boolean] Use return values to indicate results
|
|
40
|
+
def initialize(
|
|
41
|
+
input:,
|
|
42
|
+
profile: nil,
|
|
43
|
+
exclude: [],
|
|
44
|
+
output: nil,
|
|
45
|
+
format: :text,
|
|
46
|
+
full_report: false,
|
|
47
|
+
summary_report: false,
|
|
48
|
+
table_report: false,
|
|
49
|
+
verbose: false,
|
|
50
|
+
suppress_warnings: false,
|
|
51
|
+
return_value_results: false
|
|
52
|
+
)
|
|
34
53
|
@input = input
|
|
35
|
-
@
|
|
36
|
-
@
|
|
54
|
+
@profile = profile || :default
|
|
55
|
+
@exclude = exclude
|
|
56
|
+
@output = output
|
|
57
|
+
@format = format
|
|
58
|
+
@full_report = full_report
|
|
59
|
+
@summary_report = summary_report
|
|
60
|
+
@table_report = table_report
|
|
37
61
|
@verbose = verbose
|
|
38
|
-
@
|
|
62
|
+
@suppress_warnings = suppress_warnings
|
|
63
|
+
@return_value_results = return_value_results
|
|
39
64
|
end
|
|
40
65
|
|
|
41
66
|
# Run the validation command
|
|
42
67
|
#
|
|
43
|
-
# @return [Integer] Exit code (0 = valid,
|
|
68
|
+
# @return [Integer] Exit code (0 = valid, 2 = fatal, 3 = errors, 4 = warnings, 5 = info)
|
|
44
69
|
def run
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
# Create validator
|
|
52
|
-
validator = Validation::Validator.new(level: @level)
|
|
53
|
-
|
|
54
|
-
# Run validation
|
|
55
|
-
report = validator.validate(font, @input)
|
|
70
|
+
# Load font with appropriate mode
|
|
71
|
+
profile_config = Validators::ProfileLoader.profile_info(@profile)
|
|
72
|
+
unless profile_config
|
|
73
|
+
puts "Error: Unknown profile '#{@profile}'" unless @suppress_warnings
|
|
74
|
+
return 1
|
|
75
|
+
end
|
|
56
76
|
|
|
57
|
-
|
|
58
|
-
validate_variable_font(font, report) if font.has_table?("fvar")
|
|
77
|
+
mode = profile_config[:loading_mode].to_sym
|
|
59
78
|
|
|
60
|
-
|
|
61
|
-
output_report(report) unless @quiet
|
|
79
|
+
font = FontLoader.load(@input, mode: mode)
|
|
62
80
|
|
|
63
|
-
#
|
|
64
|
-
|
|
65
|
-
rescue StandardError => e
|
|
66
|
-
puts "Error: #{e.message}" unless @quiet
|
|
67
|
-
puts e.backtrace.join("\n") if @verbose && !@quiet
|
|
68
|
-
1
|
|
69
|
-
end
|
|
81
|
+
# Select validator
|
|
82
|
+
validator = Validators::ProfileLoader.load(@profile)
|
|
70
83
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
# Validate variable font structure
|
|
74
|
-
#
|
|
75
|
-
# @param font [TrueTypeFont, OpenTypeFont] The font to validate
|
|
76
|
-
# @param report [Models::ValidationReport] The validation report to update
|
|
77
|
-
# @return [void]
|
|
78
|
-
def validate_variable_font(font, report)
|
|
79
|
-
var_validator = Validation::VariableFontValidator.new(font)
|
|
80
|
-
errors = var_validator.validate
|
|
84
|
+
# Run validation
|
|
85
|
+
report = validator.validate(font)
|
|
81
86
|
|
|
82
|
-
if
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
puts " ERROR: #{error}" if @verbose && !@quiet
|
|
86
|
-
# Add to report if report supports adding errors
|
|
87
|
-
if report.respond_to?(:errors)
|
|
88
|
-
report.errors << { message: error,
|
|
89
|
-
category: "variable_font" }
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
|
-
elsif @verbose && !@quiet
|
|
93
|
-
puts "\n✓ Variable font structure valid"
|
|
87
|
+
# Filter excluded checks if specified
|
|
88
|
+
if @exclude.any?
|
|
89
|
+
report.check_results.reject! { |cr| @exclude.include?(cr.check_id) }
|
|
94
90
|
end
|
|
95
|
-
end
|
|
96
91
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
# @raise [ArgumentError] if parameters are invalid
|
|
100
|
-
# @return [void]
|
|
101
|
-
def validate_params!
|
|
102
|
-
if @input.nil? || @input.empty?
|
|
103
|
-
raise ArgumentError,
|
|
104
|
-
"Input file is required"
|
|
105
|
-
end
|
|
106
|
-
unless File.exist?(@input)
|
|
107
|
-
raise ArgumentError,
|
|
108
|
-
"Input file does not exist: #{@input}"
|
|
109
|
-
end
|
|
92
|
+
# Generate output
|
|
93
|
+
output = generate_output(report)
|
|
110
94
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
valid_formats = %i[text yaml json]
|
|
118
|
-
unless valid_formats.include?(@format)
|
|
119
|
-
raise ArgumentError,
|
|
120
|
-
"Invalid format: #{@format}. Must be one of: #{valid_formats.join(', ')}"
|
|
95
|
+
# Write to file or stdout
|
|
96
|
+
if @output
|
|
97
|
+
File.write(@output, output)
|
|
98
|
+
puts "Validation report written to #{@output}" if @verbose && !@suppress_warnings
|
|
99
|
+
else
|
|
100
|
+
puts output unless @suppress_warnings
|
|
121
101
|
end
|
|
122
|
-
end
|
|
123
102
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
puts "Error loading font: #{e.message}" unless @quiet
|
|
131
|
-
nil
|
|
103
|
+
# Return exit code
|
|
104
|
+
exit_code(report)
|
|
105
|
+
rescue => e
|
|
106
|
+
puts "Error: #{e.message}" unless @suppress_warnings
|
|
107
|
+
puts e.backtrace.join("\n") if @verbose && !@suppress_warnings
|
|
108
|
+
1
|
|
132
109
|
end
|
|
133
110
|
|
|
134
|
-
|
|
135
|
-
#
|
|
136
|
-
# @param report [Models::ValidationReport] The validation report
|
|
137
|
-
# @return [void]
|
|
138
|
-
def output_report(report)
|
|
139
|
-
case @format
|
|
140
|
-
when :text
|
|
141
|
-
output_text(report)
|
|
142
|
-
when :yaml
|
|
143
|
-
output_yaml(report)
|
|
144
|
-
when :json
|
|
145
|
-
output_json(report)
|
|
146
|
-
end
|
|
147
|
-
end
|
|
111
|
+
private
|
|
148
112
|
|
|
149
|
-
#
|
|
113
|
+
# Generate output based on requested format
|
|
150
114
|
#
|
|
151
|
-
# @param report [
|
|
152
|
-
# @return [
|
|
153
|
-
def
|
|
154
|
-
if @
|
|
155
|
-
|
|
115
|
+
# @param report [ValidationReport] The validation report
|
|
116
|
+
# @return [String] Formatted output
|
|
117
|
+
def generate_output(report)
|
|
118
|
+
if @table_report
|
|
119
|
+
report.to_table_format
|
|
120
|
+
elsif @summary_report
|
|
121
|
+
report.to_summary
|
|
122
|
+
elsif @full_report
|
|
123
|
+
report.to_text_report
|
|
156
124
|
else
|
|
157
|
-
#
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
125
|
+
# Default: format-specific output
|
|
126
|
+
case @format
|
|
127
|
+
when :yaml
|
|
128
|
+
require "yaml"
|
|
129
|
+
report.to_yaml
|
|
130
|
+
when :json
|
|
131
|
+
require "json"
|
|
132
|
+
report.to_json
|
|
133
|
+
else
|
|
134
|
+
report.text_summary
|
|
164
135
|
end
|
|
165
136
|
end
|
|
166
137
|
end
|
|
167
138
|
|
|
168
|
-
# Output report in YAML format
|
|
169
|
-
#
|
|
170
|
-
# @param report [Models::ValidationReport] The validation report
|
|
171
|
-
# @return [void]
|
|
172
|
-
def output_yaml(report)
|
|
173
|
-
require "yaml"
|
|
174
|
-
puts report.to_yaml
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
# Output report in JSON format
|
|
178
|
-
#
|
|
179
|
-
# @param report [Models::ValidationReport] The validation report
|
|
180
|
-
# @return [void]
|
|
181
|
-
def output_json(report)
|
|
182
|
-
require "json"
|
|
183
|
-
puts report.to_json
|
|
184
|
-
end
|
|
185
|
-
|
|
186
139
|
# Determine exit code based on validation results
|
|
187
140
|
#
|
|
188
|
-
# Exit codes:
|
|
189
|
-
#
|
|
190
|
-
#
|
|
191
|
-
#
|
|
141
|
+
# Exit codes (ftxvalidator compatible):
|
|
142
|
+
# 0 = No issues found
|
|
143
|
+
# 1 = Execution errors
|
|
144
|
+
# 2 = Fatal errors found
|
|
145
|
+
# 3 = Major errors found
|
|
146
|
+
# 4 = Minor errors (warnings) found
|
|
147
|
+
# 5 = Spec violations (info) found
|
|
192
148
|
#
|
|
193
|
-
# @param report [
|
|
149
|
+
# @param report [ValidationReport] The validation report
|
|
194
150
|
# @return [Integer] Exit code
|
|
195
|
-
def
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
151
|
+
def exit_code(report)
|
|
152
|
+
return 0 unless @return_value_results
|
|
153
|
+
|
|
154
|
+
return 2 if report.fatal_errors.any?
|
|
155
|
+
return 3 if report.errors_only.any?
|
|
156
|
+
return 4 if report.warnings_only.any?
|
|
157
|
+
return 5 if report.info_only.any?
|
|
158
|
+
0
|
|
203
159
|
end
|
|
204
160
|
end
|
|
205
161
|
end
|