jekyll-highlight-cards 2.0.0 → 2.2.0

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.
@@ -33,53 +33,40 @@ module JekyllHighlightCards
33
33
  # {% polaroid /img.jpg link="https://example.com" image_link="https://other.com" %}
34
34
  class PolaroidTag < Liquid::Tag
35
35
  include ArchiveHelper
36
- include DimensionParser
37
36
  include ExpressionEvaluator
38
37
  include TemplateRenderer
39
38
 
40
- # Initialize the tag
41
- #
42
- # @param tag_name [String] the name of the tag
43
- # @param markup [String] the tag markup containing parameters
44
- # @param tokens [Array] parse tokens (unused)
45
- def initialize(tag_name, markup, tokens)
46
- super
47
- @markup = markup.strip
48
- end
49
-
50
39
  # Render the polaroid tag
51
40
  #
52
41
  # @param context [Liquid::Context] the Liquid rendering context
53
42
  # @return [String] rendered HTML
54
43
  def render(context)
55
- # Parse markup
56
44
  params = parse_markup(@markup, context)
57
45
 
58
- # Validate required image_url
59
- raise ArgumentError, "polaroid tag requires an image URL" if params[:image_url].nil? || params[:image_url].empty?
60
-
61
- # Parse size parameter if present
62
- width, height = parse_dimensions(params[:size]) if params[:size]
46
+ raise ArgumentError, "polaroid tag requires an image URL" if params.fetch(:image_url).empty?
63
47
 
64
- # Determine link URL (defaults to image URL)
65
- # Track if link was explicitly provided
66
- explicit_link = !params[:link].nil? && !params[:link].empty?
67
- link_url = params[:link] || params[:image_url]
48
+ width, height = DimensionParser.parse_dimensions(params[:size])
68
49
 
69
- # Determine image link URL (can be overridden by image_link parameter)
70
- # If image_link is provided, use it; otherwise use the standard link_url
71
- image_link_url = if params[:image_link] && !params[:image_link].empty?
72
- params[:image_link]
73
- else
50
+ explicit_link = !params[:link].to_s.empty?
51
+ link_url = if params[:link].to_s.empty?
52
+ params.fetch(:image_url)
53
+ else
54
+ params.fetch(:link)
55
+ end
56
+ image_link_url = if params[:image_link].to_s.empty?
74
57
  link_url
58
+ else
59
+ params.fetch(:image_link)
75
60
  end
61
+ archive_url = resolve_archive(
62
+ params[:archive],
63
+ link_url,
64
+ site: context.registers[:site],
65
+ explicit_link: explicit_link
66
+ )
76
67
 
77
- # Resolve archive URL (archives the link URL, not the image)
78
- archive_url = resolve_archive(params[:archive], context, link_url)
79
-
80
- # Build template variables
81
68
  variables = build_template_variables(
82
- params[:image_url],
69
+ params.fetch(:image_url),
83
70
  width,
84
71
  height,
85
72
  params[:title],
@@ -90,11 +77,7 @@ module JekyllHighlightCards
90
77
  archive_url
91
78
  )
92
79
 
93
- # Get site from context
94
- site = context.registers[:site]
95
-
96
- # Render template
97
- render_template(site, "polaroid", variables)
80
+ render_template(context.registers[:site], "polaroid", variables)
98
81
  end
99
82
 
100
83
  private
@@ -105,91 +88,32 @@ module JekyllHighlightCards
105
88
  # @param context [Liquid::Context] the Liquid context
106
89
  # @return [Hash] parsed parameters
107
90
  def parse_markup(markup, context)
108
- # Split by whitespace, keeping quoted strings and Liquid expressions together
109
- # Handles escaped quotes (\") and backslashes (\\) within quoted strings
110
- tokens = []
111
- current = ""
112
- in_quotes = false
113
- quote_char = nil
114
- in_liquid = 0
115
- escaped = false
116
-
117
- markup.each_char do |char|
118
- # Handle escape sequences when in quotes
119
- if escaped
120
- current += char # Add the escaped character directly
121
- escaped = false
122
- next
123
- end
124
-
125
- # Check for escape character when in quotes
126
- if char == "\\" && in_quotes
127
- escaped = true
128
- next # Don't add backslash to output, it's just the escape marker
129
- end
130
-
131
- # Track Liquid expression boundaries
132
- if char == "{" && !in_quotes
133
- in_liquid += 1
134
- current += char
135
- elsif char == "}" && !in_quotes && in_liquid.positive?
136
- in_liquid -= 1
137
- current += char
138
- # Track quote boundaries
139
- elsif ['"', "'"].include?(char) && !in_quotes && in_liquid.zero?
140
- in_quotes = true
141
- quote_char = char
142
- current += char
143
- elsif char == quote_char && in_quotes
144
- in_quotes = false
145
- current += char
146
- quote_char = nil
147
- # Split on whitespace only if not in quotes or Liquid expression
148
- elsif char.match?(/\s/) && !in_quotes && in_liquid.zero?
149
- tokens << current unless current.empty?
150
- current = ""
151
- else
152
- current += char
153
- end
154
- end
155
- tokens << current unless current.empty?
156
-
157
- # First token is image URL (required)
158
- image_url_token = tokens.shift
159
- image_url = evaluate_expression(image_url_token, context, allow_nil: false)
91
+ parsed = PolaroidMarkup.parse(markup)
160
92
 
161
- # Parse remaining tokens as key=value pairs
162
- result = { image_url: image_url }
163
- tokens.each do |token|
164
- next unless token =~ /^(\w+)=(.+)$/
93
+ result = { image_url: evaluate_expression(parsed[:image_url], context) }
94
+ parsed.each do |key, value|
95
+ next if key == :image_url
165
96
 
166
- key = Regexp.last_match(1).to_sym
167
- value_token = Regexp.last_match(2)
168
-
169
- # Evaluate value as Liquid expression
170
- result[key] = evaluate_expression(value_token, context, allow_nil: true)
97
+ result[key] = evaluate_expression(value, context)
171
98
  end
172
99
 
173
100
  result
174
101
  end
175
102
 
176
- # Resolve archive URL (may be explicit, auto-lookup, or opt-out)
103
+ # Resolve archive URL (may be explicit, auto-lookup, or opt-out).
104
+ # Named archive params are already Liquid-evaluated in parse_markup.
177
105
  #
178
106
  # @param source [String, nil] the archive source
179
- # @param context [Liquid::Context] the Liquid context (unused but kept for consistency)
180
107
  # @param url [String] the target URL to archive
108
+ # @param site [Jekyll::Site, nil] site for +highlight_cards.noarchive+
109
+ # @param explicit_link [Boolean] whether +link=+ was provided (self-link is not an archive target)
181
110
  # @return [String, nil] resolved archive URL
182
- def resolve_archive(source, context, url)
183
- # Check for explicit opt-out
184
- return nil if source&.downcase == "none"
185
-
186
- # Check for explicit archive URL
187
- return evaluate_expression(source, context, allow_nil: true) if source && !source.empty?
188
-
189
- # Auto-lookup if enabled (archives the link URL, not the image)
190
- return archive_url_for(url) if archive_enabled?
111
+ def resolve_archive(source, url, site: nil, explicit_link: true)
112
+ return nil if source && source.downcase == "none"
113
+ return source unless source.to_s.empty?
114
+ return nil unless explicit_link
191
115
 
192
- nil
116
+ archive_enabled? && archive_url_for(url, site: site)
193
117
  end
194
118
 
195
119
  # Build template variables hash for rendering
@@ -215,12 +139,12 @@ module JekyllHighlightCards
215
139
  explicit_link,
216
140
  archive_url
217
141
  )
218
- # Only set link_display if link was explicitly provided (not defaulted to image)
219
- link_display = explicit_link && link_url ? strip_protocol(link_url) : nil
142
+ link_display = strip_protocol(link_url) if explicit_link
143
+ escaped_title = CGI.escapeHTML(title.to_s)
144
+ escaped_title = "&nbsp;" if escaped_title.empty?
220
145
 
221
146
  {
222
147
  "image_url" => image_url,
223
- "link_url" => link_url,
224
148
  "image_link_url" => image_link_url,
225
149
  "title" => title,
226
150
  "alt" => alt,
@@ -229,12 +153,12 @@ module JekyllHighlightCards
229
153
  "width" => width,
230
154
  "height" => height,
231
155
  "escaped_image_url" => CGI.escapeHTML(image_url),
232
- "escaped_link_url" => link_url ? CGI.escapeHTML(link_url) : nil,
233
- "escaped_image_link_url" => image_link_url ? CGI.escapeHTML(image_link_url) : nil,
234
- "escaped_title" => title && !title.empty? ? CGI.escapeHTML(title) : "&nbsp;",
235
- "escaped_alt" => alt && !alt.empty? ? CGI.escapeHTML(alt) : "",
236
- "escaped_link_display" => link_display && !link_display.empty? ? CGI.escapeHTML(link_display) : "&nbsp;",
237
- "escaped_archive_url" => archive_url ? CGI.escapeHTML(archive_url) : nil
156
+ "escaped_link_url" => CGI.escapeHTML(link_url),
157
+ "escaped_image_link_url" => CGI.escapeHTML(image_link_url),
158
+ "escaped_title" => escaped_title,
159
+ "escaped_alt" => CGI.escapeHTML(alt.to_s),
160
+ "escaped_link_display" => link_display && CGI.escapeHTML(link_display),
161
+ "escaped_archive_url" => archive_url && CGI.escapeHTML(archive_url)
238
162
  }
239
163
  end
240
164
 
@@ -243,7 +167,7 @@ module JekyllHighlightCards
243
167
  # @param url [String] the URL
244
168
  # @return [String] URL without protocol
245
169
  def strip_protocol(url)
246
- url.sub(%r{^https?://}, "")
170
+ url.sub(%r{\Ahttps?://}, "")
247
171
  end
248
172
  end
249
173
  end
@@ -27,13 +27,14 @@ module JekyllHighlightCards
27
27
  template = Liquid::Template.parse(template_content)
28
28
  template.render(variables)
29
29
  rescue Errno::ENOENT, Errno::EACCES => e
30
- raise TemplateRenderError, "Failed to read template '#{template_name}' at #{template_path}: #{e.message}"
30
+ raise TemplateRenderError, "Failed to read template '#{template_name}' at #{template_path}: #{e.class}: #{e}"
31
31
  rescue Encoding::InvalidByteSequenceError => e
32
- raise TemplateRenderError, "Invalid encoding in template '#{template_name}' at #{template_path}: #{e.message}"
32
+ raise TemplateRenderError,
33
+ "Invalid encoding in template '#{template_name}' at #{template_path}: #{e.class}: #{e}"
33
34
  rescue Liquid::SyntaxError, Liquid::Error => e
34
- raise TemplateRenderError, "Liquid error in template '#{template_name}': #{e.message}"
35
+ raise TemplateRenderError, "Liquid error in template '#{template_name}': #{e.class}: #{e}"
35
36
  rescue StandardError => e
36
- raise TemplateRenderError, "Unexpected error rendering template '#{template_name}': #{e.message}"
37
+ raise TemplateRenderError, "Unexpected error rendering template '#{template_name}': #{e.class}: #{e}"
37
38
  end
38
39
  end
39
40
 
@@ -57,6 +58,19 @@ module JekyllHighlightCards
57
58
  find_gem_template(relative_path)
58
59
  end
59
60
 
61
+ # Verify template path is safe (within allowed directory)
62
+ #
63
+ # @param allowed_dir [String] the allowed base directory
64
+ # @param template_path [String] the template path to check
65
+ # @return [String, nil] template path if safe, nil otherwise
66
+ def safe_template_path(allowed_dir, template_path)
67
+ return nil unless File.exist?(template_path)
68
+
69
+ expanded_dir = File.expand_path(allowed_dir)
70
+ expanded_path = File.expand_path(template_path)
71
+ template_path if expanded_path.start_with?(expanded_dir)
72
+ end
73
+
60
74
  private
61
75
 
62
76
  # Validate template name to prevent path traversal attacks
@@ -76,9 +90,11 @@ module JekyllHighlightCards
76
90
  # @param relative_path [String] relative path to template
77
91
  # @return [String, nil] path to template or nil
78
92
  def find_user_template(site, relative_path)
79
- return nil unless site&.source
93
+ source = site&.source
94
+ # Empty source would File.join to "/_includes/..." — skip it explicitly.
95
+ return nil unless source.is_a?(String) && !source.empty?
80
96
 
81
- includes_dir = File.join(site.source, "_includes")
97
+ includes_dir = File.join(source, "_includes")
82
98
  template_path = File.join(includes_dir, relative_path)
83
99
 
84
100
  safe_template_path(includes_dir, template_path)
@@ -95,19 +111,5 @@ module JekyllHighlightCards
95
111
 
96
112
  safe_template_path(includes_dir, template_path)
97
113
  end
98
-
99
- # Verify template path is safe (within allowed directory)
100
- #
101
- # @param allowed_dir [String] the allowed base directory
102
- # @param template_path [String] the template path to check
103
- # @return [String, nil] template path if safe, nil otherwise
104
- def safe_template_path(allowed_dir, template_path)
105
- return nil unless File.exist?(template_path)
106
-
107
- expanded_dir = File.expand_path(allowed_dir)
108
- expanded_path = File.expand_path(template_path)
109
-
110
- expanded_path.start_with?(expanded_dir) ? template_path : nil
111
- end
112
114
  end
113
115
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JekyllHighlightCards
4
- VERSION = "2.0.0"
4
+ VERSION = "2.2.0"
5
5
  end
@@ -12,11 +12,17 @@ require_relative "jekyll-highlight-cards/dimension_parser"
12
12
  require_relative "jekyll-highlight-cards/expression_evaluator"
13
13
  require_relative "jekyll-highlight-cards/archive_helper"
14
14
  require_relative "jekyll-highlight-cards/template_renderer"
15
+ require_relative "jekyll-highlight-cards/linkcard_markup"
16
+ require_relative "jekyll-highlight-cards/polaroid_markup"
15
17
 
16
18
  # Require tags and hooks
17
19
  require_relative "jekyll-highlight-cards/linkcard_tag"
18
20
  require_relative "jekyll-highlight-cards/polaroid_tag"
19
21
  require_relative "jekyll-highlight-cards/image_sizing_hooks"
22
+ require_relative "jekyll-highlight-cards/freeze_archives/markup_analyzer"
23
+ require_relative "jekyll-highlight-cards/freeze_archives/tag_locator"
24
+ require_relative "jekyll-highlight-cards/freeze_archives/archive_inserter"
25
+ require_relative "jekyll-highlight-cards/commands/freeze_archives"
20
26
 
21
27
  # jekyll-highlight-cards: Styled card components for Jekyll
22
28
  #
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jekyll-highlight-cards
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Texarkanine
@@ -63,6 +63,34 @@ dependencies:
63
63
  - - "~>"
64
64
  - !ruby/object:Gem::Version
65
65
  version: '2.0'
66
+ - !ruby/object:Gem::Dependency
67
+ name: mutant
68
+ requirement: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - "~>"
71
+ - !ruby/object:Gem::Version
72
+ version: '0.16'
73
+ type: :development
74
+ prerelease: false
75
+ version_requirements: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - "~>"
78
+ - !ruby/object:Gem::Version
79
+ version: '0.16'
80
+ - !ruby/object:Gem::Dependency
81
+ name: mutant-rspec
82
+ requirement: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - "~>"
85
+ - !ruby/object:Gem::Version
86
+ version: '0.16'
87
+ type: :development
88
+ prerelease: false
89
+ version_requirements: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - "~>"
92
+ - !ruby/object:Gem::Version
93
+ version: '0.16'
66
94
  - !ruby/object:Gem::Dependency
67
95
  name: rake
68
96
  requirement: !ruby/object:Gem::Requirement
@@ -139,28 +167,28 @@ dependencies:
139
167
  requirements:
140
168
  - - "~>"
141
169
  - !ruby/object:Gem::Version
142
- version: '0.22'
170
+ version: '1.0'
143
171
  type: :development
144
172
  prerelease: false
145
173
  version_requirements: !ruby/object:Gem::Requirement
146
174
  requirements:
147
175
  - - "~>"
148
176
  - !ruby/object:Gem::Version
149
- version: '0.22'
177
+ version: '1.0'
150
178
  - !ruby/object:Gem::Dependency
151
179
  name: simplecov-cobertura
152
180
  requirement: !ruby/object:Gem::Requirement
153
181
  requirements:
154
182
  - - "~>"
155
183
  - !ruby/object:Gem::Version
156
- version: '3.1'
184
+ version: '4.0'
157
185
  type: :development
158
186
  prerelease: false
159
187
  version_requirements: !ruby/object:Gem::Requirement
160
188
  requirements:
161
189
  - - "~>"
162
190
  - !ruby/object:Gem::Version
163
- version: '3.1'
191
+ version: '4.0'
164
192
  - !ruby/object:Gem::Dependency
165
193
  name: webmock
166
194
  requirement: !ruby/object:Gem::Requirement
@@ -195,10 +223,16 @@ files:
195
223
  - jekyll-highlight-cards.gemspec
196
224
  - lib/jekyll-highlight-cards.rb
197
225
  - lib/jekyll-highlight-cards/archive_helper.rb
226
+ - lib/jekyll-highlight-cards/commands/freeze_archives.rb
198
227
  - lib/jekyll-highlight-cards/dimension_parser.rb
199
228
  - lib/jekyll-highlight-cards/expression_evaluator.rb
229
+ - lib/jekyll-highlight-cards/freeze_archives/archive_inserter.rb
230
+ - lib/jekyll-highlight-cards/freeze_archives/markup_analyzer.rb
231
+ - lib/jekyll-highlight-cards/freeze_archives/tag_locator.rb
200
232
  - lib/jekyll-highlight-cards/image_sizing_hooks.rb
233
+ - lib/jekyll-highlight-cards/linkcard_markup.rb
201
234
  - lib/jekyll-highlight-cards/linkcard_tag.rb
235
+ - lib/jekyll-highlight-cards/polaroid_markup.rb
202
236
  - lib/jekyll-highlight-cards/polaroid_tag.rb
203
237
  - lib/jekyll-highlight-cards/template_renderer.rb
204
238
  - lib/jekyll-highlight-cards/version.rb