png_conform 0.1.2 → 0.1.3
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 +116 -6
- data/Gemfile +1 -1
- data/config/validation_profiles.yml +105 -0
- data/lib/png_conform/analyzers/comparison_analyzer.rb +41 -7
- data/lib/png_conform/analyzers/metrics_analyzer.rb +6 -9
- data/lib/png_conform/analyzers/optimization_analyzer.rb +30 -24
- data/lib/png_conform/analyzers/resolution_analyzer.rb +31 -32
- data/lib/png_conform/cli.rb +12 -0
- data/lib/png_conform/commands/check_command.rb +118 -53
- data/lib/png_conform/configuration.rb +147 -0
- data/lib/png_conform/container.rb +113 -0
- data/lib/png_conform/models/validation_result.rb +30 -4
- data/lib/png_conform/pipelines/pipeline_result.rb +39 -0
- data/lib/png_conform/pipelines/stages/analysis_stage.rb +35 -0
- data/lib/png_conform/pipelines/stages/base_stage.rb +23 -0
- data/lib/png_conform/pipelines/stages/chunk_validation_stage.rb +74 -0
- data/lib/png_conform/pipelines/stages/sequence_validation_stage.rb +77 -0
- data/lib/png_conform/pipelines/stages/signature_validation_stage.rb +41 -0
- data/lib/png_conform/pipelines/validation_pipeline.rb +90 -0
- data/lib/png_conform/readers/full_load_reader.rb +13 -4
- data/lib/png_conform/readers/streaming_reader.rb +27 -2
- data/lib/png_conform/reporters/color_reporter.rb +17 -14
- data/lib/png_conform/reporters/visual_elements.rb +22 -16
- data/lib/png_conform/services/analysis_manager.rb +120 -0
- data/lib/png_conform/services/chunk_processor.rb +195 -0
- data/lib/png_conform/services/file_signature.rb +226 -0
- data/lib/png_conform/services/file_strategy.rb +78 -0
- data/lib/png_conform/services/lru_cache.rb +170 -0
- data/lib/png_conform/services/parallel_validator.rb +118 -0
- data/lib/png_conform/services/profile_manager.rb +41 -12
- data/lib/png_conform/services/result_builder.rb +299 -0
- data/lib/png_conform/services/validation_cache.rb +210 -0
- data/lib/png_conform/services/validation_orchestrator.rb +188 -0
- data/lib/png_conform/services/validation_service.rb +53 -337
- data/lib/png_conform/services/validator_pool.rb +142 -0
- data/lib/png_conform/utils/colorizer.rb +149 -0
- data/lib/png_conform/validators/chunk_registry.rb +12 -0
- data/lib/png_conform/validators/streaming_idat_validator.rb +123 -0
- data/lib/png_conform/version.rb +1 -1
- data/png_conform.gemspec +1 -0
- metadata +38 -2
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "paint"
|
|
4
|
+
|
|
5
|
+
module PngConform
|
|
6
|
+
module Utils
|
|
7
|
+
# Colorization utility using the Paint gem
|
|
8
|
+
#
|
|
9
|
+
# Provides a centralized colorization service for terminal output.
|
|
10
|
+
# Wraps the Paint gem with semantic method names and caching.
|
|
11
|
+
#
|
|
12
|
+
# @example Basic usage
|
|
13
|
+
# Colorizer.success("Success message")
|
|
14
|
+
# Colorizer.error("Error message")
|
|
15
|
+
# Colorizer.warning("Warning message", bold: true)
|
|
16
|
+
#
|
|
17
|
+
class Colorizer
|
|
18
|
+
class << self
|
|
19
|
+
# Check if colorization is enabled
|
|
20
|
+
#
|
|
21
|
+
# @return [Boolean] true if colors are enabled
|
|
22
|
+
def enabled?
|
|
23
|
+
Paint.mode != 0
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Enable or disable colorization
|
|
27
|
+
#
|
|
28
|
+
# @param value [Boolean] true to enable, false to disable
|
|
29
|
+
def enabled=(value)
|
|
30
|
+
Paint.mode = value ? Paint.detect_mode : 0
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Colorize text as success (green)
|
|
34
|
+
#
|
|
35
|
+
# @param text [String] Text to colorize
|
|
36
|
+
# @param bold [Boolean] Whether to use bold formatting
|
|
37
|
+
# @return [String] Colorized text
|
|
38
|
+
def success(text, bold: false)
|
|
39
|
+
colorize(text, :green, bold: bold)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Colorize text as error (red)
|
|
43
|
+
#
|
|
44
|
+
# @param text [String] Text to colorize
|
|
45
|
+
# @param bold [Boolean] Whether to use bold formatting
|
|
46
|
+
# @return [String] Colorized text
|
|
47
|
+
def error(text, bold: false)
|
|
48
|
+
colorize(text, :red, bold: bold)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Colorize text as warning (yellow)
|
|
52
|
+
#
|
|
53
|
+
# @param text [String] Text to colorize
|
|
54
|
+
# @param bold [Boolean] Whether to use bold formatting
|
|
55
|
+
# @return [String] Colorized text
|
|
56
|
+
def warning(text, bold: false)
|
|
57
|
+
colorize(text, :yellow, bold: bold)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Colorize text as info (blue)
|
|
61
|
+
#
|
|
62
|
+
# @param text [String] Text to colorize
|
|
63
|
+
# @param bold [Boolean] Whether to use bold formatting
|
|
64
|
+
# @return [String] Colorized text
|
|
65
|
+
def info(text, bold: false)
|
|
66
|
+
colorize(text, :blue, bold: bold)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Colorize text with bold formatting
|
|
70
|
+
#
|
|
71
|
+
# @param text [String] Text to colorize
|
|
72
|
+
# @param color [Symbol] Color to use
|
|
73
|
+
# @return [String] Colorized text
|
|
74
|
+
def bold(text, color = nil)
|
|
75
|
+
return Paint[text, :bold] unless color
|
|
76
|
+
|
|
77
|
+
Paint[text, color, :bold]
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Colorize text with a specific color
|
|
81
|
+
#
|
|
82
|
+
# @param text [String] Text to colorize
|
|
83
|
+
# @param color [Symbol] Color to use
|
|
84
|
+
# @param bold [Boolean] Whether to use bold formatting
|
|
85
|
+
# @return [String] Colorized text
|
|
86
|
+
def colorize(text, color, bold: false)
|
|
87
|
+
return text.to_s unless enabled?
|
|
88
|
+
|
|
89
|
+
if bold
|
|
90
|
+
Paint[text, color, :bold]
|
|
91
|
+
else
|
|
92
|
+
Paint[text, color]
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Colorize text based on priority
|
|
97
|
+
#
|
|
98
|
+
# @param text [String] Text to colorize
|
|
99
|
+
# @param priority [Symbol] Priority (:high, :medium, :low)
|
|
100
|
+
# @return [String] Colorized text
|
|
101
|
+
def priority(text, priority:)
|
|
102
|
+
case priority
|
|
103
|
+
when :high
|
|
104
|
+
error(text, bold: true)
|
|
105
|
+
when :medium
|
|
106
|
+
warning(text, bold: true)
|
|
107
|
+
when :low
|
|
108
|
+
info(text)
|
|
109
|
+
else
|
|
110
|
+
text.to_s
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Get a checkmark symbol (with color)
|
|
115
|
+
#
|
|
116
|
+
# @param success [Boolean] Whether the check should be green
|
|
117
|
+
# @return [String] Colored checkmark
|
|
118
|
+
def checkmark(success: true)
|
|
119
|
+
if success
|
|
120
|
+
success("✓")
|
|
121
|
+
else
|
|
122
|
+
error("✗")
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Remove all color codes from text
|
|
127
|
+
#
|
|
128
|
+
# @param text [String] Text to uncolorize
|
|
129
|
+
# @return [String] Plain text
|
|
130
|
+
def uncolorize(text)
|
|
131
|
+
Paint.unpaint(text)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Apply multiple colorizations to different parts of text
|
|
135
|
+
#
|
|
136
|
+
# @param template [String] Template with %{key} placeholders
|
|
137
|
+
# @param substitutions [Hash] Hash of key => [text, color] pairs
|
|
138
|
+
# @return [String] Colorized text
|
|
139
|
+
# @example
|
|
140
|
+
# Colorizer.template("%{greeting} %{name}",
|
|
141
|
+
# greeting: ["Hello", :green],
|
|
142
|
+
# name: ["World", :blue])
|
|
143
|
+
def template(template, substitutions)
|
|
144
|
+
Paint[template, substitutions]
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
@@ -189,6 +189,18 @@ module PngConform
|
|
|
189
189
|
validator_class.new(chunk, context)
|
|
190
190
|
end
|
|
191
191
|
|
|
192
|
+
# Preload all validators for performance
|
|
193
|
+
#
|
|
194
|
+
# Loads all validator classes into cache at startup to improve
|
|
195
|
+
# validation performance by avoiding lazy loading overhead.
|
|
196
|
+
#
|
|
197
|
+
# @return [void]
|
|
198
|
+
def preload_validators!
|
|
199
|
+
VALIDATOR_PATHS.each_key do |chunk_type|
|
|
200
|
+
validator_for(chunk_type)
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
192
204
|
private
|
|
193
205
|
|
|
194
206
|
# Cache for loaded validator classes
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../base_validator"
|
|
4
|
+
|
|
5
|
+
module PngConform
|
|
6
|
+
module Validators
|
|
7
|
+
# Streaming IDAT validator for memory-efficient validation
|
|
8
|
+
#
|
|
9
|
+
# This validator processes IDAT chunk data in streaming mode to avoid
|
|
10
|
+
# loading large decompressed image data into memory. Instead of
|
|
11
|
+
# fully decompressing, it validates the zlib stream incrementally.
|
|
12
|
+
#
|
|
13
|
+
class StreamingIdatValidator < BaseValidator
|
|
14
|
+
# Validate IDAT chunk
|
|
15
|
+
#
|
|
16
|
+
# Performs streaming validation of zlib-compressed image data
|
|
17
|
+
# without fully decompressing into memory.
|
|
18
|
+
#
|
|
19
|
+
# @return [void]
|
|
20
|
+
def validate
|
|
21
|
+
return add_error("Invalid IDAT sequence") unless check_idat_sequence
|
|
22
|
+
return add_error("Invalid IDAT order") unless check_idat_order
|
|
23
|
+
|
|
24
|
+
# Validate compression in streaming mode
|
|
25
|
+
validate_streaming_compression
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
# Check IDAT sequence requirements
|
|
31
|
+
#
|
|
32
|
+
# @return [Boolean] True if IDAT sequence is valid
|
|
33
|
+
def check_idat_sequence
|
|
34
|
+
# IDAT chunks must be consecutive
|
|
35
|
+
previous_chunk_type = @context.seen?("IDAT") ? "IDAT" : nil
|
|
36
|
+
|
|
37
|
+
if previous_chunk_type && previous_chunk_type != "IDAT"
|
|
38
|
+
add_warning("IDAT chunks must be consecutive")
|
|
39
|
+
return false
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
true
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Check IDAT order requirements
|
|
46
|
+
#
|
|
47
|
+
# IDAT must come after PLTE (if present) and before IEND
|
|
48
|
+
#
|
|
49
|
+
# @return [Boolean] True if IDAT is in correct position
|
|
50
|
+
def check_idat_order
|
|
51
|
+
# IDAT cannot be first (must have IHDR)
|
|
52
|
+
return false unless @context.seen?("IHDR")
|
|
53
|
+
|
|
54
|
+
# If PLTE exists, IDAT must come after it
|
|
55
|
+
if @context.seen?("PLTE")
|
|
56
|
+
@context.chunks_of_type("PLTE")
|
|
57
|
+
find_chunk_index("PLTE")
|
|
58
|
+
@context.chunks_of_type("IDAT").length
|
|
59
|
+
|
|
60
|
+
# For now, just check that PLTE was seen before any IDAT
|
|
61
|
+
# The actual ordering is validated in sequence validation
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
true
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Find the index of a chunk in the validation sequence
|
|
68
|
+
#
|
|
69
|
+
# @param chunk_type [String] Chunk type to find
|
|
70
|
+
# @return [Integer, nil] Index of chunk or nil
|
|
71
|
+
def find_chunk_index(_chunk_type)
|
|
72
|
+
# This would require tracking chunk order in context
|
|
73
|
+
# For now, return nil as this is handled by sequence validation
|
|
74
|
+
nil
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Validate compression using streaming zlib validation
|
|
78
|
+
#
|
|
79
|
+
# Instead of fully decompressing the image data (which can be huge),
|
|
80
|
+
# this validates that the compressed data is valid zlib format without
|
|
81
|
+
# loading the decompressed result.
|
|
82
|
+
#
|
|
83
|
+
# @return [void]
|
|
84
|
+
def validate_streaming_compression
|
|
85
|
+
require "zlib"
|
|
86
|
+
|
|
87
|
+
# Try to validate the zlib stream without full decompression
|
|
88
|
+
# We inflate just enough to verify validity
|
|
89
|
+
begin
|
|
90
|
+
inflater = Zlib::Inflate.new(Zlib::MAX_WBITS)
|
|
91
|
+
|
|
92
|
+
# Process data in chunks to avoid large memory allocation
|
|
93
|
+
chunk.data.each_slice(8192) do |slice|
|
|
94
|
+
# << is used to append data to the inflate stream
|
|
95
|
+
# This validates the data format without storing result
|
|
96
|
+
result = inflater << slice
|
|
97
|
+
|
|
98
|
+
# If we got decompressed data back, the stream is valid
|
|
99
|
+
# We don't need to store it, just validate
|
|
100
|
+
if result && !result.empty?
|
|
101
|
+
# Data is valid, we can discard it
|
|
102
|
+
# This means the compressed data is well-formed
|
|
103
|
+
end
|
|
104
|
+
rescue Zlib::DataError => e
|
|
105
|
+
return add_error("Invalid compressed data in IDAT: #{e.message}")
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Finish the stream to validate end of data
|
|
109
|
+
inflater.finish
|
|
110
|
+
inflater.close
|
|
111
|
+
|
|
112
|
+
add_info("Streaming compression validation successful")
|
|
113
|
+
rescue Zlib::StreamError => e
|
|
114
|
+
add_error("Invalid zlib stream in IDAT: #{e.message}")
|
|
115
|
+
rescue Zlib::BufError => e
|
|
116
|
+
add_error("Buffer error in IDAT compression: #{e.message}")
|
|
117
|
+
rescue StandardError => e
|
|
118
|
+
add_error("Compression validation error: #{e.message}")
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
data/lib/png_conform/version.rb
CHANGED
data/png_conform.gemspec
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: png_conform
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-01-20 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bindata
|
|
@@ -38,6 +38,20 @@ dependencies:
|
|
|
38
38
|
- - "~>"
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
40
|
version: '0.7'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: paint
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '2.3'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '2.3'
|
|
41
55
|
- !ruby/object:Gem::Dependency
|
|
42
56
|
name: thor
|
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -91,6 +105,7 @@ files:
|
|
|
91
105
|
- benchmarks/profile_validation.rb
|
|
92
106
|
- benchmarks/results/.gitkeep
|
|
93
107
|
- benchmarks/run_benchmark.rb
|
|
108
|
+
- config/validation_profiles.yml
|
|
94
109
|
- docs/ARCHITECTURE.adoc
|
|
95
110
|
- docs/CHUNK_TYPES.adoc
|
|
96
111
|
- docs/CLI_OPTIONS.adoc
|
|
@@ -112,6 +127,8 @@ files:
|
|
|
112
127
|
- lib/png_conform/cli.rb
|
|
113
128
|
- lib/png_conform/commands/check_command.rb
|
|
114
129
|
- lib/png_conform/commands/list_command.rb
|
|
130
|
+
- lib/png_conform/configuration.rb
|
|
131
|
+
- lib/png_conform/container.rb
|
|
115
132
|
- lib/png_conform/models/chunk.rb
|
|
116
133
|
- lib/png_conform/models/chunk_info.rb
|
|
117
134
|
- lib/png_conform/models/compression_info.rb
|
|
@@ -121,6 +138,13 @@ files:
|
|
|
121
138
|
- lib/png_conform/models/image_info.rb
|
|
122
139
|
- lib/png_conform/models/validation_error.rb
|
|
123
140
|
- lib/png_conform/models/validation_result.rb
|
|
141
|
+
- lib/png_conform/pipelines/pipeline_result.rb
|
|
142
|
+
- lib/png_conform/pipelines/stages/analysis_stage.rb
|
|
143
|
+
- lib/png_conform/pipelines/stages/base_stage.rb
|
|
144
|
+
- lib/png_conform/pipelines/stages/chunk_validation_stage.rb
|
|
145
|
+
- lib/png_conform/pipelines/stages/sequence_validation_stage.rb
|
|
146
|
+
- lib/png_conform/pipelines/stages/signature_validation_stage.rb
|
|
147
|
+
- lib/png_conform/pipelines/validation_pipeline.rb
|
|
124
148
|
- lib/png_conform/readers/full_load_reader.rb
|
|
125
149
|
- lib/png_conform/readers/streaming_reader.rb
|
|
126
150
|
- lib/png_conform/reporters/base_reporter.rb
|
|
@@ -135,9 +159,20 @@ files:
|
|
|
135
159
|
- lib/png_conform/reporters/very_verbose_reporter.rb
|
|
136
160
|
- lib/png_conform/reporters/visual_elements.rb
|
|
137
161
|
- lib/png_conform/reporters/yaml_reporter.rb
|
|
162
|
+
- lib/png_conform/services/analysis_manager.rb
|
|
163
|
+
- lib/png_conform/services/chunk_processor.rb
|
|
164
|
+
- lib/png_conform/services/file_signature.rb
|
|
165
|
+
- lib/png_conform/services/file_strategy.rb
|
|
166
|
+
- lib/png_conform/services/lru_cache.rb
|
|
167
|
+
- lib/png_conform/services/parallel_validator.rb
|
|
138
168
|
- lib/png_conform/services/profile_manager.rb
|
|
169
|
+
- lib/png_conform/services/result_builder.rb
|
|
170
|
+
- lib/png_conform/services/validation_cache.rb
|
|
171
|
+
- lib/png_conform/services/validation_orchestrator.rb
|
|
139
172
|
- lib/png_conform/services/validation_service.rb
|
|
173
|
+
- lib/png_conform/services/validator_pool.rb
|
|
140
174
|
- lib/png_conform/services/zlib_validator.rb
|
|
175
|
+
- lib/png_conform/utils/colorizer.rb
|
|
141
176
|
- lib/png_conform/validators/ancillary/bkgd_validator.rb
|
|
142
177
|
- lib/png_conform/validators/ancillary/chrm_validator.rb
|
|
143
178
|
- lib/png_conform/validators/ancillary/cicp_validator.rb
|
|
@@ -187,6 +222,7 @@ files:
|
|
|
187
222
|
- lib/png_conform/validators/mng/seek_validator.rb
|
|
188
223
|
- lib/png_conform/validators/mng/show_validator.rb
|
|
189
224
|
- lib/png_conform/validators/mng/term_validator.rb
|
|
225
|
+
- lib/png_conform/validators/streaming_idat_validator.rb
|
|
190
226
|
- lib/png_conform/version.rb
|
|
191
227
|
- png_conform.gemspec
|
|
192
228
|
homepage: https://github.com/claricle/png_conform
|