jekyll_picture_tag 1.14.0 → 2.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/.envrc +2 -0
  3. data/.github/workflows/code-checks.yml +2 -12
  4. data/.rubocop.yml +2 -0
  5. data/.ruby-version +1 -1
  6. data/docs/devs/contributing/code.md +11 -3
  7. data/docs/devs/contributing/testing.md +0 -11
  8. data/docs/devs/releases.md +38 -2
  9. data/docs/index.md +43 -18
  10. data/docs/logo.png +0 -0
  11. data/docs/logo.svg +880 -0
  12. data/docs/users/deployment.md +49 -0
  13. data/docs/users/getting_started.md +55 -0
  14. data/docs/users/installation.md +18 -38
  15. data/docs/users/liquid_tag/argument_reference/crop.md +21 -36
  16. data/docs/users/liquid_tag/examples.md +13 -25
  17. data/docs/users/liquid_tag/index.md +1 -1
  18. data/docs/users/notes/{migration.md → migration_1.md} +1 -1
  19. data/docs/users/notes/migration_2.md +99 -0
  20. data/docs/users/presets/cropping.md +21 -22
  21. data/docs/users/presets/default.md +10 -2
  22. data/docs/users/presets/examples.md +77 -45
  23. data/docs/users/presets/fallback_image.md +1 -1
  24. data/docs/users/presets/html_attributes.md +1 -1
  25. data/docs/users/presets/image_formats.md +3 -3
  26. data/docs/users/presets/image_quality.md +71 -56
  27. data/docs/users/presets/index.md +19 -45
  28. data/docs/users/presets/link_source.md +1 -1
  29. data/docs/users/presets/media_queries.md +1 -1
  30. data/docs/users/presets/nomarkdown_override.md +1 -1
  31. data/docs/users/presets/pixel_ratio_srcsets.md +1 -1
  32. data/docs/users/presets/width_height_attributes.md +1 -1
  33. data/docs/users/presets/width_srcsets.md +61 -23
  34. data/docs/users/presets/writing_presets.md +65 -0
  35. data/docs/users/tutorial.md +97 -0
  36. data/jekyll_picture_tag.gemspec +33 -23
  37. data/lib/jekyll_picture_tag.rb +8 -6
  38. data/lib/jekyll_picture_tag/cache.rb +64 -3
  39. data/lib/jekyll_picture_tag/defaults/global.rb +18 -0
  40. data/lib/jekyll_picture_tag/defaults/presets.rb +57 -0
  41. data/lib/jekyll_picture_tag/images.rb +1 -0
  42. data/lib/jekyll_picture_tag/images/generated_image.rb +25 -63
  43. data/lib/jekyll_picture_tag/images/image_file.rb +105 -0
  44. data/lib/jekyll_picture_tag/images/img_uri.rb +3 -12
  45. data/lib/jekyll_picture_tag/images/source_image.rb +44 -9
  46. data/lib/jekyll_picture_tag/instructions.rb +70 -6
  47. data/lib/jekyll_picture_tag/instructions/children/config.rb +128 -0
  48. data/lib/jekyll_picture_tag/instructions/children/context.rb +24 -0
  49. data/lib/jekyll_picture_tag/instructions/children/params.rb +90 -0
  50. data/lib/jekyll_picture_tag/instructions/children/parsers.rb +48 -0
  51. data/lib/jekyll_picture_tag/instructions/children/preset.rb +182 -0
  52. data/lib/jekyll_picture_tag/instructions/parents/conditional_instruction.rb +69 -0
  53. data/lib/jekyll_picture_tag/instructions/parents/env_instruction.rb +29 -0
  54. data/lib/jekyll_picture_tag/output_formats/basic.rb +5 -17
  55. data/lib/jekyll_picture_tag/parsers.rb +6 -0
  56. data/lib/jekyll_picture_tag/{instructions → parsers}/arg_splitter.rb +1 -1
  57. data/lib/jekyll_picture_tag/parsers/configuration.rb +28 -0
  58. data/lib/jekyll_picture_tag/{instructions → parsers}/html_attributes.rb +1 -1
  59. data/lib/jekyll_picture_tag/parsers/image_backend.rb +46 -0
  60. data/lib/jekyll_picture_tag/parsers/preset.rb +43 -0
  61. data/lib/jekyll_picture_tag/{instructions → parsers}/tag_parser.rb +15 -12
  62. data/lib/jekyll_picture_tag/router.rb +35 -93
  63. data/lib/jekyll_picture_tag/srcsets/basic.rb +4 -10
  64. data/lib/jekyll_picture_tag/utils.rb +10 -20
  65. data/lib/jekyll_picture_tag/version.rb +1 -1
  66. data/readme.md +38 -0
  67. metadata +126 -105
  68. data/Dockerfile +0 -9
  69. data/docs/users/notes/input_checking.md +0 -6
  70. data/docs/users/presets/strip_metadata.md +0 -13
  71. data/install_imagemagick.sh +0 -23
  72. data/jekyll-picture-tag.gemspec +0 -52
  73. data/lib/jekyll-picture-tag.rb +0 -25
  74. data/lib/jekyll_picture_tag/cache/base.rb +0 -61
  75. data/lib/jekyll_picture_tag/cache/generated.rb +0 -20
  76. data/lib/jekyll_picture_tag/cache/source.rb +0 -19
  77. data/lib/jekyll_picture_tag/defaults/global.yml +0 -13
  78. data/lib/jekyll_picture_tag/defaults/presets.yml +0 -12
  79. data/lib/jekyll_picture_tag/instructions/configuration.rb +0 -121
  80. data/lib/jekyll_picture_tag/instructions/preset.rb +0 -122
  81. data/lib/jekyll_picture_tag/instructions/set.rb +0 -75
@@ -1,6 +1,70 @@
1
- require_relative './instructions/set'
2
- require_relative './instructions/configuration'
3
- require_relative './instructions/html_attributes'
4
- require_relative './instructions/preset'
5
- require_relative './instructions/tag_parser'
6
- require_relative './instructions/arg_splitter'
1
+ module PictureTag
2
+ # Instructions obtain, validate, and typecast/coerce input values. These
3
+ # inputs are either taken directly from jekyll's inputs, or handled by parsers
4
+ # first.
5
+ #
6
+ # Logic which affects only a single setting belongs in Instructions, while
7
+ # logic which affects multiple settings belongs in Parsers.
8
+ #
9
+ # Since instruction classes are so small, we define several per file in the
10
+ # instructions directory to save on boilerplate. All fall under the
11
+ # Instructions module namespace.
12
+ module Instructions
13
+ # Generic instruction, meant to be inherited. Children of this class must
14
+ # override the source method, and likely want to override valid?, coerce,
15
+ # and error_message as applicable.
16
+ class Instruction
17
+ # Memoized value of the given instruction. This is the public API.
18
+ def value
19
+ return @value if defined?(@value)
20
+
21
+ raise ArgumentError, error_message unless valid?
22
+
23
+ @value = coerced
24
+ end
25
+
26
+ private
27
+
28
+ # Source(s) of truth - where does this setting come from? Logic does not
29
+ # belong here. If information comes from muliple places, return an array
30
+ # or a hash.
31
+ def source
32
+ raise NotImplementedError
33
+ end
34
+
35
+ # Determine whether or not the input(s) are valid.
36
+ def valid?
37
+ true
38
+ end
39
+
40
+ # Convert input(s) to output.
41
+ def coerce
42
+ source
43
+ end
44
+
45
+ # Message returned if validation fails. Override this with something more
46
+ # helpful.
47
+ def error_message
48
+ "JPT - #{setting_name} received an invalid argument: #{source}"
49
+ end
50
+
51
+ def coerced
52
+ return @coerced if defined?(@coerced)
53
+
54
+ @coerced = coerce
55
+ end
56
+
57
+ def setting_name
58
+ Utils.snakeize(self.class.to_s.split('::').last)
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ # Load Parents
65
+ Dir[File.dirname(__FILE__) + '/instructions/parents/*.rb']
66
+ .sort.each { |file| require file }
67
+
68
+ # Load children:
69
+ Dir[File.dirname(__FILE__) + '/instructions/children/*.rb']
70
+ .sort.each { |file| require file }
@@ -0,0 +1,128 @@
1
+ module PictureTag
2
+ module Instructions
3
+ # PictureTag configuration in _config.yml
4
+ class Pconfig < Instruction
5
+ def source
6
+ PictureTag.config['picture']
7
+ end
8
+ end
9
+
10
+ # https://example.com/my-base-path/assets/generated-images/image.jpg
11
+ # ^^^^^^^^^^^^^
12
+ # | domain | baseurl | directory | filename
13
+ class Baseurl < Instruction
14
+ def source
15
+ {
16
+ ignore: PictureTag.pconfig['ignore_baseurl'],
17
+ key: PictureTag.pconfig['baseurl_key']
18
+ }
19
+ end
20
+
21
+ def coerce
22
+ return '' if source[:ignore]
23
+
24
+ PictureTag.config[source[:key]] || ''
25
+ end
26
+ end
27
+
28
+ # Whether to use relative or absolute URLs for images.
29
+ class RelativeUrl < EnvInstruction
30
+ def source
31
+ PictureTag.pconfig['relative_url']
32
+ end
33
+ end
34
+
35
+ # Image source directory
36
+ class SourceDir < Instruction
37
+ private
38
+
39
+ def source
40
+ [
41
+ PictureTag.site.source,
42
+ PictureTag.pconfig['source']
43
+ ]
44
+ end
45
+
46
+ def coerce
47
+ File.join(*source.map(&:to_s))
48
+ end
49
+
50
+ def setting_name
51
+ 'source directory'
52
+ end
53
+ end
54
+
55
+ # Image output directory
56
+ class DestDir < Instruction
57
+ private
58
+
59
+ def source
60
+ [
61
+ PictureTag.site.config['destination'],
62
+ PictureTag.pconfig['output']
63
+ ]
64
+ end
65
+
66
+ def coerce
67
+ File.join(*source.map(&:to_s))
68
+ end
69
+ end
70
+
71
+ # Whether to continue if a source image is missing
72
+ class ContinueOnMissing < EnvInstruction
73
+ def source
74
+ PictureTag.pconfig['ignore_missing_images']
75
+ end
76
+ end
77
+
78
+ # Whether to use a CDN
79
+ class Cdn < EnvInstruction
80
+ def source
81
+ {
82
+ url: PictureTag.pconfig['cdn_url'],
83
+ setting: PictureTag.pconfig['cdn_environments']
84
+ }
85
+ end
86
+
87
+ def coerce
88
+ source[:url] && super
89
+ end
90
+ end
91
+
92
+ # CDN URL
93
+ class CdnUrl < Instruction
94
+ def source
95
+ PictureTag.pconfig['cdn_url']
96
+ end
97
+
98
+ def valid?
99
+ require 'uri'
100
+ uri = URI(source)
101
+
102
+ # If the URI library can't parse it, it's not valid.
103
+ uri.scheme && uri.host
104
+ end
105
+
106
+ def error_message
107
+ <<~HEREDOC
108
+ cdn_url must be a valid URI in the following format: https://example.com/
109
+ current setting: #{source}
110
+ HEREDOC
111
+ end
112
+ end
113
+
114
+ # Disable JPT?
115
+ class Disabled < EnvInstruction
116
+ def source
117
+ PictureTag.pconfig['disabled']
118
+ end
119
+ end
120
+
121
+ # Fast build?
122
+ class FastBuild < EnvInstruction
123
+ def source
124
+ PictureTag.pconfig['fast_build']
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,24 @@
1
+ module PictureTag
2
+ module Instructions
3
+ # Jekyll site info
4
+ class Site < Instruction
5
+ def source
6
+ PictureTag.context.registers[:site]
7
+ end
8
+ end
9
+
10
+ # Current page in jekyll site
11
+ class Page < Instruction
12
+ def source
13
+ PictureTag.context.registers[:page]
14
+ end
15
+ end
16
+
17
+ # Digs into jekyll context, returns current environment
18
+ class JekyllEnv < Instruction
19
+ def source
20
+ PictureTag.context.environments.first['jekyll']['environment']
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,90 @@
1
+ module PictureTag
2
+ module Instructions
3
+ # Builds instances of all source images.
4
+ class SourceImages < Instruction
5
+ def source
6
+ {
7
+ source_names: PictureTag.params.source_names,
8
+ media_presets: PictureTag.params.media_presets
9
+ }
10
+ end
11
+
12
+ def coerce
13
+ sources = [PictureTag::SourceImage.new(source[:source_names].shift)]
14
+
15
+ while source[:source_names].any?
16
+ sources << PictureTag::SourceImage.new(
17
+ source[:source_names].shift, source[:media_presets].shift
18
+ )
19
+ end
20
+
21
+ sources
22
+ end
23
+ end
24
+
25
+ # Which crop to use for a given media query. Can be given either in params
26
+ # or preset.
27
+ class Crop < ConditionalInstruction
28
+ def source
29
+ super.merge(
30
+ { params: PictureTag.params.crop }
31
+ )
32
+ end
33
+
34
+ def coerce(media = nil)
35
+ raise ArgumentError unless valid?
36
+
37
+ source[:params][media] || value_hash[media]
38
+ end
39
+
40
+ def setting_basename
41
+ 'crop'
42
+ end
43
+
44
+ def setting_prefix
45
+ 'media'
46
+ end
47
+
48
+ def acceptable_types
49
+ super + [String]
50
+ end
51
+ end
52
+
53
+ # Which vips interestingness setting to use for a given media query. Can be
54
+ # given either in params or preset.
55
+ class Keep < ConditionalInstruction
56
+ def source
57
+ super.merge(
58
+ { params: PictureTag.params.keep }
59
+ )
60
+ end
61
+
62
+ def coerce(media = nil)
63
+ raise ArgumentError unless valid?
64
+
65
+ lookup[source[:params][media] || super(media)]
66
+ end
67
+
68
+ def lookup
69
+ {
70
+ 'center' => :centre,
71
+ 'centre' => :centre,
72
+ 'attention' => :attention,
73
+ 'entropy' => :entropy
74
+ }
75
+ end
76
+
77
+ def setting_basename
78
+ 'keep'
79
+ end
80
+
81
+ def setting_prefix
82
+ 'media'
83
+ end
84
+
85
+ def acceptable_types
86
+ super + [String]
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,48 @@
1
+ module PictureTag
2
+ module Instructions
3
+ # PictureTag configuration
4
+ class Config < Instruction
5
+ def source
6
+ PictureTag::Parsers::Configuration.new
7
+ end
8
+ end
9
+
10
+ # Tag parameters
11
+ class Params < Instruction
12
+ def source
13
+ PictureTag::Parsers::TagParser.new PictureTag.raw_params
14
+ end
15
+ end
16
+
17
+ # Currently selected preset
18
+ class Preset < Instruction
19
+ def source
20
+ PictureTag::Parsers::Preset.new PictureTag.params.preset_name
21
+ end
22
+ end
23
+
24
+ # Handles non-image arguments to liquid tag and preset.
25
+ class HtmlAttributes < Instruction
26
+ def source
27
+ PictureTag::Parsers::HTMLAttributeSet.new PictureTag.params.leftovers
28
+ end
29
+ end
30
+
31
+ # TODO: rename to MediaQueries
32
+ # Returns user-defined media queries.
33
+ class MediaPresets < Instruction
34
+ def source
35
+ STOCK_MEDIA_QUERIES.merge(
36
+ PictureTag.site.data.dig('picture', 'media_queries') || {}
37
+ )
38
+ end
39
+ end
40
+
41
+ # Main job is to determine which backend should handle which image formats.
42
+ class Backend < Instruction
43
+ def source
44
+ PictureTag::Parsers::ImageBackend.new
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,182 @@
1
+ require 'mime-types'
2
+
3
+ module PictureTag
4
+ module Instructions
5
+ # Returns an instance of the correct markup format's corresponding class
6
+ class OutputClass < Instruction
7
+ private
8
+
9
+ def source
10
+ PictureTag.preset['markup']
11
+ end
12
+
13
+ def coerce
14
+ Object.const_get(class_name)
15
+ end
16
+
17
+ def class_name
18
+ 'PictureTag::OutputFormats::' + Utils.titleize(source)
19
+ end
20
+
21
+ def valid?
22
+ source.is_a?(String) &&
23
+ !source.include?(' ') &&
24
+ Object.const_defined?(class_name)
25
+ end
26
+ end
27
+
28
+ # Image formats
29
+ class Formats < Instruction
30
+ private
31
+
32
+ def source
33
+ PictureTag.preset['formats']
34
+ end
35
+
36
+ def coerce
37
+ [source].flatten
38
+ end
39
+
40
+ def valid?
41
+ coerced.all? do |format|
42
+ types = MIME::Types.type_for(format)
43
+ format == 'original' ||
44
+ types.length == 1 && types.first.media_type == 'image'
45
+ end
46
+ end
47
+ end
48
+
49
+ # Fallback image format
50
+ class FallbackFormat < Instruction
51
+ private
52
+
53
+ def source
54
+ PictureTag.preset['fallback_format']
55
+ end
56
+
57
+ def valid?
58
+ types = MIME::Types.type_for(coerced)
59
+ coerced == 'original' ||
60
+ types.length == 1 && types.first.media_type == 'image'
61
+ end
62
+ end
63
+
64
+ # Fallback Image width
65
+ class FallbackWidth < Instruction
66
+ private
67
+
68
+ def source
69
+ PictureTag.preset['fallback_width']
70
+ end
71
+
72
+ def valid?
73
+ source.is_a? Integer
74
+ end
75
+
76
+ def error_message
77
+ <<~HEREDOC
78
+ fallback_width for preset '#{PictureTag.preset.name}' is invalid. It
79
+ should be a positive integer. You can use underscores as separators:
80
+ 1200
81
+ 1_200
82
+ HEREDOC
83
+ end
84
+ end
85
+
86
+ # Whether to add a {::nomarkdown} wrapper
87
+ class Nomarkdown < Instruction
88
+ private
89
+
90
+ def source
91
+ {
92
+ config: PictureTag.pconfig['nomarkdown'],
93
+ md_page: Utils.markdown_page?,
94
+ preset: PictureTag.preset['nomarkdown']
95
+ }
96
+ end
97
+
98
+ def valid?
99
+ source.fetch_values(:preset, :config).all? do |setting|
100
+ [true, false, nil].include? setting
101
+ end
102
+ end
103
+
104
+ def coerce
105
+ return source[:preset] unless source[:preset].nil?
106
+
107
+ source[:md_page] && source[:config]
108
+ end
109
+ end
110
+
111
+ # Returns widths for a given media query.
112
+ class Widths < ConditionalInstruction
113
+ private
114
+
115
+ def setting_name
116
+ 'widths'
117
+ end
118
+
119
+ def setting_prefix
120
+ 'media'
121
+ end
122
+
123
+ def acceptable_types
124
+ super + [Array]
125
+ end
126
+
127
+ def valid_hash?
128
+ hash = source[:hash]
129
+ return true if hash.nil?
130
+
131
+ hash.is_a?(Hash) && valid_hash_keys?(hash) && valid_hash_values?(hash)
132
+ end
133
+
134
+ def valid_hash_keys?(hash)
135
+ hash.keys.all? { |k| k.is_a? String }
136
+ end
137
+
138
+ def valid_hash_values?(hash)
139
+ hash.values.all? do |val|
140
+ val.is_a?(Array) && val.all? { |subval| subval.is_a? Integer }
141
+ end
142
+ end
143
+ end
144
+
145
+ # Returns quality for a given width.
146
+ class Quality < ConditionalInstruction
147
+ private
148
+
149
+ def setting_name
150
+ 'quality'
151
+ end
152
+
153
+ def setting_prefix
154
+ 'format'
155
+ end
156
+
157
+ def acceptable_types
158
+ super + [Integer, Hash]
159
+ end
160
+
161
+ def coerce(format = nil, width = nil)
162
+ setting = super(format)
163
+
164
+ return setting unless setting.is_a? Hash
165
+
166
+ parse_quality_hash(setting, width)
167
+ end
168
+
169
+ # Works out linearly interpolated quality settings.
170
+ def parse_quality_hash(points, width)
171
+ # The points can be given in any order.
172
+ low, high = *points.keys.map(&:to_i).sort
173
+
174
+ case width
175
+ when 0..low then points[low]
176
+ when low..high then Utils.interpolate(points.keys, points.values, width)
177
+ when high..999_999 then points[high]
178
+ end
179
+ end
180
+ end
181
+ end
182
+ end