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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8400a4bc3d213b557802452b3af0816098443aa5542f27e81bf690dd2ee3f7a8
4
- data.tar.gz: 1f8562de9d69d7451603623ab2e63741f4889a4d04d35d9ae01a561878e8d539
3
+ metadata.gz: e314c5d37485b9d821e58bd4614798c9e61e3b944c60f6bbb948fb50a6fe9c2b
4
+ data.tar.gz: f9b64d4bc5119727cda256d7e1b1fb77e9ee6c9a1b19e97175290cf8f23df1c9
5
5
  SHA512:
6
- metadata.gz: cbd0f3c0d194bd7acfdc199c9c1eb78dcb69d09c4cc9f213ceb48d1c1c5cb4402518a87a14f44f234df1487ae6eba4495f92aae2b31bb602b199bcaaab4438ef
7
- data.tar.gz: 7c07124101bf734ebedce868d1329900c79acde6d90463d5fbd680fe49f02517e743ba4d7d37ebf3e26454e3e9dd4df80ada8265874bb96b1659767a33a8306f
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
  [![Status](https://img.shields.io/badge/status-active-success)](https://github.com/gundestrup/jekyll-imgflow)
9
9
  [![Semgrep](https://img.shields.io/badge/Semgrep-scanned-1B2B34?logo=semgrep&logoColor=white)](https://semgrep.dev/)
10
10
  [![CodeFactor](https://www.codefactor.io/repository/github/gundestrup/jekyll-imgflow/badge)](https://www.codefactor.io/repository/github/gundestrup/jekyll-imgflow)
11
+ [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=gundestrup_jekyll-imgflow&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=gundestrup_jekyll-imgflow)
11
12
  [![codecov](https://codecov.io/gh/gundestrup/jekyll-imgflow/branch/main/graph/badge.svg)](https://codecov.io/gh/gundestrup/jekyll-imgflow)
12
13
  [![VS Code Extension](https://img.shields.io/badge/VS%20Code%20Companion-jekyll--imgflow-blue?logo=visual-studio-code)](https://github.com/gundestrup/jekyll-imgflow-vscode)
13
14
  [![License](https://img.shields.io/github/license/gundestrup/jekyll-imgflow)](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
- current_originals = original_paths.map { |path| path.sub("#{originals_dir}/", "") }
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.reject { |path| AnimatedGifDetector.animated?(path) }.each do |path|
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.find do |candidate|
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
- @originals = cfg["originals"] || DEFAULT_ORIGINALS
41
- @output = cfg["output"] || DEFAULT_OUTPUT
42
- @input_formats = cfg["input_formats"] || DEFAULT_INPUT_FORMATS
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
- candidate = JekyllImgFlow::FilenameGenerator.new.parse_filename(filename)
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
- tags[:width] = params["width"] if params["width"]
152
- tags[:height] = params["height"] if params["height"]
153
- when "crop"
154
- tags[:ratio] = params["ratio"] if params["ratio"]
155
- tags[:aspect_ratio] = params["aspect_ratio"] if params["aspect_ratio"]
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
- coords = if operation[:ratio]
49
- { x: opts[:calculated_x], y: opts[:calculated_y],
50
- width: opts[:calculated_width], height: opts[:calculated_height] }
51
- else
52
- { x: opts[:x] || 0, y: opts[:y] || 0,
53
- width: opts[:width], height: opts[:height] }
54
- end
55
- coords.merge(smartcrop: smartcrop, keep: keep)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JekyllImgFlow
4
- VERSION = "0.4.0"
4
+ VERSION = "0.4.1"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jekyll-imgflow
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Svend Gundestrup