jekyll-imgflow 0.4.0 → 0.4.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 +4 -4
- data/README.md +1 -0
- data/lib/jekyll-imgflow/build_time_processor.rb +42 -29
- data/lib/jekyll-imgflow/config.rb +15 -8
- data/lib/jekyll-imgflow/modal_assets.rb +12 -6
- data/lib/jekyll-imgflow/preset_manager.rb +23 -17
- data/lib/jekyll-imgflow/providers/base_provider.rb +26 -19
- data/lib/jekyll-imgflow/tags/resize_tag.rb +12 -12
- data/lib/jekyll-imgflow/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e314c5d37485b9d821e58bd4614798c9e61e3b944c60f6bbb948fb50a6fe9c2b
|
|
4
|
+
data.tar.gz: f9b64d4bc5119727cda256d7e1b1fb77e9ee6c9a1b19e97175290cf8f23df1c9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9102a522a856e6fe440eb207b31c18d350e81a68055b13745ef835e51d257920e115b0054af3494534f9116e89616b13d99fd57c9aed132250bce6e98f280497
|
|
7
|
+
data.tar.gz: e6039b8fe325d7604fea5267c2f9fdfa40b8990647a8e0dfa936c7c2add4376b66cca4a28d8f548f6c0799321660f076902a27cba4dc3e734c424a683b7c0fdd
|
data/README.md
CHANGED
|
@@ -8,6 +8,7 @@ A [Jekyll](https://jekyllrb.com/) plugin for automatic image optimization with m
|
|
|
8
8
|
[](https://github.com/gundestrup/jekyll-imgflow)
|
|
9
9
|
[](https://semgrep.dev/)
|
|
10
10
|
[](https://www.codefactor.io/repository/github/gundestrup/jekyll-imgflow)
|
|
11
|
+
[](https://sonarcloud.io/summary/new_code?id=gundestrup_jekyll-imgflow)
|
|
11
12
|
[](https://codecov.io/gh/gundestrup/jekyll-imgflow)
|
|
12
13
|
[](https://github.com/gundestrup/jekyll-imgflow-vscode)
|
|
13
14
|
[](LICENSE)
|
|
@@ -44,35 +44,11 @@ module JekyllImgFlow
|
|
|
44
44
|
original_paths = find_original_images
|
|
45
45
|
Jekyll.logger.info "📸 Found #{original_paths.length} original images"
|
|
46
46
|
|
|
47
|
-
originals_dir = File.join(@site.source, @config.originals)
|
|
48
|
-
|
|
49
47
|
# Clean up manifest entries for deleted originals
|
|
50
|
-
|
|
51
|
-
@manifest.cleanup_deleted_originals(current_originals)
|
|
52
|
-
@manifest.cleanup_obsolete_defaults(expected_default_operations)
|
|
53
|
-
@manifest.reset_page_usage unless incremental_build?
|
|
48
|
+
prepare_manifest(original_paths)
|
|
54
49
|
|
|
55
50
|
# Build tasks for each original image
|
|
56
|
-
original_paths
|
|
57
|
-
original_name = path.sub("#{originals_dir}/", "")
|
|
58
|
-
file_digest = @filename_generator.file_digest(path)
|
|
59
|
-
|
|
60
|
-
# Check if needs processing (file changed or provider changed)
|
|
61
|
-
if needs_processing?(original_name, path, file_digest)
|
|
62
|
-
Jekyll.logger.info "🔧 Queuing default versions for: #{original_name}"
|
|
63
|
-
|
|
64
|
-
# Build default tasks
|
|
65
|
-
tasks = BatchManager.build_default_tasks(
|
|
66
|
-
original_name, path, @config, @site, file_digest: file_digest
|
|
67
|
-
)
|
|
68
|
-
force_processing = source_digest_changed?(original_name, file_digest)
|
|
69
|
-
tasks.each { |task| task[:force_processing] = force_processing }
|
|
70
|
-
@batch_manager.add_tasks(tasks)
|
|
71
|
-
else
|
|
72
|
-
expected_default_operations.length.times { @operation_processor.stats.record_cache_hit }
|
|
73
|
-
Jekyll.logger.debug "⏭️ Skipping #{original_name} - already up-to-date"
|
|
74
|
-
end
|
|
75
|
-
end
|
|
51
|
+
queue_default_tasks(original_paths)
|
|
76
52
|
|
|
77
53
|
# Process all queued tasks
|
|
78
54
|
results = @batch_manager.process_all
|
|
@@ -89,6 +65,39 @@ module JekyllImgFlow
|
|
|
89
65
|
|
|
90
66
|
private
|
|
91
67
|
|
|
68
|
+
def prepare_manifest(original_paths)
|
|
69
|
+
originals_dir = File.join(@site.source, @config.originals)
|
|
70
|
+
current_originals = original_paths.map { |path| path.sub("#{originals_dir}/", "") }
|
|
71
|
+
@manifest.cleanup_deleted_originals(current_originals)
|
|
72
|
+
@manifest.cleanup_obsolete_defaults(expected_default_operations)
|
|
73
|
+
@manifest.reset_page_usage unless incremental_build?
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def queue_default_tasks(original_paths)
|
|
77
|
+
originals_dir = File.join(@site.source, @config.originals)
|
|
78
|
+
original_paths.reject { |path| AnimatedGifDetector.animated?(path) }.each do |path|
|
|
79
|
+
queue_tasks_for(path, path.sub("#{originals_dir}/", ""))
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def queue_tasks_for(path, original_name)
|
|
84
|
+
file_digest = @filename_generator.file_digest(path)
|
|
85
|
+
unless needs_processing?(original_name, path, file_digest)
|
|
86
|
+
expected_default_operations.length.times { @operation_processor.stats.record_cache_hit }
|
|
87
|
+
Jekyll.logger.debug "⏭️ Skipping #{original_name} - already up-to-date"
|
|
88
|
+
return
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
Jekyll.logger.info "🔧 Queuing default versions for: #{original_name}"
|
|
92
|
+
|
|
93
|
+
tasks = BatchManager.build_default_tasks(
|
|
94
|
+
original_name, path, @config, @site, file_digest: file_digest
|
|
95
|
+
)
|
|
96
|
+
force_processing = source_digest_changed?(original_name, file_digest)
|
|
97
|
+
tasks.each { |task| task[:force_processing] = force_processing }
|
|
98
|
+
@batch_manager.add_tasks(tasks)
|
|
99
|
+
end
|
|
100
|
+
|
|
92
101
|
def register_skipped_tasks(completed_tasks)
|
|
93
102
|
provider_name = @registry.current_provider&.class&.provider_name || "unknown"
|
|
94
103
|
completed_tasks.each do |completed|
|
|
@@ -127,13 +136,17 @@ module JekyllImgFlow
|
|
|
127
136
|
current_provider = @registry.current_provider&.class&.provider_name || "unknown"
|
|
128
137
|
|
|
129
138
|
expected_default_operations.any? do |operations|
|
|
130
|
-
version = default_versions
|
|
131
|
-
@manifest.same_operations?(candidate["operations"], operations)
|
|
132
|
-
end
|
|
139
|
+
version = find_default_version(default_versions, operations)
|
|
133
140
|
!current_default_version?(version, current_provider, file_digest)
|
|
134
141
|
end
|
|
135
142
|
end
|
|
136
143
|
|
|
144
|
+
def find_default_version(default_versions, operations)
|
|
145
|
+
default_versions.find do |candidate|
|
|
146
|
+
@manifest.same_operations?(candidate["operations"], operations)
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
137
150
|
def expected_default_operations
|
|
138
151
|
@config.sizes.each_value.flat_map do |width|
|
|
139
152
|
@config.formats.map do |format|
|
|
@@ -22,6 +22,18 @@ module JekyllImgFlow
|
|
|
22
22
|
DEFAULT_FALLBACK_FORMAT = "jpg"
|
|
23
23
|
DEFAULT_IMAGE_MODAL = true
|
|
24
24
|
|
|
25
|
+
# Core config keys mapped to their defaults (assigned as @ivar per key).
|
|
26
|
+
CORE_CONFIG_DEFAULTS = {
|
|
27
|
+
"originals" => DEFAULT_ORIGINALS,
|
|
28
|
+
"output" => DEFAULT_OUTPUT,
|
|
29
|
+
"input_formats" => DEFAULT_INPUT_FORMATS,
|
|
30
|
+
"sizes" => DEFAULT_SIZES,
|
|
31
|
+
"formats" => DEFAULT_FORMATS,
|
|
32
|
+
"quality" => DEFAULT_QUALITY,
|
|
33
|
+
"backend_priority" => DEFAULT_BACKEND_PRIORITY,
|
|
34
|
+
"fallback_format" => DEFAULT_FALLBACK_FORMAT
|
|
35
|
+
}.freeze
|
|
36
|
+
|
|
25
37
|
attr_reader :site, :originals, :output, :input_formats, :sizes, :formats,
|
|
26
38
|
:quality, :backend_priority, :imgproxy_url,
|
|
27
39
|
:weserv_url, :flyimg_url,
|
|
@@ -37,14 +49,9 @@ module JekyllImgFlow
|
|
|
37
49
|
end
|
|
38
50
|
|
|
39
51
|
def assign_core_config(cfg)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
@sizes = cfg["sizes"] || DEFAULT_SIZES
|
|
44
|
-
@formats = cfg["formats"] || DEFAULT_FORMATS
|
|
45
|
-
@quality = cfg["quality"] || DEFAULT_QUALITY
|
|
46
|
-
@backend_priority = cfg["backend_priority"] || DEFAULT_BACKEND_PRIORITY
|
|
47
|
-
@fallback_format = cfg["fallback_format"] || DEFAULT_FALLBACK_FORMAT
|
|
52
|
+
CORE_CONFIG_DEFAULTS.each do |key, default|
|
|
53
|
+
instance_variable_set("@#{key}", cfg[key] || default)
|
|
54
|
+
end
|
|
48
55
|
@image_modal = cfg.fetch("image_modal", DEFAULT_IMAGE_MODAL)
|
|
49
56
|
end
|
|
50
57
|
|
|
@@ -135,17 +135,23 @@ module JekyllImgFlow
|
|
|
135
135
|
|
|
136
136
|
max_width = @config.sizes.values.max
|
|
137
137
|
Dir.children(directory).filter_map do |filename|
|
|
138
|
-
|
|
139
|
-
next unless candidate[:base_name] == parsed[:base_name]
|
|
140
|
-
next unless candidate[:hash] == parsed[:hash]
|
|
141
|
-
next if max_width && candidate[:width] > max_width
|
|
142
|
-
|
|
143
|
-
File.join(File.dirname(result), filename)
|
|
138
|
+
matching_variant_path(filename, parsed, max_width, result)
|
|
144
139
|
end
|
|
145
140
|
rescue Errno::ENOENT
|
|
146
141
|
[]
|
|
147
142
|
end
|
|
148
143
|
|
|
144
|
+
# Return the output path for +filename+ when it is a variant of the same
|
|
145
|
+
# image (same base name and content hash) within the max configured width.
|
|
146
|
+
def matching_variant_path(filename, parsed, max_width, result)
|
|
147
|
+
candidate = JekyllImgFlow::FilenameGenerator.new.parse_filename(filename)
|
|
148
|
+
return unless candidate[:base_name] == parsed[:base_name]
|
|
149
|
+
return unless candidate[:hash] == parsed[:hash]
|
|
150
|
+
return if max_width && candidate[:width] > max_width
|
|
151
|
+
|
|
152
|
+
File.join(File.dirname(result), filename)
|
|
153
|
+
end
|
|
154
|
+
|
|
149
155
|
def modal_width(result)
|
|
150
156
|
JekyllImgFlow::FilenameGenerator.new.parse_filename(File.basename(result))[:width] || 0
|
|
151
157
|
end
|
|
@@ -147,28 +147,34 @@ module JekyllImgFlow
|
|
|
147
147
|
|
|
148
148
|
def merge_operation_tags(tags, op_type, params)
|
|
149
149
|
case op_type
|
|
150
|
-
when "resize"
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
when "
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
tags[:width] = params["width"] if params["width"] && !tags[:width]
|
|
157
|
-
tags[:height] = params["height"] if params["height"] && !tags[:height]
|
|
158
|
-
when "format"
|
|
159
|
-
merge_format_tag(tags, params)
|
|
160
|
-
when "quality"
|
|
161
|
-
tags[:quality] = params["quality"] if params["quality"]
|
|
162
|
-
when "optimize"
|
|
163
|
-
tags[:optimize] = true
|
|
164
|
-
tags[:level] = params["level"] if params["level"]
|
|
165
|
-
when "opacity"
|
|
166
|
-
tags[:opacity] = params["opacity"] if params["opacity"]
|
|
150
|
+
when "resize" then merge_resize_tags(tags, params)
|
|
151
|
+
when "crop" then merge_crop_tags(tags, params)
|
|
152
|
+
when "format" then merge_format_tag(tags, params)
|
|
153
|
+
when "quality" then tags[:quality] = params["quality"] if params["quality"]
|
|
154
|
+
when "optimize" then merge_optimize_tags(tags, params)
|
|
155
|
+
when "opacity" then tags[:opacity] = params["opacity"] if params["opacity"]
|
|
167
156
|
else
|
|
168
157
|
merge_generic_tags(tags, params)
|
|
169
158
|
end
|
|
170
159
|
end
|
|
171
160
|
|
|
161
|
+
def merge_resize_tags(tags, params)
|
|
162
|
+
tags[:width] = params["width"] if params["width"]
|
|
163
|
+
tags[:height] = params["height"] if params["height"]
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def merge_crop_tags(tags, params)
|
|
167
|
+
tags[:ratio] = params["ratio"] if params["ratio"]
|
|
168
|
+
tags[:aspect_ratio] = params["aspect_ratio"] if params["aspect_ratio"]
|
|
169
|
+
tags[:width] = params["width"] if params["width"] && !tags[:width]
|
|
170
|
+
tags[:height] = params["height"] if params["height"] && !tags[:height]
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def merge_optimize_tags(tags, params)
|
|
174
|
+
tags[:optimize] = true
|
|
175
|
+
tags[:level] = params["level"] if params["level"]
|
|
176
|
+
end
|
|
177
|
+
|
|
172
178
|
def merge_format_tag(tags, params)
|
|
173
179
|
if params["formats"]
|
|
174
180
|
tag_value = params["formats"].is_a?(Array) ? params["formats"].join(",") : params["formats"]
|
|
@@ -45,14 +45,19 @@ module JekyllImgFlow
|
|
|
45
45
|
params = operation[:params] || {}
|
|
46
46
|
keep = opts[:keep] || params[:keep] || params[:position]
|
|
47
47
|
smartcrop = operation[:ratio] && keep && SMARTCROP_POSITIONS.include?(keep.to_s)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
48
|
+
crop_coords(operation, opts).merge(smartcrop: smartcrop, keep: keep)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Coordinates for a crop operation: ratio crops use calculated values,
|
|
52
|
+
# explicit crops use x/y/width/height options (defaulting x/y to 0).
|
|
53
|
+
def crop_coords(operation, opts)
|
|
54
|
+
if operation[:ratio]
|
|
55
|
+
{ x: opts[:calculated_x], y: opts[:calculated_y],
|
|
56
|
+
width: opts[:calculated_width], height: opts[:calculated_height] }
|
|
57
|
+
else
|
|
58
|
+
{ x: opts[:x] || 0, y: opts[:y] || 0,
|
|
59
|
+
width: opts[:width], height: opts[:height] }
|
|
60
|
+
end
|
|
56
61
|
end
|
|
57
62
|
|
|
58
63
|
# Extract watermark operation parts into a hash.
|
|
@@ -145,17 +150,7 @@ module JekyllImgFlow
|
|
|
145
150
|
|
|
146
151
|
# For HTTP providers, construct proper HTTP URL
|
|
147
152
|
site = @config.site
|
|
148
|
-
|
|
149
|
-
# Get the relative path from site source using Pathname
|
|
150
|
-
source_path = Pathname.new(site.source)
|
|
151
|
-
file_pathname = Pathname.new(file_path)
|
|
152
|
-
relative_path = if file_pathname.absolute? && file_path.to_s.start_with?(site.source)
|
|
153
|
-
file_pathname.relative_path_from(source_path).to_s
|
|
154
|
-
elsif file_path.start_with?("/")
|
|
155
|
-
file_path[1..]
|
|
156
|
-
else
|
|
157
|
-
file_path
|
|
158
|
-
end
|
|
153
|
+
relative_path = relative_file_path(file_path, site)
|
|
159
154
|
|
|
160
155
|
# URL-encode each path segment to handle spaces and special characters
|
|
161
156
|
encoded_path = relative_path.split("/").map do |segment|
|
|
@@ -171,6 +166,18 @@ module JekyllImgFlow
|
|
|
171
166
|
"#{base_url}#{baseurl}/#{encoded_path}"
|
|
172
167
|
end
|
|
173
168
|
|
|
169
|
+
# Get the relative path from site source using Pathname
|
|
170
|
+
def relative_file_path(file_path, site)
|
|
171
|
+
file_pathname = Pathname.new(file_path)
|
|
172
|
+
if file_pathname.absolute? && file_path.to_s.start_with?(site.source)
|
|
173
|
+
file_pathname.relative_path_from(Pathname.new(site.source)).to_s
|
|
174
|
+
elsif file_path.start_with?("/")
|
|
175
|
+
file_path[1..]
|
|
176
|
+
else
|
|
177
|
+
file_path
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
174
181
|
# Operation collection methods - these should NOT execute immediately
|
|
175
182
|
def resize(width, height, options = {})
|
|
176
183
|
@operations << { type: :resize, width: width, height: height,
|
|
@@ -21,19 +21,8 @@ module JekyllImgFlow
|
|
|
21
21
|
# Calculate missing dimension to maintain aspect ratio ONLY when one dimension is missing
|
|
22
22
|
width, height = calculate_missing_dimension(width, height, original_width, original_height)
|
|
23
23
|
|
|
24
|
-
# Calculate scale factors for provider
|
|
25
|
-
scale_x = width && original_width ? width.to_f / original_width : nil
|
|
26
|
-
scale_y = height && original_height ? height.to_f / original_height : nil
|
|
27
|
-
|
|
28
24
|
# Provider receives ALL data - provider chooses what to use
|
|
29
|
-
resize_data =
|
|
30
|
-
original_width: original_width,
|
|
31
|
-
original_height: original_height,
|
|
32
|
-
target_width: width,
|
|
33
|
-
target_height: height,
|
|
34
|
-
scale_x: scale_x,
|
|
35
|
-
scale_y: scale_y
|
|
36
|
-
}
|
|
25
|
+
resize_data = build_resize_data(width, height, original_width, original_height)
|
|
37
26
|
|
|
38
27
|
Jekyll.logger.debug "🔍 ResizeTag: About to execute provider - input: #{input_path}, output: #{output_path}"
|
|
39
28
|
@provider.resize(width, height, resize_data)
|
|
@@ -46,6 +35,17 @@ module JekyllImgFlow
|
|
|
46
35
|
|
|
47
36
|
private
|
|
48
37
|
|
|
38
|
+
def build_resize_data(width, height, original_width, original_height)
|
|
39
|
+
{
|
|
40
|
+
original_width: original_width,
|
|
41
|
+
original_height: original_height,
|
|
42
|
+
target_width: width,
|
|
43
|
+
target_height: height,
|
|
44
|
+
scale_x: width && original_width ? width.to_f / original_width : nil,
|
|
45
|
+
scale_y: height && original_height ? height.to_f / original_height : nil
|
|
46
|
+
}
|
|
47
|
+
end
|
|
48
|
+
|
|
49
49
|
def calculate_missing_dimension(width, height, original_width, original_height)
|
|
50
50
|
if width.nil? && height
|
|
51
51
|
# Only height provided - calculate width to maintain aspect ratio
|