Class: JekyllImgFlow::Parser

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

Overview

Central parser for all ImgFlow markup (tags, presets, etc.) Optimized version with reduced duplication and complexity

Constant Summary collapse

OPERATION_PARAMS =

Define valid operation parameters and attribute types

%i[width height ratio aspect_ratio quality format formats
optimize level watermark opacity position keep preset].freeze
HTML_ATTRIBUTES =
%i[alt class title loading link modal].freeze
SIMPLE_OPERATIONS =

Simple operation mappings for single-parameter operations

{
  quality: ->(val) { { type: :quality, params: { quality: val } } },
  watermark: ->(val) { { type: :watermark, params: { text: val } } },
  opacity: ->(val) { { type: :alpha_opacity, params: { opacity: val } } }
}.freeze

Class Method Summary collapse

Class Method Details

.add_crop_resize_operations(operations, opts) ⇒ Object



121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/jekyll-imgflow/parser.rb', line 121

def self.add_crop_resize_operations(operations, opts)
  has_crop = opts[:ratio] || opts[:aspect_ratio]
  has_resize = opts[:width] || opts[:height]
  crop_params = opts.slice(:ratio, :aspect_ratio, :keep, :position)

  if has_crop && has_resize
    operations << { type: :crop, params: crop_params }
    operations << { type: :resize, params: opts.except(:ratio, :aspect_ratio, :keep, :position) }
  elsif has_crop
    operations << { type: :crop, params: crop_params }
  elsif has_resize
    operations << { type: :resize, params: opts }
  end
end

.add_format_operation(operations, opts) ⇒ Object



142
143
144
145
146
147
148
149
150
151
# File 'lib/jekyll-imgflow/parser.rb', line 142

def self.add_format_operation(operations, opts)
  return unless opts[:format] || opts[:formats]

  format_value = opts[:format] || opts[:formats]
  operations << if format_value.is_a?(Array)
                  { type: :format, params: { formats: format_value } }
                else
                  { type: :format, params: { format: format_value } }
                end
end

.add_optimize_operation(operations, opts) ⇒ Object



153
154
155
156
157
158
# File 'lib/jekyll-imgflow/parser.rb', line 153

def self.add_optimize_operation(operations, opts)
  return unless opts[:optimize] || opts[:level]

  operations << { type: :optimize,
                  params: { level: opts[:level] || :medium } }
end

.add_simple_operations(operations, opts) ⇒ Object



136
137
138
139
140
# File 'lib/jekyll-imgflow/parser.rb', line 136

def self.add_simple_operations(operations, opts)
  SIMPLE_OPERATIONS.each do |key, builder|
    operations << builder.call(opts[key]) if opts[key]
  end
end

.build_error_response(raw_options, error_message) ⇒ Object



182
183
184
185
186
187
188
189
190
191
# File 'lib/jekyll-imgflow/parser.rb', line 182

def self.build_error_response(raw_options, error_message)
  {
    image_path: nil,
    operations: [],
    html_attributes: {},
    raw_options: raw_options,
    preset: nil,
    error: error_message
  }
end

.build_success_response(image_path, operations, html_attributes, raw_options) ⇒ Object



193
194
195
196
197
198
199
200
201
# File 'lib/jekyll-imgflow/parser.rb', line 193

def self.build_success_response(image_path, operations, html_attributes, raw_options)
  {
    image_path: image_path,
    operations: operations,
    html_attributes: html_attributes,
    raw_options: raw_options,
    preset: raw_options[:preset]
  }
end

.clean_and_convert_value(value, options = {}) ⇒ Object



81
82
83
84
85
86
87
88
89
# File 'lib/jekyll-imgflow/parser.rb', line 81

def self.clean_and_convert_value(value, options = {})
  value = value.tr("'\"", "")
  value = convert_numeric_value(value)

  # Handle comma-separated arrays
  value = value.split(",").map(&:strip) if options[:handle_arrays] && value.is_a?(String) && value.include?(",")

  value
end

.convert_numeric_value(value) ⇒ Object



91
92
93
94
95
96
# File 'lib/jekyll-imgflow/parser.rb', line 91

def self.convert_numeric_value(value)
  return value.to_i if value.match?(/^\d+$/)
  return value.to_f if value.match?(/^\d+\.\d+$/)

  value
end

.convert_operations_array(operations_array) ⇒ Object

Convert operations array from TagScanner to structured hash



173
174
175
176
177
178
179
180
# File 'lib/jekyll-imgflow/parser.rb', line 173

def self.convert_operations_array(operations_array)
  operations_array.each_with_object({}) do |op_str, hash|
    next unless op_str.include?(":")

    key, value = op_str.split(":", 2)
    hash[key.strip.to_sym] = clean_and_convert_value(value.strip, handle_arrays: true)
  end
end

.detect_operations(options) ⇒ Object



109
110
111
112
113
114
115
116
117
118
119
# File 'lib/jekyll-imgflow/parser.rb', line 109

def self.detect_operations(options)
  operations = []
  operation_options = options.slice(*OPERATION_PARAMS)

  add_crop_resize_operations(operations, operation_options)
  add_simple_operations(operations, operation_options)
  add_format_operation(operations, operation_options)
  add_optimize_operation(operations, operation_options)

  operations
end

.extract_html_attributes(options) ⇒ Object



160
161
162
# File 'lib/jekyll-imgflow/parser.rb', line 160

def self.extract_html_attributes(options)
  options.slice(*HTML_ATTRIBUTES)
end

.extract_image_path(options, markup) ⇒ Object



164
165
166
167
168
169
170
# File 'lib/jekyll-imgflow/parser.rb', line 164

def self.extract_image_path(options, markup)
  return options[:image_path] if options[:image_path]
  return if markup.strip.empty?

  # Extract first word (before space or =)
  markup.split(/[\s=]/, 2).first&.strip
end

.parse(markup, _context = nil) ⇒ Object



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/jekyll-imgflow/parser.rb', line 21

def self.parse(markup, _context = nil)
  # For KISS architecture: expect structured data from TagScanner
  return validate_structured_data(markup) if markup.is_a?(Hash) && markup.key?(:image_path)

  # Parse simple markup (backward compatibility)
  raw_options = parse_markup_to_hash(markup)
  operations = detect_operations(raw_options)
  html_attributes = extract_html_attributes(raw_options)
  image_path = extract_image_path(raw_options, markup)

  # Early return if no image path
  return build_error_response(raw_options, "No image path found in markup") unless image_path

  # File validation handled by PathResolver when resolving paths
  build_success_response(image_path, operations, html_attributes, raw_options)
end

.parse_key_value_format(markup, options) ⇒ Object



51
52
53
# File 'lib/jekyll-imgflow/parser.rb', line 51

def self.parse_key_value_format(markup, options)
  parse_key_value_pairs(markup.split, "=", result: options, handle_arrays: false)
end

.parse_key_value_pairs(pairs, separator, options = {}) ⇒ Object



70
71
72
73
74
75
76
77
78
79
# File 'lib/jekyll-imgflow/parser.rb', line 70

def self.parse_key_value_pairs(pairs, separator, options = {})
  pairs.each do |pair|
    if pair.include?(separator)
      key, value = pair.split(separator, 2)
      options[:result][key.to_sym] = clean_and_convert_value(value, options)
    elsif options[:image_path_handler]
      options[:image_path_handler].call(pair, options[:result])
    end
  end
end

.parse_markup_to_hash(markup) ⇒ Object



38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/jekyll-imgflow/parser.rb', line 38

def self.parse_markup_to_hash(markup)
  options = {}

  # Handle different markup formats
  if markup.include?("=")
    parse_key_value_format(markup, options)
  else
    parse_positional_format(markup, options)
  end

  options
end

.parse_positional_format(markup, options) ⇒ Object



55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/jekyll-imgflow/parser.rb', line 55

def self.parse_positional_format(markup, options)
  # Use Shellwords for proper quote handling
  parts = Shellwords.split(markup)

  parse_key_value_pairs(
    parts,
    ":",
    result: options,
    handle_arrays: true,
    image_path_handler: lambda { |part, result|
      result[:image_path] = part unless result[:image_path]
    }
  )
end

.validate_structured_data(data) ⇒ Object



98
99
100
101
102
103
104
105
106
107
# File 'lib/jekyll-imgflow/parser.rb', line 98

def self.validate_structured_data(data)
  image_path = data[:image_path]
  operations = data[:operations] || {}

  return build_error_response(data, "No image path found in structured data") unless image_path

  # Operations validation handled by Tags - Parser only parses structure
  # File validation handled by PathResolver when resolving paths
  build_success_response(image_path, operations, data[:html_attributes] || {}, data)
end