jekyll_picture_tag 1.14.0 → 2.0.3
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/.envrc +2 -0
- data/.github/workflows/code-checks.yml +2 -12
- data/.rubocop.yml +2 -0
- data/.ruby-version +1 -1
- data/docs/devs/contributing/code.md +11 -3
- data/docs/devs/contributing/testing.md +0 -11
- data/docs/devs/releases.md +38 -2
- data/docs/index.md +43 -18
- data/docs/logo.png +0 -0
- data/docs/logo.svg +880 -0
- data/docs/users/deployment.md +49 -0
- data/docs/users/getting_started.md +55 -0
- data/docs/users/installation.md +18 -38
- data/docs/users/liquid_tag/argument_reference/crop.md +21 -36
- data/docs/users/liquid_tag/examples.md +13 -25
- data/docs/users/liquid_tag/index.md +1 -1
- data/docs/users/notes/{migration.md → migration_1.md} +1 -1
- data/docs/users/notes/migration_2.md +99 -0
- data/docs/users/presets/cropping.md +21 -22
- data/docs/users/presets/default.md +10 -2
- data/docs/users/presets/examples.md +77 -45
- data/docs/users/presets/fallback_image.md +1 -1
- data/docs/users/presets/html_attributes.md +1 -1
- data/docs/users/presets/image_formats.md +3 -3
- data/docs/users/presets/image_quality.md +71 -56
- data/docs/users/presets/index.md +19 -45
- data/docs/users/presets/link_source.md +1 -1
- data/docs/users/presets/media_queries.md +1 -1
- data/docs/users/presets/nomarkdown_override.md +1 -1
- data/docs/users/presets/pixel_ratio_srcsets.md +1 -1
- data/docs/users/presets/width_height_attributes.md +1 -1
- data/docs/users/presets/width_srcsets.md +61 -23
- data/docs/users/presets/writing_presets.md +65 -0
- data/docs/users/tutorial.md +97 -0
- data/jekyll_picture_tag.gemspec +33 -23
- data/lib/jekyll_picture_tag.rb +8 -6
- data/lib/jekyll_picture_tag/cache.rb +64 -3
- data/lib/jekyll_picture_tag/defaults/global.rb +18 -0
- data/lib/jekyll_picture_tag/defaults/presets.rb +57 -0
- data/lib/jekyll_picture_tag/images.rb +1 -0
- data/lib/jekyll_picture_tag/images/generated_image.rb +25 -63
- data/lib/jekyll_picture_tag/images/image_file.rb +105 -0
- data/lib/jekyll_picture_tag/images/img_uri.rb +3 -12
- data/lib/jekyll_picture_tag/images/source_image.rb +44 -9
- data/lib/jekyll_picture_tag/instructions.rb +70 -6
- data/lib/jekyll_picture_tag/instructions/children/config.rb +128 -0
- data/lib/jekyll_picture_tag/instructions/children/context.rb +24 -0
- data/lib/jekyll_picture_tag/instructions/children/params.rb +90 -0
- data/lib/jekyll_picture_tag/instructions/children/parsers.rb +48 -0
- data/lib/jekyll_picture_tag/instructions/children/preset.rb +182 -0
- data/lib/jekyll_picture_tag/instructions/parents/conditional_instruction.rb +69 -0
- data/lib/jekyll_picture_tag/instructions/parents/env_instruction.rb +29 -0
- data/lib/jekyll_picture_tag/output_formats/basic.rb +5 -17
- data/lib/jekyll_picture_tag/parsers.rb +6 -0
- data/lib/jekyll_picture_tag/{instructions → parsers}/arg_splitter.rb +1 -1
- data/lib/jekyll_picture_tag/parsers/configuration.rb +28 -0
- data/lib/jekyll_picture_tag/{instructions → parsers}/html_attributes.rb +1 -1
- data/lib/jekyll_picture_tag/parsers/image_backend.rb +46 -0
- data/lib/jekyll_picture_tag/parsers/preset.rb +43 -0
- data/lib/jekyll_picture_tag/{instructions → parsers}/tag_parser.rb +15 -12
- data/lib/jekyll_picture_tag/router.rb +35 -93
- data/lib/jekyll_picture_tag/srcsets/basic.rb +4 -10
- data/lib/jekyll_picture_tag/utils.rb +10 -20
- data/lib/jekyll_picture_tag/version.rb +1 -1
- data/readme.md +38 -0
- metadata +126 -105
- data/Dockerfile +0 -9
- data/docs/users/notes/input_checking.md +0 -6
- data/docs/users/presets/strip_metadata.md +0 -13
- data/install_imagemagick.sh +0 -23
- data/jekyll-picture-tag.gemspec +0 -52
- data/lib/jekyll-picture-tag.rb +0 -25
- data/lib/jekyll_picture_tag/cache/base.rb +0 -61
- data/lib/jekyll_picture_tag/cache/generated.rb +0 -20
- data/lib/jekyll_picture_tag/cache/source.rb +0 -19
- data/lib/jekyll_picture_tag/defaults/global.yml +0 -13
- data/lib/jekyll_picture_tag/defaults/presets.yml +0 -12
- data/lib/jekyll_picture_tag/instructions/configuration.rb +0 -121
- data/lib/jekyll_picture_tag/instructions/preset.rb +0 -122
- data/lib/jekyll_picture_tag/instructions/set.rb +0 -75
@@ -0,0 +1,69 @@
|
|
1
|
+
module PictureTag
|
2
|
+
module Instructions
|
3
|
+
# Many inputs take a common format: a generic setting which applies all of
|
4
|
+
# the time, or more specific versions of that setting for specific
|
5
|
+
# circumstances. For example, quality can be set globally, or per image
|
6
|
+
# format. This instruction class handles those cases.
|
7
|
+
#
|
8
|
+
# To use, you must at minimum define setting_basename, setting_prefix, and
|
9
|
+
# add to the acceptable_types (or write your own validation).
|
10
|
+
class ConditionalInstruction < Instruction
|
11
|
+
def value(*args)
|
12
|
+
coerce(*args)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def setting_basename
|
18
|
+
raise NotImplementedError
|
19
|
+
end
|
20
|
+
|
21
|
+
# Special condition for setting; media, crop, etc
|
22
|
+
def setting_prefix
|
23
|
+
raise NotImplementedError
|
24
|
+
end
|
25
|
+
|
26
|
+
def acceptable_types
|
27
|
+
[NilClass]
|
28
|
+
end
|
29
|
+
|
30
|
+
def coerce(arg)
|
31
|
+
raise ArgumentError unless valid?
|
32
|
+
|
33
|
+
value_hash[arg]
|
34
|
+
end
|
35
|
+
|
36
|
+
def source
|
37
|
+
{
|
38
|
+
hash: PictureTag.preset[setting_prefix + '_' + setting_name],
|
39
|
+
default: PictureTag.preset[setting_name]
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
def value_hash
|
44
|
+
vals = source[:hash] || {}
|
45
|
+
vals.default = source[:default]
|
46
|
+
|
47
|
+
vals
|
48
|
+
end
|
49
|
+
|
50
|
+
def valid?
|
51
|
+
valid_hash? && valid_default?
|
52
|
+
end
|
53
|
+
|
54
|
+
def acceptable_type?(value)
|
55
|
+
acceptable_types.any? { |type| value.is_a? type }
|
56
|
+
end
|
57
|
+
|
58
|
+
def valid_hash?
|
59
|
+
source[:hash].nil? || source[:hash].values.all? do |v|
|
60
|
+
acceptable_type?(v)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def valid_default?
|
65
|
+
acceptable_type? source[:default]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module PictureTag
|
2
|
+
module Instructions
|
3
|
+
# There are a few config settings which are environment dependent, and can
|
4
|
+
# either be booleans, environment names, or arrays of environment names.
|
5
|
+
# This class works it out and returns a boolean.
|
6
|
+
class EnvInstruction < Instruction
|
7
|
+
private
|
8
|
+
|
9
|
+
def coerce
|
10
|
+
get_bool(source)
|
11
|
+
end
|
12
|
+
|
13
|
+
def get_bool(value)
|
14
|
+
case value
|
15
|
+
when true, false, nil then value
|
16
|
+
when String then value == PictureTag.jekyll_env
|
17
|
+
when Array then value.include? PictureTag.jekyll_env
|
18
|
+
when Hash then get_bool(value[:setting])
|
19
|
+
else raise ArgumentError, error_message
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def error_message
|
24
|
+
"JPT - #{setting_name} must be a boolean, an environment name," \
|
25
|
+
' or an array of environment names.'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -76,9 +76,7 @@ module PictureTag
|
|
76
76
|
image = GeneratedImage.new(
|
77
77
|
source_file: PictureTag.source_images.first,
|
78
78
|
format: PictureTag.fallback_format,
|
79
|
-
width: checked_fallback_width
|
80
|
-
crop: PictureTag.crop,
|
81
|
-
gravity: PictureTag.gravity
|
79
|
+
width: checked_fallback_width
|
82
80
|
)
|
83
81
|
|
84
82
|
image.generate
|
@@ -92,9 +90,7 @@ module PictureTag
|
|
92
90
|
@fallback_candidate ||= GeneratedImage.new(
|
93
91
|
source_file: PictureTag.source_images.first,
|
94
92
|
format: PictureTag.fallback_format,
|
95
|
-
width: PictureTag.fallback_width
|
96
|
-
crop: PictureTag.crop,
|
97
|
-
gravity: PictureTag.gravity
|
93
|
+
width: PictureTag.fallback_width
|
98
94
|
)
|
99
95
|
end
|
100
96
|
|
@@ -119,22 +115,14 @@ module PictureTag
|
|
119
115
|
PictureTag.source_images.first
|
120
116
|
end
|
121
117
|
|
122
|
-
def source_width
|
123
|
-
if PictureTag.crop
|
124
|
-
fallback_candidate.source_width
|
125
|
-
else
|
126
|
-
source.width
|
127
|
-
end
|
128
|
-
end
|
129
|
-
|
130
118
|
def checked_fallback_width
|
131
119
|
target = PictureTag.fallback_width
|
132
120
|
|
133
|
-
if target >
|
121
|
+
if target > source.width
|
134
122
|
Utils.warning "#{source.shortname} is smaller than the " \
|
135
|
-
"requested fallback width of #{target}px. Using #{
|
123
|
+
"requested fallback width of #{target}px. Using #{source.width}" \
|
136
124
|
' px instead.'
|
137
|
-
|
125
|
+
source.width
|
138
126
|
else
|
139
127
|
target
|
140
128
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module PictureTag
|
2
|
+
module Parsers
|
3
|
+
# Global config (big picture). loads jekyll data/config files, and the j-p-t
|
4
|
+
# defaults from included yml files.
|
5
|
+
class Configuration
|
6
|
+
# returns jekyll's configuration (picture is a subset)
|
7
|
+
def [](key)
|
8
|
+
content[key]
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def content
|
14
|
+
@content ||= setting_merge(DEFAULT_CONFIG, PictureTag.site.config)
|
15
|
+
end
|
16
|
+
|
17
|
+
def setting_merge(default, jekyll)
|
18
|
+
jekyll.merge default do |_key, config_setting, default_setting|
|
19
|
+
if default_setting.respond_to? :merge
|
20
|
+
setting_merge(default_setting, config_setting)
|
21
|
+
else
|
22
|
+
config_setting
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module PictureTag
|
2
|
+
module Parsers
|
3
|
+
# Returns information regarding image handlers
|
4
|
+
class ImageBackend
|
5
|
+
def handler_for(format)
|
6
|
+
if (vips_formats & all_names(format)).any?
|
7
|
+
:vips
|
8
|
+
elsif (magick_formats & all_names(format)).any?
|
9
|
+
:magick
|
10
|
+
else
|
11
|
+
raise "No support for generating #{format} files in this environment."
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns array of formats that vips can save to
|
16
|
+
def vips_formats
|
17
|
+
@vips_formats ||= `vips -l filesave`
|
18
|
+
.scan(/\.[a-z]{1,5}/)
|
19
|
+
.uniq
|
20
|
+
.map { |format| format.strip.delete_prefix('.') }
|
21
|
+
end
|
22
|
+
|
23
|
+
# Returns an array of formats that imagemagick can handle.
|
24
|
+
def magick_formats
|
25
|
+
@magick_formats ||= `convert -version`
|
26
|
+
.split("\n")
|
27
|
+
.last
|
28
|
+
.delete_prefix('Delegates (built-in):')
|
29
|
+
.split
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns an array of all known names of a format, for the purposes of
|
33
|
+
# parsing supported output formats.
|
34
|
+
def all_names(format)
|
35
|
+
alts = alternates.select { |a| a.include? format }.flatten
|
36
|
+
alts.any? ? alts : [format]
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def alternates
|
42
|
+
[%w[jpg jpeg], %w[avif heic heif]]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module PictureTag
|
2
|
+
module Parsers
|
3
|
+
# Handles the specific tag image set to construct.
|
4
|
+
class Preset
|
5
|
+
attr_reader :name
|
6
|
+
|
7
|
+
def initialize(name)
|
8
|
+
@name = name
|
9
|
+
end
|
10
|
+
|
11
|
+
def [](key)
|
12
|
+
content[key]
|
13
|
+
end
|
14
|
+
|
15
|
+
protected
|
16
|
+
|
17
|
+
def content
|
18
|
+
@content ||= DEFAULT_PRESET.merge settings
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def settings
|
24
|
+
PictureTag.site.data.dig('picture', 'presets', name) ||
|
25
|
+
STOCK_PRESETS[name] ||
|
26
|
+
no_preset
|
27
|
+
end
|
28
|
+
|
29
|
+
def no_preset
|
30
|
+
unless name == 'default'
|
31
|
+
Utils.warning(
|
32
|
+
<<~HEREDOC
|
33
|
+
Preset "#{name}" not found in #{PictureTag.config['data_dir']}/picture.yml
|
34
|
+
under 'presets' key, or in stock presets. Using default values."
|
35
|
+
HEREDOC
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
39
|
+
{}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module PictureTag
|
2
|
-
module
|
2
|
+
module Parsers
|
3
3
|
# Tag Parsing Responsibilities:
|
4
4
|
#
|
5
5
|
# {% picture mypreset a.jpg 3:2 mobile: b.jpg --alt "Alt" --link "/" %}
|
@@ -9,17 +9,16 @@ module PictureTag
|
|
9
9
|
# string), hands them to ArgSplitter (which breaks them up into an array of
|
10
10
|
# words), extracts the preset name (if present), source image name(s),
|
11
11
|
# associated media queries (if present), and image-related arguments such as
|
12
|
-
# crop and
|
12
|
+
# crop and keep. HTML attributes are handed off to its respective class
|
13
13
|
# (as 'leftovers')
|
14
14
|
#
|
15
15
|
# Media presets and source names are stored as arrays in their correct
|
16
|
-
# orders.
|
16
|
+
# orders. Crop settings are stored in a hash, keyed by their
|
17
17
|
# relevant media presets. Note that the base image will have a media preset
|
18
18
|
# of nil, which is a perfectly fine hash key.
|
19
|
-
#
|
20
19
|
class TagParser
|
21
|
-
attr_reader :preset_name, :source_names, :media_presets, :
|
22
|
-
:
|
20
|
+
attr_reader :preset_name, :source_names, :media_presets, :keep,
|
21
|
+
:crop, :leftovers
|
23
22
|
|
24
23
|
def initialize(raw_params)
|
25
24
|
@raw_params = raw_params
|
@@ -27,8 +26,8 @@ module PictureTag
|
|
27
26
|
|
28
27
|
@media_presets = []
|
29
28
|
@source_names = []
|
30
|
-
@
|
31
|
-
@
|
29
|
+
@keep = {}
|
30
|
+
@crop = {}
|
32
31
|
|
33
32
|
parse_params
|
34
33
|
end
|
@@ -51,14 +50,18 @@ module PictureTag
|
|
51
50
|
end
|
52
51
|
|
53
52
|
def parse_param(param)
|
53
|
+
# Media query, i.e. 'mobile:'
|
54
54
|
if param.match?(/[\w\-]+:$/)
|
55
55
|
add_media_source
|
56
56
|
|
57
|
-
|
58
|
-
|
57
|
+
# Smartcrop interestingness setting. We label it 'keep', since it
|
58
|
+
# determines what to keep when cropping.
|
59
|
+
elsif %w[none centre center entropy attention].include?(param.downcase)
|
60
|
+
@keep[@media_presets.last] = @params.shift
|
59
61
|
|
60
|
-
|
61
|
-
|
62
|
+
# Aspect ratio, i.e. '16:9'
|
63
|
+
elsif param.match?(/\A\d+:\d+\z/)
|
64
|
+
@crop[@media_presets.last] = @params.shift
|
62
65
|
|
63
66
|
else
|
64
67
|
raise_error(param)
|
@@ -1,115 +1,57 @@
|
|
1
1
|
module PictureTag
|
2
2
|
# The rest of the application doesn't care where the instruction logic
|
3
|
-
# resides.
|
4
|
-
#
|
5
|
-
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
3
|
+
# resides. This module 'routes' method calls to the right place, so
|
4
|
+
# information consumers can just call 'PictureTag.(some method)'
|
5
|
+
#
|
6
|
+
# This is accomplished with a bit of metaprogramming, which is hopefully not
|
7
|
+
# unnecessarily clever or complicated. Missing methods are converted to class
|
8
|
+
# names, which are looked up under the Instructions module namespace.
|
9
|
+
#
|
10
|
+
# Instantiated Instructions are stored in a hash, keyed by method name.
|
10
11
|
module Router
|
11
|
-
|
12
|
-
|
13
|
-
# Context forwarding
|
14
|
-
|
15
|
-
# Global site data
|
16
|
-
def site
|
17
|
-
@context.registers[:site]
|
18
|
-
end
|
19
|
-
|
20
|
-
# Page which tag is called from
|
21
|
-
def page
|
22
|
-
@context.registers[:page]
|
23
|
-
end
|
24
|
-
|
25
|
-
# Instructions forwarding
|
26
|
-
|
27
|
-
def config
|
28
|
-
@instructions.config
|
29
|
-
end
|
30
|
-
|
31
|
-
def preset
|
32
|
-
@instructions.preset
|
33
|
-
end
|
34
|
-
|
35
|
-
def media_presets
|
36
|
-
@instructions.media_presets
|
37
|
-
end
|
38
|
-
|
39
|
-
def html_attributes
|
40
|
-
@instructions.html_attributes
|
41
|
-
end
|
42
|
-
|
43
|
-
def output_class
|
44
|
-
@instructions.output_class
|
45
|
-
end
|
12
|
+
# These two attributes encompass everything passed in by Jekyll.
|
13
|
+
attr_accessor :raw_params, :context
|
46
14
|
|
47
|
-
def
|
48
|
-
|
15
|
+
def method_missing(method_name, *args)
|
16
|
+
if instruction_exists?(method_name)
|
17
|
+
instruction(method_name).value(*args)
|
18
|
+
else
|
19
|
+
super
|
20
|
+
end
|
49
21
|
end
|
50
22
|
|
51
|
-
def
|
52
|
-
|
23
|
+
def respond_to_missing?(method_name, *args)
|
24
|
+
instruction_exists?(method_name) || super
|
53
25
|
end
|
54
26
|
|
55
|
-
|
56
|
-
|
27
|
+
# Required at least for testing; instructions are persisted between tags
|
28
|
+
# otherwise.
|
29
|
+
def clear_instructions
|
30
|
+
instructions.clear
|
57
31
|
end
|
58
32
|
|
59
|
-
|
60
|
-
|
61
|
-
def source_dir
|
62
|
-
config.source_dir
|
63
|
-
end
|
64
|
-
|
65
|
-
def dest_dir
|
66
|
-
config.dest_dir
|
67
|
-
end
|
68
|
-
|
69
|
-
def continue_on_missing?
|
70
|
-
config.continue_on_missing?
|
71
|
-
end
|
72
|
-
|
73
|
-
def cdn?
|
74
|
-
config.cdn?
|
75
|
-
end
|
76
|
-
|
77
|
-
def pconfig
|
78
|
-
config.pconfig
|
79
|
-
end
|
80
|
-
|
81
|
-
def disabled?
|
82
|
-
config.disabled?
|
83
|
-
end
|
84
|
-
|
85
|
-
def fast_build?
|
86
|
-
config.fast_build?
|
87
|
-
end
|
88
|
-
|
89
|
-
# Preset forwarding
|
90
|
-
|
91
|
-
def widths(media)
|
92
|
-
preset.widths(media)
|
93
|
-
end
|
33
|
+
private
|
94
34
|
|
95
|
-
def
|
96
|
-
|
35
|
+
def instruction(method_name)
|
36
|
+
instructions[method_name] ||= instruction_class(method_name).new
|
97
37
|
end
|
98
38
|
|
99
|
-
def
|
100
|
-
|
39
|
+
def instructions
|
40
|
+
@instructions ||= {}
|
101
41
|
end
|
102
42
|
|
103
|
-
def
|
104
|
-
|
43
|
+
def instruction_exists?(method_name)
|
44
|
+
Object.const_defined? instruction_class_name(method_name.to_sym)
|
105
45
|
end
|
106
46
|
|
107
|
-
|
108
|
-
|
47
|
+
# Class names can't contain question marks, so we strip them.
|
48
|
+
def instruction_class(method_name)
|
49
|
+
Object.const_get instruction_class_name(method_name)
|
109
50
|
end
|
110
51
|
|
111
|
-
def
|
112
|
-
|
52
|
+
def instruction_class_name(method_name)
|
53
|
+
'PictureTag::Instructions::' +
|
54
|
+
Utils.titleize(method_name.to_s.delete_suffix('?'))
|
113
55
|
end
|
114
56
|
end
|
115
57
|
end
|