jekyll_picture_tag 1.14.0 → 2.0.0pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) 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 +20 -0
  9. data/docs/index.md +32 -17
  10. data/docs/logo.png +0 -0
  11. data/docs/logo.svg +880 -0
  12. data/docs/users/getting_started.md +55 -0
  13. data/docs/users/installation.md +17 -38
  14. data/docs/users/liquid_tag/argument_reference/crop.md +21 -36
  15. data/docs/users/liquid_tag/examples.md +13 -25
  16. data/docs/users/liquid_tag/index.md +1 -1
  17. data/docs/users/notes/{migration.md → migration_1.md} +1 -1
  18. data/docs/users/notes/migration_2.md +99 -0
  19. data/docs/users/presets/cropping.md +21 -22
  20. data/docs/users/presets/default.md +11 -3
  21. data/docs/users/presets/examples.md +77 -45
  22. data/docs/users/presets/fallback_image.md +1 -1
  23. data/docs/users/presets/html_attributes.md +1 -1
  24. data/docs/users/presets/image_formats.md +3 -3
  25. data/docs/users/presets/image_quality.md +70 -55
  26. data/docs/users/presets/index.md +78 -42
  27. data/docs/users/presets/link_source.md +1 -1
  28. data/docs/users/presets/media_queries.md +1 -1
  29. data/docs/users/presets/nomarkdown_override.md +1 -1
  30. data/docs/users/presets/pixel_ratio_srcsets.md +1 -1
  31. data/docs/users/presets/width_height_attributes.md +1 -1
  32. data/docs/users/presets/width_srcsets.md +61 -23
  33. data/docs/users/tutorial.md +97 -0
  34. data/jekyll_picture_tag.gemspec +33 -23
  35. data/lib/jekyll_picture_tag.rb +8 -6
  36. data/lib/jekyll_picture_tag/cache.rb +64 -3
  37. data/lib/jekyll_picture_tag/defaults/global.rb +18 -0
  38. data/lib/jekyll_picture_tag/defaults/presets.rb +57 -0
  39. data/lib/jekyll_picture_tag/images.rb +1 -0
  40. data/lib/jekyll_picture_tag/images/generated_image.rb +25 -63
  41. data/lib/jekyll_picture_tag/images/image_file.rb +90 -0
  42. data/lib/jekyll_picture_tag/images/img_uri.rb +3 -12
  43. data/lib/jekyll_picture_tag/images/source_image.rb +44 -9
  44. data/lib/jekyll_picture_tag/instructions.rb +70 -6
  45. data/lib/jekyll_picture_tag/instructions/children/config.rb +128 -0
  46. data/lib/jekyll_picture_tag/instructions/children/context.rb +24 -0
  47. data/lib/jekyll_picture_tag/instructions/children/params.rb +90 -0
  48. data/lib/jekyll_picture_tag/instructions/children/parsers.rb +41 -0
  49. data/lib/jekyll_picture_tag/instructions/children/preset.rb +182 -0
  50. data/lib/jekyll_picture_tag/instructions/parents/conditional_instruction.rb +69 -0
  51. data/lib/jekyll_picture_tag/instructions/parents/env_instruction.rb +29 -0
  52. data/lib/jekyll_picture_tag/output_formats/basic.rb +5 -17
  53. data/lib/jekyll_picture_tag/parsers.rb +5 -0
  54. data/lib/jekyll_picture_tag/{instructions → parsers}/arg_splitter.rb +1 -1
  55. data/lib/jekyll_picture_tag/parsers/configuration.rb +28 -0
  56. data/lib/jekyll_picture_tag/{instructions → parsers}/html_attributes.rb +1 -1
  57. data/lib/jekyll_picture_tag/parsers/preset.rb +43 -0
  58. data/lib/jekyll_picture_tag/{instructions → parsers}/tag_parser.rb +15 -12
  59. data/lib/jekyll_picture_tag/router.rb +35 -93
  60. data/lib/jekyll_picture_tag/srcsets/basic.rb +4 -10
  61. data/lib/jekyll_picture_tag/utils.rb +10 -20
  62. data/lib/jekyll_picture_tag/version.rb +1 -1
  63. data/readme.md +2 -0
  64. metadata +124 -106
  65. data/Dockerfile +0 -9
  66. data/docs/users/notes/input_checking.md +0 -6
  67. data/docs/users/presets/strip_metadata.md +0 -13
  68. data/install_imagemagick.sh +0 -23
  69. data/jekyll-picture-tag.gemspec +0 -52
  70. data/lib/jekyll-picture-tag.rb +0 -25
  71. data/lib/jekyll_picture_tag/cache/base.rb +0 -61
  72. data/lib/jekyll_picture_tag/cache/generated.rb +0 -20
  73. data/lib/jekyll_picture_tag/cache/source.rb +0 -19
  74. data/lib/jekyll_picture_tag/defaults/global.yml +0 -13
  75. data/lib/jekyll_picture_tag/defaults/presets.yml +0 -12
  76. data/lib/jekyll_picture_tag/instructions/configuration.rb +0 -121
  77. data/lib/jekyll_picture_tag/instructions/preset.rb +0 -122
  78. 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 > source_width
121
+ if target > source.width
134
122
  Utils.warning "#{source.shortname} is smaller than the " \
135
- "requested fallback width of #{target}px. Using #{source_width}" \
123
+ "requested fallback width of #{target}px. Using #{source.width}" \
136
124
  ' px instead.'
137
- source_width
125
+ source.width
138
126
  else
139
127
  target
140
128
  end
@@ -0,0 +1,5 @@
1
+ require_relative 'parsers/arg_splitter'
2
+ require_relative 'parsers/configuration'
3
+ require_relative 'parsers/html_attributes'
4
+ require_relative 'parsers/preset'
5
+ require_relative 'parsers/tag_parser'
@@ -1,5 +1,5 @@
1
1
  module PictureTag
2
- module Instructions
2
+ module Parsers
3
3
  # This class takes in the arguments passed to the liquid tag, and splits it
4
4
  # up into 'words' (correctly handling quotes and backslash escapes.)
5
5
  #
@@ -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
@@ -1,5 +1,5 @@
1
1
  module PictureTag
2
- module Instructions
2
+ module Parsers
3
3
  # Handles HTML attributes, sourced from configuration and the liquid tag,
4
4
  # sent to various elements.
5
5
  # Stored as a hash, with string keys.
@@ -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 Instructions
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 gravity. HTML attributes are handed off to its respective class
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. Gravities and geometries are stored in a hash, keyed by their
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, :gravities,
22
- :geometries, :leftovers
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
- @geometries = {}
31
- @gravities = {}
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
- elsif Utils::GRAVITIES.include?(param.downcase)
58
- @gravities[@media_presets.last] = @params.shift
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
- elsif param.match?(Utils::GEOMETRY_REGEX)
61
- @geometries[@media_presets.last] = @params.shift
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. The following module 'routes' method calls to the right place, so
4
- # the rest of the application can just call 'PictureTag.(some method)'
5
-
6
- # At first I thought I'd do some sweet dynamic metaprogramming here, but it
7
- # ended up complicated and clever, rather than convenient and understandable.
8
- # This way is not strictly DRY, but it's straightforward and readable. If it
9
- # gets big, I'll refactor.
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
- attr_accessor :instructions, :context
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 source_images
48
- @instructions.source_images
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 crop(media = nil)
52
- @instructions.crop(media)
23
+ def respond_to_missing?(method_name, *args)
24
+ instruction_exists?(method_name) || super
53
25
  end
54
26
 
55
- def gravity(media = nil)
56
- @instructions.gravity(media)
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
- # Config Forwarding
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 formats
96
- preset.formats
35
+ def instruction(method_name)
36
+ instructions[method_name] ||= instruction_class(method_name).new
97
37
  end
98
38
 
99
- def fallback_format
100
- preset.fallback_format
39
+ def instructions
40
+ @instructions ||= {}
101
41
  end
102
42
 
103
- def fallback_width
104
- preset.fallback_width
43
+ def instruction_exists?(method_name)
44
+ Object.const_defined? instruction_class_name(method_name.to_sym)
105
45
  end
106
46
 
107
- def nomarkdown?
108
- preset.nomarkdown?
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 quality(format, width)
112
- preset.quality(format, width)
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