Class: JekyllImgFlow::Providers::BaseProvider

Inherits:
Object
  • Object
show all
Defined in:
lib/jekyll-imgflow/providers/base_provider.rb

Overview

Base provider class - all providers must inherit from this

Direct Known Subclasses

CliBase, HttpBase

Constant Summary collapse

SMARTCROP_POSITIONS =

Valid smartcrop position values

%w[attention entropy center centre].freeze
COMPASS_TO_SHORT =

Short compass → provider position mapping used by HTTP providers.

{
  "northwest" => "tl",
  "northeast" => "tr",
  "southwest" => "bl",
  "southeast" => "br",
  "center" => "c"
}.freeze
KNOWN_OPERATIONS =

Provider capability methods

%i[crop format opacity optimize quality resize
watermark alpha_opacity].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config = {}) ⇒ BaseProvider

Returns a new instance of BaseProvider.



25
26
27
28
# File 'lib/jekyll-imgflow/providers/base_provider.rb', line 25

def initialize(config = {})
  @config = config
  @operations = []
end

Instance Attribute Details

#config ⇒ Object

Returns the value of attribute config.



23
24
25
# File 'lib/jekyll-imgflow/providers/base_provider.rb', line 23

def config
  @config
end

Class Method Details

.provider_name ⇒ Object

Provider name for identification (override in subclasses)



287
288
289
# File 'lib/jekyll-imgflow/providers/base_provider.rb', line 287

def provider_name
  name.split("::").last.downcase
end

.supports_operation?(operation) ⇒ Boolean

Returns:

  • (Boolean)


299
300
301
302
303
# File 'lib/jekyll-imgflow/providers/base_provider.rb', line 299

def supports_operation?(operation)
  return false unless KNOWN_OPERATIONS.include?(operation)

  !unsupported_operations.include?(operation)
end

.unsupported_operations ⇒ Object



295
296
297
# File 'lib/jekyll-imgflow/providers/base_provider.rb', line 295

def unsupported_operations
  [] # Default: no unsupported operations
end

Instance Method Details

#add_watermark(watermark_path, options = {}) ⇒ Object



222
223
224
225
# File 'lib/jekyll-imgflow/providers/base_provider.rb', line 222

def add_watermark(watermark_path, options = {})
  @operations << { type: :watermark, watermark_path: watermark_path,
                   options: options }
end

#alpha_byte_value(opacity) ⇒ Object

Convert a 0..1 opacity float to a 0..255 integer (used by all providers that encode alpha as an 8-bit value).



85
86
87
# File 'lib/jekyll-imgflow/providers/base_provider.rb', line 85

def alpha_byte_value(opacity)
  (opacity * 255).round
end

#alpha_opacity=(opacity) ⇒ Object



227
228
229
# File 'lib/jekyll-imgflow/providers/base_provider.rb', line 227

def alpha_opacity=(opacity)
  @operations << { type: :alpha_opacity, opacity: opacity }
end

#available? ⇒ Boolean

Check if this provider is available (must be implemented by subclasses)

Returns:

  • (Boolean)


116
117
118
119
120
# File 'lib/jekyll-imgflow/providers/base_provider.rb', line 116

def available?
  # Default: not available unless subclass implements actual check
  # This ensures providers explicitly check their availability
  false
end

#check_http_service(url) ⇒ Object

Helper method for HTTP providers to check service availability



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/jekyll-imgflow/providers/base_provider.rb', line 123

def check_http_service(url)
  return false unless url

  begin
    require "net/http"
    require "uri"

    uri = URI(url)
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = uri.scheme == "https"
    http.read_timeout = 2
    http.open_timeout = 2

    request = Net::HTTP::Get.new(uri)
    http.request(request)

    # Consider available if we get any response (even 404 means service is running)
    true
  rescue StandardError
    false
  end
end

#cli_available?(*commands) ⇒ Boolean

Check whether any of the given CLI commands is available on PATH. Used by CLI providers (sharp, libvips, imagemagick).

Returns:

  • (Boolean)


108
109
110
111
112
113
# File 'lib/jekyll-imgflow/providers/base_provider.rb', line 108

def cli_available?(*commands)
  commands.any? do |cmd|
    _, _, status = Open3.capture3("which", cmd)
    status.success?
  end
end

#compass_to_short(position) ⇒ Object

Shared compass → short position mapping (tl/tr/bl/br/c). HTTP providers (imgproxy, weserv, flyimg) all use this mapping.



74
75
76
# File 'lib/jekyll-imgflow/providers/base_provider.rb', line 74

def compass_to_short(position)
  COMPASS_TO_SHORT[position.to_s] || position.to_s
end

#convert_format(format) ⇒ Object



202
203
204
# File 'lib/jekyll-imgflow/providers/base_provider.rb', line 202

def convert_format(format)
  @operations << { type: :format, format: format }
end

#crop(ratio_or_width, height_val = nil, x_val = nil, y_val = nil) ⇒ Object



187
188
189
190
191
192
193
194
195
196
# File 'lib/jekyll-imgflow/providers/base_provider.rb', line 187

def crop(ratio_or_width, height_val = nil, x_val = nil, y_val = nil)
  if height_val.is_a?(Hash)
    @operations << { type: :crop, ratio: ratio_or_width, options: height_val }
  elsif height_val
    options = { width: ratio_or_width, height: height_val, x: x_val, y: y_val }
    @operations << options.merge(type: :crop, ratio: nil, options: options)
  else
    @operations << { type: :crop, ratio: ratio_or_width, options: {} }
  end
end

#crop_coords(operation, opts) ⇒ Object

Coordinates for a crop operation: ratio crops use calculated values, explicit crops use x/y/width/height options (defaulting x/y to 0).



53
54
55
56
57
58
59
60
61
# File 'lib/jekyll-imgflow/providers/base_provider.rb', line 53

def crop_coords(operation, opts)
  if operation[:ratio]
    { x: opts[:calculated_x], y: opts[:calculated_y],
      width: opts[:calculated_width], height: opts[:calculated_height] }
  else
    { x: opts[:x] || 0, y: opts[:y] || 0,
      width: opts[:width], height: opts[:height] }
  end
end

#crop_geometry(operation) ⇒ Object

Normalize a crop operation into a geometry hash. Returns { smartcrop:, keep:, x:, y:, width:, height: } where smartcrop is true when ratio + keep + valid smartcrop position.



43
44
45
46
47
48
49
# File 'lib/jekyll-imgflow/providers/base_provider.rb', line 43

def crop_geometry(operation)
  opts = operation[:options] || {}
  params = operation[:params] || {}
  keep = opts[:keep] || params[:keep] || params[:position]
  smartcrop = operation[:ratio] && keep && SMARTCROP_POSITIONS.include?(keep.to_s)
  crop_coords(operation, opts).merge(smartcrop: smartcrop, keep: keep)
end

#encode_file_url(file_path) ⇒ Object

Helper method for HTTP providers to construct HTTP URLs from file paths



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/jekyll-imgflow/providers/base_provider.rb', line 147

def encode_file_url(file_path)
  # If already a URL, return as-is
  return file_path if file_path.start_with?("http://", "https://")

  # For HTTP providers, construct proper HTTP URL
  site = @config.site
  relative_path = relative_file_path(file_path, site)

  # URL-encode each path segment to handle spaces and special characters
  encoded_path = relative_path.split("/").map do |segment|
    URI.encode_www_form_component(segment)
  end.join("/")

  # Construct HTTP URL for the file
  # Use Jekyll server URL (localhost:4000 by default for development)
  # In production, this would be the actual site URL from config
  base_url = site.config["url"] || "http://localhost:4000"
  baseurl = site.config["baseurl"] || ""

  "#{base_url}#{baseurl}/#{encoded_path}"
end

#execute(input_path, output_path) ⇒ Object

Final execution method - providers must implement this

Raises:

  • (NotImplementedError)


240
241
242
# File 'lib/jekyll-imgflow/providers/base_provider.rb', line 240

def execute(input_path, output_path)
  raise NotImplementedError, "Provider must implement execute method"
end

#find_op(type) ⇒ Object

Operation lookup helpers — used by all providers to avoid repeating @operations.find { |op| op[:type] == :xxx }.



32
33
34
# File 'lib/jekyll-imgflow/providers/base_provider.rb', line 32

def find_op(type)
  @operations.find { |op| op[:type] == type }
end

#format(value) ⇒ Object



210
211
212
# File 'lib/jekyll-imgflow/providers/base_provider.rb', line 210

def format(value)
  convert_format(value)
end

#input_format_ext(input_path) ⇒ Object

Input format extension (lowercased, no dot) for format preservation.



79
80
81
# File 'lib/jekyll-imgflow/providers/base_provider.rb', line 79

def input_format_ext(input_path)
  File.extname(input_path).delete(".").downcase
end

#op?(type) ⇒ Boolean

Returns:

  • (Boolean)


36
37
38
# File 'lib/jekyll-imgflow/providers/base_provider.rb', line 36

def op?(type)
  @operations.any? { |op| op[:type] == type }
end

#opacity(value) ⇒ Object



218
219
220
# File 'lib/jekyll-imgflow/providers/base_provider.rb', line 218

def opacity(value)
  self.alpha_opacity = value
end

#operations ⇒ Object

Get current operations (for testing/debugging)



250
251
252
# File 'lib/jekyll-imgflow/providers/base_provider.rb', line 250

def operations
  @operations.dup
end

#optimize(level = :medium) ⇒ Object



214
215
216
# File 'lib/jekyll-imgflow/providers/base_provider.rb', line 214

def optimize(level = :medium)
  @operations << { type: :optimize, level: level }
end

#quality(value) ⇒ Object



206
207
208
# File 'lib/jekyll-imgflow/providers/base_provider.rb', line 206

def quality(value)
  self.quality = value
end

#quality=(quality) ⇒ Object



198
199
200
# File 'lib/jekyll-imgflow/providers/base_provider.rb', line 198

def quality=(quality)
  @operations << { type: :quality, quality: quality }
end

#relative_file_path(file_path, site) ⇒ Object

Get the relative path from site source using Pathname



170
171
172
173
174
175
176
177
178
179
# File 'lib/jekyll-imgflow/providers/base_provider.rb', line 170

def relative_file_path(file_path, site)
  file_pathname = Pathname.new(file_path)
  if file_pathname.absolute? && file_path.to_s.start_with?(site.source)
    file_pathname.relative_path_from(Pathname.new(site.source)).to_s
  elsif file_path.start_with?("/")
    file_path[1..]
  else
    file_path
  end
end

#reset_operations ⇒ Object

Reset operations for next processing



245
246
247
# File 'lib/jekyll-imgflow/providers/base_provider.rb', line 245

def reset_operations
  @operations = []
end

#resize(width, height, options = {}) ⇒ Object

Operation collection methods - these should NOT execute immediately



182
183
184
185
# File 'lib/jekyll-imgflow/providers/base_provider.rb', line 182

def resize(width, height, options = {})
  @operations << { type: :resize, width: width, height: height,
                   options: options }
end

#smartcrop_interestingness(keep) ⇒ Object

Map a smartcrop keep value to the libvips interestingness string used by Sharp and LibVips smartcrop/thumbnail --crop operations.



98
99
100
101
102
103
104
# File 'lib/jekyll-imgflow/providers/base_provider.rb', line 98

def smartcrop_interestingness(keep)
  case keep.to_s
  when "entropy" then "entropy"
  when "center", "centre" then "centre"
  else "attention"
  end
end

#supports_operation?(operation) ⇒ Boolean

Returns:

  • (Boolean)


235
236
237
# File 'lib/jekyll-imgflow/providers/base_provider.rb', line 235

def supports_operation?(operation)
  self.class.supports_operation?(operation)
end

#temp_path(input_path, suffix) ⇒ Object

Generate a temporary file path by replacing the extension of input_path with suffix (e.g. temp_path("a.jpg", "tmp_base.jpg") → "a.tmp_base.jpg").



92
93
94
# File 'lib/jekyll-imgflow/providers/base_provider.rb', line 92

def temp_path(input_path, suffix)
  input_path.gsub(/\.[^.]+$/, ".#{suffix}")
end

#watermark(watermark_path, options = {}) ⇒ Object



231
232
233
# File 'lib/jekyll-imgflow/providers/base_provider.rb', line 231

def watermark(watermark_path, options = {})
  add_watermark(watermark_path, options)
end

#watermark_parts(operation) ⇒ Object

Extract watermark operation parts into a hash.



64
65
66
67
68
69
70
# File 'lib/jekyll-imgflow/providers/base_provider.rb', line 64

def watermark_parts(operation)
  {
    watermark_path: operation[:watermark_path],
    position: operation[:options][:position],
    opacity: operation[:options][:opacity]
  }
end