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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -0
- data/README.md +36 -0
- data/jekyll-highlight-cards.gemspec +4 -2
- data/lib/jekyll-highlight-cards/archive_helper.rb +73 -54
- data/lib/jekyll-highlight-cards/commands/freeze_archives.rb +178 -0
- data/lib/jekyll-highlight-cards/dimension_parser.rb +10 -18
- data/lib/jekyll-highlight-cards/expression_evaluator.rb +22 -23
- data/lib/jekyll-highlight-cards/freeze_archives/archive_inserter.rb +72 -0
- data/lib/jekyll-highlight-cards/freeze_archives/markup_analyzer.rb +59 -0
- data/lib/jekyll-highlight-cards/freeze_archives/tag_locator.rb +32 -0
- data/lib/jekyll-highlight-cards/image_sizing_hooks.rb +53 -62
- data/lib/jekyll-highlight-cards/linkcard_markup.rb +83 -0
- data/lib/jekyll-highlight-cards/linkcard_tag.rb +13 -92
- data/lib/jekyll-highlight-cards/polaroid_markup.rb +81 -0
- data/lib/jekyll-highlight-cards/polaroid_tag.rb +43 -119
- data/lib/jekyll-highlight-cards/template_renderer.rb +22 -20
- data/lib/jekyll-highlight-cards/version.rb +1 -1
- data/lib/jekyll-highlight-cards.rb +6 -0
- metadata +39 -5
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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 =
|
|
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,
|
|
183
|
-
|
|
184
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
219
|
-
|
|
142
|
+
link_display = strip_protocol(link_url) if explicit_link
|
|
143
|
+
escaped_title = CGI.escapeHTML(title.to_s)
|
|
144
|
+
escaped_title = " " 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" =>
|
|
233
|
-
"escaped_image_link_url" =>
|
|
234
|
-
"escaped_title" =>
|
|
235
|
-
"escaped_alt" =>
|
|
236
|
-
"escaped_link_display" => link_display &&
|
|
237
|
-
"escaped_archive_url" => archive_url
|
|
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{
|
|
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.
|
|
30
|
+
raise TemplateRenderError, "Failed to read template '#{template_name}' at #{template_path}: #{e.class}: #{e}"
|
|
31
31
|
rescue Encoding::InvalidByteSequenceError => e
|
|
32
|
-
raise TemplateRenderError,
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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(
|
|
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
|
|
@@ -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.
|
|
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
|
|
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
|
|
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: '
|
|
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: '
|
|
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
|