jekyll_picture_tag 1.9.0 → 1.10.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/.travis.yml +4 -7
- data/Dockerfile +9 -0
- data/docs/Gemfile.lock +183 -88
- data/docs/contributing.md +50 -16
- data/docs/example_presets.md +1 -1
- data/docs/index.md +26 -20
- data/docs/installation.md +22 -7
- data/docs/presets.md +106 -54
- data/docs/releases.md +4 -0
- data/docs/usage.md +68 -38
- data/jekyll_picture_tag.gemspec +2 -1
- data/lib/jekyll_picture_tag/defaults/presets.yml +1 -0
- data/lib/jekyll_picture_tag/generated_image.rb +47 -15
- data/lib/jekyll_picture_tag/instructions.rb +1 -0
- data/lib/jekyll_picture_tag/instructions/arg_splitter.rb +68 -0
- data/lib/jekyll_picture_tag/instructions/preset.rb +34 -14
- data/lib/jekyll_picture_tag/instructions/set.rb +18 -8
- data/lib/jekyll_picture_tag/instructions/tag_parser.rb +59 -69
- data/lib/jekyll_picture_tag/output_formats/basic.rb +28 -11
- data/lib/jekyll_picture_tag/router.rb +8 -0
- data/lib/jekyll_picture_tag/srcsets/basic.rb +20 -7
- data/lib/jekyll_picture_tag/utils.rb +18 -0
- data/lib/jekyll_picture_tag/version.rb +1 -1
- data/readme.md +35 -16
- metadata +21 -5
@@ -34,6 +34,24 @@ module PictureTag
|
|
34
34
|
@source_images ||= build_source_images
|
35
35
|
end
|
36
36
|
|
37
|
+
def crop(media = nil)
|
38
|
+
params.geometries[media] || preset.crop(media)
|
39
|
+
end
|
40
|
+
|
41
|
+
def gravity(media = nil)
|
42
|
+
params.gravities[media] || preset.gravity(media)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns a class constant for the selected output format, which is used
|
46
|
+
# to dynamically instantiate it.
|
47
|
+
def output_class
|
48
|
+
Object.const_get(
|
49
|
+
'PictureTag::OutputFormats::' + Utils.titleize(preset['markup'])
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
37
55
|
def build_source_images
|
38
56
|
source_names = params.source_names
|
39
57
|
media_presets = params.media_presets
|
@@ -48,14 +66,6 @@ module PictureTag
|
|
48
66
|
|
49
67
|
sources
|
50
68
|
end
|
51
|
-
|
52
|
-
# Returns a class constant for the selected output format, which is used
|
53
|
-
# to dynamically instantiate it.
|
54
|
-
def output_class
|
55
|
-
Object.const_get(
|
56
|
-
'PictureTag::OutputFormats::' + Utils.titleize(preset['markup'])
|
57
|
-
)
|
58
|
-
end
|
59
69
|
end
|
60
70
|
end
|
61
71
|
end
|
@@ -1,105 +1,95 @@
|
|
1
1
|
module PictureTag
|
2
2
|
module Instructions
|
3
|
-
#
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
3
|
+
# Tag Parsing Responsibilities:
|
4
|
+
#
|
5
|
+
# {% picture mypreset a.jpg 3:2 mobile: b.jpg --alt "Alt" --link "/" %}
|
6
|
+
# | Jekyll | TagParser | HTMLAttributes |
|
7
|
+
#
|
8
|
+
# This class takes the arguments handed to the liquid tag (given as a simple
|
9
|
+
# string), hands them to ArgSplitter (which breaks them up into an array of
|
10
|
+
# words), extracts the preset name (if present), source image name(s),
|
11
|
+
# associated media queries (if present), and image-related arguments such as
|
12
|
+
# crop and gravity. HTML attributes are handed off to its respective class
|
13
|
+
# (as 'leftovers')
|
14
|
+
#
|
15
|
+
# Media presets and source names are stored as arrays in their correct
|
16
|
+
# orders. Gravities and geometries are stored in a hash, keyed by their
|
17
|
+
# relevant media presets. Note that the base image will have a media preset
|
18
|
+
# of nil, which is a perfectly fine hash key.
|
19
|
+
#
|
7
20
|
class TagParser
|
8
|
-
attr_reader :preset_name, :source_names, :media_presets
|
9
|
-
|
10
|
-
build_params PictureTag::Utils.liquid_lookup(raw_params)
|
21
|
+
attr_reader :preset_name, :source_names, :media_presets, :gravities,
|
22
|
+
:geometries, :leftovers
|
11
23
|
|
12
|
-
|
24
|
+
def initialize(raw_params)
|
25
|
+
@raw_params = raw_params
|
26
|
+
@params = split_params
|
13
27
|
|
14
|
-
# The first param specified will be our base image, so it has no
|
15
|
-
# associated media query.
|
16
28
|
@media_presets = []
|
17
|
-
@source_names = []
|
29
|
+
@source_names = []
|
30
|
+
@geometries = {}
|
31
|
+
@gravities = {}
|
18
32
|
|
19
|
-
|
20
|
-
# keys and values.
|
21
|
-
add_media_source while @params.first =~ /[\w\-]+:$/
|
22
|
-
end
|
23
|
-
|
24
|
-
def leftovers
|
25
|
-
@params
|
33
|
+
parse_params
|
26
34
|
end
|
27
35
|
|
28
36
|
private
|
29
37
|
|
30
|
-
def
|
31
|
-
|
32
|
-
|
38
|
+
def split_params
|
39
|
+
ArgSplitter
|
40
|
+
.new(Utils.liquid_lookup(@raw_params))
|
41
|
+
.words
|
33
42
|
end
|
34
43
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
'default'
|
39
|
-
else
|
40
|
-
@params.shift
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
# Originally separating arguments was just handled by splitting the raw
|
45
|
-
# params on spaces. To handle quotes and backslash escaping, we have to
|
46
|
-
# parse the string by characters to break it up correctly. I'm sure
|
47
|
-
# there's a library to do this, but it's not that much code honestly. If
|
48
|
-
# this starts getting big, we'll pull in a new dependency.
|
49
|
-
def build_params(raw_params)
|
50
|
-
@params = []
|
51
|
-
@word = ''
|
52
|
-
@in_quotes = false
|
53
|
-
@escaped = false
|
44
|
+
def parse_params
|
45
|
+
@preset_name = determine_preset_name
|
46
|
+
@source_names << strip_quotes(@params.shift)
|
54
47
|
|
55
|
-
|
48
|
+
parse_param(@params.first) until stop_here? @params.first
|
56
49
|
|
57
|
-
|
50
|
+
@leftovers = @params
|
58
51
|
end
|
59
52
|
|
60
|
-
def
|
61
|
-
|
62
|
-
|
63
|
-
close_escape char
|
53
|
+
def parse_param(param)
|
54
|
+
if param.match?(/[\w\-]+:$/)
|
55
|
+
add_media_source
|
64
56
|
|
65
|
-
|
66
|
-
|
67
|
-
handle_special char
|
57
|
+
elsif Utils::GRAVITIES.include?(param.downcase)
|
58
|
+
@gravities[@media_presets.last] = @params.shift
|
68
59
|
|
69
|
-
|
70
|
-
|
71
|
-
@word << char
|
60
|
+
elsif param.match?(Utils::GEOMETRY_REGEX)
|
61
|
+
@geometries[@media_presets.last] = @params.shift
|
72
62
|
|
73
|
-
# Character is whitespace outside of double quotes:
|
74
63
|
else
|
75
|
-
|
64
|
+
raise_error(param)
|
76
65
|
end
|
77
66
|
end
|
78
67
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
@word << char
|
85
|
-
end
|
68
|
+
# HTML attributes are handled by its own class; once we encounter them
|
69
|
+
# we are finished here.
|
70
|
+
def stop_here?(param)
|
71
|
+
# No param Explicit HTML attribute Implicit HTML attribute
|
72
|
+
param.nil? || param.match?(/^--\S*/) || param.match?(/^\w*="/)
|
86
73
|
end
|
87
74
|
|
88
|
-
def
|
89
|
-
|
90
|
-
|
91
|
-
@params << @word
|
92
|
-
@word = ''
|
75
|
+
def add_media_source
|
76
|
+
@media_presets << @params.shift.delete_suffix(':')
|
77
|
+
@source_names << strip_quotes(@params.shift)
|
93
78
|
end
|
94
79
|
|
95
|
-
|
96
|
-
|
97
|
-
@
|
80
|
+
# The first param is the preset name, unless it's a filename.
|
81
|
+
def determine_preset_name
|
82
|
+
@params.first.include?('.') ? 'default' : @params.shift
|
98
83
|
end
|
99
84
|
|
100
85
|
def strip_quotes(name)
|
101
86
|
name.delete_prefix('"').delete_suffix('"')
|
102
87
|
end
|
88
|
+
|
89
|
+
def raise_error(param)
|
90
|
+
raise ArgumentError, "Could not parse '#{param}' in the following "\
|
91
|
+
"tag: \n {% picture #{@raw_params} %}"
|
92
|
+
end
|
103
93
|
end
|
104
94
|
end
|
105
95
|
end
|
@@ -71,22 +71,28 @@ module PictureTag
|
|
71
71
|
|
72
72
|
# GeneratedImage class, not HTML
|
73
73
|
def build_fallback_image
|
74
|
-
|
74
|
+
return fallback_candidate if fallback_candidate.exists?
|
75
|
+
|
76
|
+
build_new_fallback_image
|
77
|
+
end
|
78
|
+
|
79
|
+
def fallback_candidate
|
80
|
+
@fallback_candidate ||= GeneratedImage.new(
|
75
81
|
source_file: PictureTag.source_images.first,
|
76
82
|
format: PictureTag.fallback_format,
|
77
|
-
width: PictureTag.fallback_width
|
83
|
+
width: PictureTag.fallback_width,
|
84
|
+
crop: PictureTag.crop,
|
85
|
+
gravity: PictureTag.gravity
|
78
86
|
)
|
79
|
-
|
80
|
-
return fallback if fallback.exists?
|
81
|
-
|
82
|
-
build_new_fallback_image
|
83
87
|
end
|
84
88
|
|
85
89
|
def build_new_fallback_image
|
86
90
|
GeneratedImage.new(
|
87
91
|
source_file: PictureTag.source_images.first,
|
88
92
|
format: PictureTag.fallback_format,
|
89
|
-
width: checked_fallback_width
|
93
|
+
width: checked_fallback_width,
|
94
|
+
crop: PictureTag.crop,
|
95
|
+
gravity: PictureTag.gravity
|
90
96
|
)
|
91
97
|
end
|
92
98
|
|
@@ -107,15 +113,26 @@ module PictureTag
|
|
107
113
|
content.add_parent anchor
|
108
114
|
end
|
109
115
|
|
116
|
+
def source
|
117
|
+
PictureTag.source_images.first
|
118
|
+
end
|
119
|
+
|
120
|
+
def source_width
|
121
|
+
if PictureTag.crop
|
122
|
+
fallback_candidate.cropped_source_width
|
123
|
+
else
|
124
|
+
source.width
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
110
128
|
def checked_fallback_width
|
111
|
-
source = PictureTag.source_images.first
|
112
129
|
target = PictureTag.fallback_width
|
113
130
|
|
114
|
-
if target >
|
131
|
+
if target > source_width
|
115
132
|
Utils.warning "#{source.shortname} is smaller than the " \
|
116
|
-
"requested fallback width of #{target}px. Using #{
|
133
|
+
"requested fallback width of #{target}px. Using #{source_width}" \
|
117
134
|
' px instead.'
|
118
|
-
|
135
|
+
source_width
|
119
136
|
else
|
120
137
|
target
|
121
138
|
end
|
@@ -65,26 +65,37 @@ module PictureTag
|
|
65
65
|
end
|
66
66
|
|
67
67
|
def checked_targets
|
68
|
-
if target_files.any? { |f| f.width >
|
68
|
+
if target_files.any? { |f| f.width > source_width }
|
69
69
|
|
70
70
|
small_source_warn
|
71
71
|
|
72
|
-
files = target_files.reject { |f| f.width >=
|
73
|
-
files.push(generate_file(
|
72
|
+
files = target_files.reject { |f| f.width >= source_width }
|
73
|
+
files.push(generate_file(source_width))
|
74
74
|
end
|
75
75
|
|
76
76
|
files || target_files
|
77
77
|
end
|
78
78
|
|
79
|
+
def source_width
|
80
|
+
@source_width ||= if PictureTag.crop(@media)
|
81
|
+
target_files.first.cropped_source_width
|
82
|
+
else
|
83
|
+
@source_image.width
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
79
87
|
def target_files
|
80
88
|
@target_files ||= widths.collect { |w| generate_file(w) }
|
81
89
|
end
|
82
90
|
|
83
91
|
def small_source_warn
|
84
92
|
Utils.warning(
|
85
|
-
|
86
|
-
|
87
|
-
|
93
|
+
<<~HEREDOC
|
94
|
+
#{@source_image.shortname}
|
95
|
+
is #{source_width}px wide (after cropping, if applicable),
|
96
|
+
smaller than at least one size in the set #{widths}.
|
97
|
+
Will not enlarge.
|
98
|
+
HEREDOC
|
88
99
|
)
|
89
100
|
end
|
90
101
|
|
@@ -92,7 +103,9 @@ module PictureTag
|
|
92
103
|
GeneratedImage.new(
|
93
104
|
source_file: @source_image,
|
94
105
|
width: width,
|
95
|
-
format: @input_format
|
106
|
+
format: @input_format,
|
107
|
+
crop: PictureTag.crop(@media),
|
108
|
+
gravity: PictureTag.gravity(@media)
|
96
109
|
)
|
97
110
|
end
|
98
111
|
end
|
@@ -2,6 +2,24 @@ module PictureTag
|
|
2
2
|
# This is a little module to hold logic that doesn't fit other places. If it
|
3
3
|
# starts getting big, refactor.
|
4
4
|
module Utils
|
5
|
+
# These are valid ImageMagick gravity arguments (relevant to our use
|
6
|
+
# case):
|
7
|
+
GRAVITIES =
|
8
|
+
%w[center
|
9
|
+
north
|
10
|
+
northeast
|
11
|
+
east
|
12
|
+
southeast
|
13
|
+
south
|
14
|
+
southwest
|
15
|
+
west
|
16
|
+
northwest].freeze
|
17
|
+
|
18
|
+
# This is an attempt to recognize valid imagemagick geometry arguments
|
19
|
+
# with regex. It only tries to match values which don't preserve aspect
|
20
|
+
# ratio, as they're the ones people might actually need here.
|
21
|
+
GEOMETRY_REGEX = /\A\d*%?[x:]?\d*[%!]?([+-]\d+){,2}\Z/i.freeze
|
22
|
+
|
5
23
|
class << self
|
6
24
|
# Configure Jekyll to keep our generated files
|
7
25
|
def keep_files
|
data/readme.md
CHANGED
@@ -1,21 +1,20 @@
|
|
1
1
|
# Jekyll Picture Tag
|
2
2
|
|
3
|
-
**
|
3
|
+
**Responsive Images done correctly.**
|
4
4
|
|
5
|
-
It's
|
6
|
-
|
7
|
-
automated.
|
5
|
+
It's simple to throw a photo on a page and call it a day, but doing justice to users on all
|
6
|
+
different browsers and devices is tedious and tricky. Tedious, tricky things should be automated.
|
8
7
|
|
9
|
-
Jekyll Picture Tag
|
10
|
-
|
11
|
-
|
12
|
-
|
8
|
+
Jekyll Picture Tag automatically builds cropped, resized, and reformatted images, builds several
|
9
|
+
kinds of markup, offers extensive configuration while requiring none, and solves both the art
|
10
|
+
direction and resolution switching problems with a little YAML configuration and a simple template
|
11
|
+
tag.
|
13
12
|
|
14
13
|
## Why use Responsive Images?
|
15
14
|
|
16
|
-
**Performance:** The fastest sites are static sites, but
|
17
|
-
the top of a blog post you
|
18
|
-
|
15
|
+
**Performance:** The fastest sites are static sites, but if you plonk a 2mb picture of your dog at
|
16
|
+
the top of a blog post you throw it all away. Responsive images allow you to keep your site fast,
|
17
|
+
without compromising image quality.
|
19
18
|
|
20
19
|
**Design:** Your desktop image may not work well on mobile, regardless of its resolution. We often
|
21
20
|
want to do more than just resize images for different screen sizes, we want to crop them or use a
|
@@ -24,15 +23,35 @@ different image entirely.
|
|
24
23
|
## Why use Jekyll Picture Tag?
|
25
24
|
|
26
25
|
**Developer Sanity:** If you want to serve multiple images in multiple formats and resolutions, you
|
27
|
-
have a litany of markup to write and a big pile of images to generate. Jekyll Picture
|
28
|
-
responsive images minion - give it simple instructions and it'll handle the rest.
|
26
|
+
have a litany of markup to write and a big pile of images to generate and organize. Jekyll Picture
|
27
|
+
Tag is your responsive images minion - give it simple instructions and it'll handle the rest.
|
29
28
|
|
30
29
|
## Features
|
31
30
|
|
32
|
-
*
|
33
|
-
*
|
34
|
-
*
|
31
|
+
* Generate piles of cropped, resized, and converted image files.
|
32
|
+
* Generate corresponding markup in several different formats.
|
33
|
+
* Configure it easily, or not at all.
|
34
|
+
* Make Lighthouse happy.
|
35
35
|
|
36
36
|
## Documentation:
|
37
37
|
|
38
38
|
https://rbuchberger.github.io/jekyll_picture_tag/
|
39
|
+
|
40
|
+
## Changelog:
|
41
|
+
|
42
|
+
https://rbuchberger.github.io/jekyll_picture_tag/releases
|
43
|
+
|
44
|
+
Latest version:
|
45
|
+
|
46
|
+
* 1.10.0 May 11, 2020
|
47
|
+
* **Image Cropping support!** access the power of ImageMagick's `crop` function.
|
48
|
+
* Don't issue a warning when `default` preset is not found.
|
49
|
+
* Documentation improvements
|
50
|
+
|
51
|
+
## Help Wanted
|
52
|
+
|
53
|
+
Writing code is only part of the job; often the harder part is knowing what needs to be changed. Any
|
54
|
+
and all feedback is greatly appreciated, especially in regards to documentation. What are your pain
|
55
|
+
points? See the [contributing
|
56
|
+
guidelines](https://rbuchberger.github.io/jekyll_picture_tag/contributing), or the
|
57
|
+
[issues](https://github.com/rbuchberger/jekyll_picture_tag/issues) page for more.
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jekyll_picture_tag
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Wierzbowski
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2020-
|
13
|
+
date: 2020-05-11 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: bundler
|
@@ -152,6 +152,20 @@ dependencies:
|
|
152
152
|
- - "~>"
|
153
153
|
- !ruby/object:Gem::Version
|
154
154
|
version: '2.6'
|
155
|
+
- !ruby/object:Gem::Dependency
|
156
|
+
name: base32
|
157
|
+
requirement: !ruby/object:Gem::Requirement
|
158
|
+
requirements:
|
159
|
+
- - "~>"
|
160
|
+
- !ruby/object:Gem::Version
|
161
|
+
version: '0.3'
|
162
|
+
type: :runtime
|
163
|
+
prerelease: false
|
164
|
+
version_requirements: !ruby/object:Gem::Requirement
|
165
|
+
requirements:
|
166
|
+
- - "~>"
|
167
|
+
- !ruby/object:Gem::Version
|
168
|
+
version: '0.3'
|
155
169
|
- !ruby/object:Gem::Dependency
|
156
170
|
name: mime-types
|
157
171
|
requirement: !ruby/object:Gem::Requirement
|
@@ -186,14 +200,14 @@ dependencies:
|
|
186
200
|
requirements:
|
187
201
|
- - "~>"
|
188
202
|
- !ruby/object:Gem::Version
|
189
|
-
version: 1.1.
|
203
|
+
version: 1.1.2
|
190
204
|
type: :runtime
|
191
205
|
prerelease: false
|
192
206
|
version_requirements: !ruby/object:Gem::Requirement
|
193
207
|
requirements:
|
194
208
|
- - "~>"
|
195
209
|
- !ruby/object:Gem::Version
|
196
|
-
version: 1.1.
|
210
|
+
version: 1.1.2
|
197
211
|
- !ruby/object:Gem::Dependency
|
198
212
|
name: jekyll
|
199
213
|
requirement: !ruby/object:Gem::Requirement
|
@@ -225,6 +239,7 @@ files:
|
|
225
239
|
- ".ruby-version"
|
226
240
|
- ".solargraph.yml"
|
227
241
|
- ".travis.yml"
|
242
|
+
- Dockerfile
|
228
243
|
- Gemfile
|
229
244
|
- LICENSE.txt
|
230
245
|
- Rakefile
|
@@ -253,6 +268,7 @@ files:
|
|
253
268
|
- lib/jekyll_picture_tag/generated_image.rb
|
254
269
|
- lib/jekyll_picture_tag/img_uri.rb
|
255
270
|
- lib/jekyll_picture_tag/instructions.rb
|
271
|
+
- lib/jekyll_picture_tag/instructions/arg_splitter.rb
|
256
272
|
- lib/jekyll_picture_tag/instructions/configuration.rb
|
257
273
|
- lib/jekyll_picture_tag/instructions/html_attributes.rb
|
258
274
|
- lib/jekyll_picture_tag/instructions/preset.rb
|
@@ -301,7 +317,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
301
317
|
- !ruby/object:Gem::Version
|
302
318
|
version: '0'
|
303
319
|
requirements: []
|
304
|
-
rubygems_version: 3.
|
320
|
+
rubygems_version: 3.1.3
|
305
321
|
signing_key:
|
306
322
|
specification_version: 4
|
307
323
|
summary: Easy responsive images for Jekyll.
|