jekyll_picture_tag 1.7.0 → 1.10.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/.solargraph.yml +15 -0
- data/.travis.yml +4 -7
- data/Dockerfile +9 -0
- data/docs/Gemfile +4 -0
- data/docs/Gemfile.lock +249 -0
- data/docs/_config.yml +13 -0
- data/docs/_layouts/directory.html +32 -0
- data/docs/assets/style.css +31 -0
- data/{contributing.md → docs/contributing.md} +57 -15
- data/docs/{examples/_data/picture.yml → example_presets.md} +36 -25
- data/docs/global_configuration.md +61 -3
- data/docs/index.md +114 -0
- data/docs/installation.md +36 -21
- data/docs/migration.md +4 -0
- data/docs/notes.md +64 -58
- data/docs/output.md +63 -0
- data/docs/presets.md +175 -221
- data/docs/releases.md +64 -0
- data/docs/usage.md +91 -79
- data/jekyll_picture_tag.gemspec +3 -1
- data/lib/jekyll_picture_tag.rb +27 -10
- data/lib/jekyll_picture_tag/defaults/global.yml +2 -0
- data/lib/jekyll_picture_tag/defaults/presets.yml +2 -0
- data/lib/jekyll_picture_tag/generated_image.rb +105 -19
- 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/configuration.rb +47 -11
- data/lib/jekyll_picture_tag/instructions/html_attributes.rb +14 -8
- 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 +35 -6
- data/lib/jekyll_picture_tag/output_formats/data_attributes.rb +4 -1
- data/lib/jekyll_picture_tag/router.rb +16 -0
- data/lib/jekyll_picture_tag/source_image.rb +6 -1
- data/lib/jekyll_picture_tag/srcsets/basic.rb +45 -19
- data/lib/jekyll_picture_tag/srcsets/pixel_ratio.rb +1 -3
- data/lib/jekyll_picture_tag/srcsets/width.rb +1 -1
- data/lib/jekyll_picture_tag/utils.rb +18 -0
- data/lib/jekyll_picture_tag/version.rb +1 -1
- data/readme.md +43 -200
- metadata +49 -13
- data/docs/examples/_config.yml +0 -10
- data/docs/examples/post.md +0 -46
- data/docs/readme.md +0 -23
@@ -0,0 +1,68 @@
|
|
1
|
+
module PictureTag
|
2
|
+
module Instructions
|
3
|
+
# This class takes in the arguments passed to the liquid tag, and splits it
|
4
|
+
# up into 'words' (correctly handling quotes and backslash escapes.)
|
5
|
+
#
|
6
|
+
# To handle quotes and backslash escaping, we have to parse the string by
|
7
|
+
# characters to break it up correctly. I'm sure there's a library to do
|
8
|
+
# this, but it's not that much code honestly. If this starts getting big,
|
9
|
+
# we'll pull in a new dependency.
|
10
|
+
#
|
11
|
+
class ArgSplitter
|
12
|
+
attr_reader :words
|
13
|
+
|
14
|
+
def initialize(raw_params)
|
15
|
+
@words = []
|
16
|
+
@word = ''
|
17
|
+
@in_quotes = false
|
18
|
+
@escaped = false
|
19
|
+
|
20
|
+
raw_params.each_char { |c| handle_char(c) }
|
21
|
+
|
22
|
+
add_word # We have to explicitly add the last one.
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def handle_char(char)
|
28
|
+
# last character was a backslash:
|
29
|
+
if @escaped
|
30
|
+
close_escape char
|
31
|
+
|
32
|
+
# char is a backslash or a quote:
|
33
|
+
elsif char.match?(/["\\]/)
|
34
|
+
handle_special char
|
35
|
+
|
36
|
+
# Character isn't whitespace, or it's inside double quotes:
|
37
|
+
elsif @in_quotes || char.match(/\S/)
|
38
|
+
@word << char
|
39
|
+
|
40
|
+
# Character is whitespace outside of double quotes:
|
41
|
+
else
|
42
|
+
add_word
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def add_word
|
47
|
+
return if @word.empty?
|
48
|
+
|
49
|
+
@words << @word
|
50
|
+
@word = ''
|
51
|
+
end
|
52
|
+
|
53
|
+
def handle_special(char)
|
54
|
+
if char == '\\'
|
55
|
+
@escaped = true
|
56
|
+
elsif char == '"'
|
57
|
+
@in_quotes = !@in_quotes
|
58
|
+
@word << char
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def close_escape(char)
|
63
|
+
@word << char
|
64
|
+
@escaped = false
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -30,7 +30,7 @@ module PictureTag
|
|
30
30
|
# source_dest is the jekyll-picture-tag destination directory. (generated
|
31
31
|
# file location setting.)
|
32
32
|
def dest_dir
|
33
|
-
File.join PictureTag.site.
|
33
|
+
File.join PictureTag.site.config['destination'], pconfig['output']
|
34
34
|
end
|
35
35
|
|
36
36
|
def nomarkdown?
|
@@ -38,28 +38,64 @@ module PictureTag
|
|
38
38
|
end
|
39
39
|
|
40
40
|
def continue_on_missing?
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
setting
|
50
|
-
end
|
41
|
+
env_check pconfig['ignore_missing_images']
|
42
|
+
rescue ArgumentError
|
43
|
+
raise ArgumentError,
|
44
|
+
<<~HEREDOC
|
45
|
+
continue_on_missing setting invalid. Must be either a boolean
|
46
|
+
(true/false), an environment name, or an array of environment
|
47
|
+
names.
|
48
|
+
HEREDOC
|
51
49
|
end
|
52
50
|
|
53
51
|
def cdn?
|
54
52
|
pconfig['cdn_url'] && pconfig['cdn_environments'].include?(jekyll_env)
|
55
53
|
end
|
56
54
|
|
55
|
+
def disabled?
|
56
|
+
env_check pconfig['disabled']
|
57
|
+
rescue ArgumentError
|
58
|
+
raise ArgumentError,
|
59
|
+
<<~HEREDOC
|
60
|
+
"disabled" setting invalid. Must be either a boolean
|
61
|
+
(true/false), an environment name, or an array of environment
|
62
|
+
names.
|
63
|
+
HEREDOC
|
64
|
+
end
|
65
|
+
|
66
|
+
def fast_build?
|
67
|
+
env_check pconfig['fast_build']
|
68
|
+
rescue ArgumentError
|
69
|
+
raise ArgumentError,
|
70
|
+
<<~HEREDOC
|
71
|
+
"fast_build" setting invalid. Must be either a boolean
|
72
|
+
(true/false), an environment name, or an array of environment
|
73
|
+
names.
|
74
|
+
HEREDOC
|
75
|
+
end
|
76
|
+
|
57
77
|
private
|
58
78
|
|
59
79
|
def content
|
60
80
|
@content ||= setting_merge(defaults, PictureTag.site.config)
|
61
81
|
end
|
62
82
|
|
83
|
+
# There are a few config settings which can either be booleans,
|
84
|
+
# environment names, or arrays of environment names. This method works it
|
85
|
+
# out and returns a boolean.
|
86
|
+
def env_check(setting)
|
87
|
+
if setting.is_a? Array
|
88
|
+
setting.include? jekyll_env
|
89
|
+
elsif setting.is_a? String
|
90
|
+
setting == jekyll_env
|
91
|
+
elsif [true, false].include? setting
|
92
|
+
setting
|
93
|
+
else
|
94
|
+
raise ArgumentError,
|
95
|
+
"#{setting} must either be a string, an array, or a boolean."
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
63
99
|
def setting_merge(default, jekyll)
|
64
100
|
jekyll.merge default do |_key, config_setting, default_setting|
|
65
101
|
if default_setting.respond_to? :merge
|
@@ -4,22 +4,28 @@ module PictureTag
|
|
4
4
|
# sent to various elements.
|
5
5
|
# Stored as a hash, with string keys.
|
6
6
|
class HTMLAttributeSet
|
7
|
-
# Initialize with leftovers passed into the liquid tag
|
7
|
+
# Initialize with leftovers passed into the liquid tag. These leftovers
|
8
|
+
# (params) take the form of an array of strings, called words, which the
|
9
|
+
# tag parser has separated.
|
8
10
|
def initialize(params)
|
9
|
-
@
|
11
|
+
@attributes = load_preset
|
10
12
|
|
11
13
|
parse_params(params) if params
|
12
14
|
handle_source_url
|
13
15
|
end
|
14
16
|
|
15
17
|
def [](key)
|
16
|
-
@
|
18
|
+
@attributes[key]
|
17
19
|
end
|
18
20
|
|
19
21
|
private
|
20
22
|
|
21
23
|
def load_preset
|
22
|
-
|
24
|
+
# Shamelessly stolen from stackoverflow. Deep cloning a hash is
|
25
|
+
# surprisingly tricky! I could pull in ActiveSupport and get
|
26
|
+
# Hash#deep_dup, but for now I don't think it's necessary.
|
27
|
+
|
28
|
+
Marshal.load(Marshal.dump(PictureTag.preset['attributes'])) || {}
|
23
29
|
end
|
24
30
|
|
25
31
|
# Syntax this function processes:
|
@@ -30,10 +36,10 @@ module PictureTag
|
|
30
36
|
words.each do |word|
|
31
37
|
if word.match(/^--/)
|
32
38
|
key = word.delete_prefix('--')
|
33
|
-
elsif @
|
34
|
-
@
|
39
|
+
elsif @attributes[key]
|
40
|
+
@attributes[key] << ' ' + word
|
35
41
|
else
|
36
|
-
@
|
42
|
+
@attributes[key] = word
|
37
43
|
end
|
38
44
|
end
|
39
45
|
end
|
@@ -43,7 +49,7 @@ module PictureTag
|
|
43
49
|
|
44
50
|
target = PictureTag.source_images.first.shortname
|
45
51
|
|
46
|
-
@
|
52
|
+
@attributes['link'] = ImgURI.new(target, source_image: true).to_s
|
47
53
|
end
|
48
54
|
end
|
49
55
|
end
|
@@ -12,13 +12,6 @@ module PictureTag
|
|
12
12
|
@content[key]
|
13
13
|
end
|
14
14
|
|
15
|
-
# Returns the set of widths to use for a given media query.
|
16
|
-
def widths(media)
|
17
|
-
width_hash = self['media_widths'] || {}
|
18
|
-
width_hash.default = self['widths']
|
19
|
-
width_hash[media]
|
20
|
-
end
|
21
|
-
|
22
15
|
def formats
|
23
16
|
@content['formats']
|
24
17
|
end
|
@@ -41,15 +34,38 @@ module PictureTag
|
|
41
34
|
end
|
42
35
|
end
|
43
36
|
|
37
|
+
# Image widths to generate for a given media query.
|
38
|
+
def widths(media = nil)
|
39
|
+
setting_lookup('widths', 'media', media)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Image quality setting, possibly dependent on format.
|
44
43
|
def quality(format = nil)
|
45
|
-
|
46
|
-
|
44
|
+
setting_lookup('quality', 'format', format)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Gravity setting (for imagemagick cropping)
|
48
|
+
def gravity(media = nil)
|
49
|
+
setting_lookup('gravity', 'media', media)
|
50
|
+
end
|
47
51
|
|
48
|
-
|
52
|
+
# Crop value
|
53
|
+
def crop(media = nil)
|
54
|
+
setting_lookup('crop', 'media', media)
|
49
55
|
end
|
50
56
|
|
51
57
|
private
|
52
58
|
|
59
|
+
# Return arbitrary setting values, taking their defaults into account.
|
60
|
+
# Ex: quality can be set for all image formats, or individually per
|
61
|
+
# format. Per-format settings should override the general setting.
|
62
|
+
def setting_lookup(setting, prefix, lookup)
|
63
|
+
media_values = @content[prefix + '_' + setting] || {}
|
64
|
+
media_values.default = @content[setting]
|
65
|
+
|
66
|
+
media_values[lookup]
|
67
|
+
end
|
68
|
+
|
53
69
|
def build_preset
|
54
70
|
# The _data/picture.yml file is optional.
|
55
71
|
picture_data_file = grab_data_file
|
@@ -70,10 +86,14 @@ module PictureTag
|
|
70
86
|
end
|
71
87
|
|
72
88
|
def no_preset
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
89
|
+
unless @name == 'default'
|
90
|
+
Utils.warning(
|
91
|
+
<<~HEREDOC
|
92
|
+
Preset "#{@name}" not found in {PictureTag.config['data_dir']}/picture.yml
|
93
|
+
under markup_presets key. Using default values."
|
94
|
+
HEREDOC
|
95
|
+
)
|
96
|
+
end
|
77
97
|
|
78
98
|
{}
|
79
99
|
end
|
@@ -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
|