jekyll_picture_tag 1.10.2 → 2.0.0pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (123) hide show
  1. checksums.yaml +4 -4
  2. data/.envrc +4 -0
  3. data/.github/workflows/code-checks.yml +33 -0
  4. data/.gitignore +3 -0
  5. data/.rubocop.yml +32 -0
  6. data/.ruby-version +1 -1
  7. data/docs/.envrc +2 -0
  8. data/docs/Gemfile +4 -2
  9. data/docs/Gemfile.lock +14 -12
  10. data/docs/_config.yml +6 -10
  11. data/docs/devs/contributing/code.md +54 -0
  12. data/docs/devs/contributing/docs.md +31 -0
  13. data/docs/devs/contributing/index.md +15 -0
  14. data/docs/devs/contributing/setup.md +33 -0
  15. data/docs/devs/contributing/testing.md +23 -0
  16. data/docs/devs/index.md +7 -0
  17. data/docs/devs/releases.md +113 -0
  18. data/docs/index.md +62 -31
  19. data/docs/logo.png +0 -0
  20. data/docs/logo.svg +880 -0
  21. data/docs/users/configuration/directories.md +34 -0
  22. data/docs/users/configuration/disable.md +24 -0
  23. data/docs/users/configuration/fast_build.md +28 -0
  24. data/docs/users/configuration/ignore_missing.md +23 -0
  25. data/docs/users/configuration/index.md +29 -0
  26. data/docs/users/configuration/kramdown_fix.md +20 -0
  27. data/docs/users/configuration/suppress_warnings.md +16 -0
  28. data/docs/users/configuration/urls.md +69 -0
  29. data/docs/users/getting_started.md +55 -0
  30. data/docs/users/index.md +7 -0
  31. data/docs/users/installation.md +31 -0
  32. data/docs/users/liquid_tag/argument_reference/alternate_images.md +18 -0
  33. data/docs/users/liquid_tag/argument_reference/attributes.md +42 -0
  34. data/docs/users/liquid_tag/argument_reference/base_image.md +12 -0
  35. data/docs/users/liquid_tag/argument_reference/crop.md +33 -0
  36. data/docs/users/liquid_tag/argument_reference/link.md +16 -0
  37. data/docs/users/liquid_tag/argument_reference/preset.md +17 -0
  38. data/docs/users/liquid_tag/argument_reference/readme.md +9 -0
  39. data/docs/users/liquid_tag/examples.md +81 -0
  40. data/docs/users/liquid_tag/index.md +31 -0
  41. data/docs/users/notes/git_lfs.md +7 -0
  42. data/docs/users/notes/github_pages.md +5 -0
  43. data/docs/users/notes/html_attributes.md +5 -0
  44. data/docs/users/notes/index.md +6 -0
  45. data/docs/users/notes/kramdown_bug.md +41 -0
  46. data/docs/users/notes/managing_images.md +21 -0
  47. data/docs/{migration.md → users/notes/migration_1.md} +1 -1
  48. data/docs/users/notes/migration_2.md +99 -0
  49. data/docs/users/presets/cropping.md +60 -0
  50. data/docs/users/presets/default.md +32 -0
  51. data/docs/users/presets/examples.md +111 -0
  52. data/docs/users/presets/fallback_image.md +28 -0
  53. data/docs/users/presets/html_attributes.md +26 -0
  54. data/docs/users/presets/image_formats.md +21 -0
  55. data/docs/users/presets/image_quality.md +120 -0
  56. data/docs/users/presets/index.md +137 -0
  57. data/docs/users/presets/link_source.md +16 -0
  58. data/docs/users/presets/markup_formats/fragments.md +48 -0
  59. data/docs/users/presets/markup_formats/javascript_friendly.md +57 -0
  60. data/docs/users/presets/markup_formats/readme.md +43 -0
  61. data/docs/users/presets/markup_formats/standard_html.md +25 -0
  62. data/docs/users/presets/media_queries.md +36 -0
  63. data/docs/users/presets/nomarkdown_override.md +17 -0
  64. data/docs/users/presets/pixel_ratio_srcsets.md +32 -0
  65. data/docs/users/presets/quality_width_graph.png +0 -0
  66. data/docs/users/presets/width_height_attributes.md +34 -0
  67. data/docs/users/presets/width_srcsets.md +123 -0
  68. data/docs/users/tutorial.md +97 -0
  69. data/jekyll_picture_tag.gemspec +38 -24
  70. data/lib/jekyll_picture_tag.rb +11 -9
  71. data/lib/jekyll_picture_tag/cache.rb +64 -0
  72. data/lib/jekyll_picture_tag/defaults/global.rb +18 -0
  73. data/lib/jekyll_picture_tag/defaults/presets.rb +57 -0
  74. data/lib/jekyll_picture_tag/images.rb +4 -0
  75. data/lib/jekyll_picture_tag/images/generated_image.rb +92 -0
  76. data/lib/jekyll_picture_tag/images/image_file.rb +90 -0
  77. data/lib/jekyll_picture_tag/{img_uri.rb → images/img_uri.rb} +4 -10
  78. data/lib/jekyll_picture_tag/images/source_image.rb +138 -0
  79. data/lib/jekyll_picture_tag/instructions.rb +70 -6
  80. data/lib/jekyll_picture_tag/instructions/children/config.rb +128 -0
  81. data/lib/jekyll_picture_tag/instructions/children/context.rb +24 -0
  82. data/lib/jekyll_picture_tag/instructions/children/params.rb +90 -0
  83. data/lib/jekyll_picture_tag/instructions/children/parsers.rb +41 -0
  84. data/lib/jekyll_picture_tag/instructions/children/preset.rb +182 -0
  85. data/lib/jekyll_picture_tag/instructions/parents/conditional_instruction.rb +69 -0
  86. data/lib/jekyll_picture_tag/instructions/parents/env_instruction.rb +29 -0
  87. data/lib/jekyll_picture_tag/output_formats/basic.rb +11 -21
  88. data/lib/jekyll_picture_tag/output_formats/img.rb +11 -0
  89. data/lib/jekyll_picture_tag/output_formats/picture.rb +22 -0
  90. data/lib/jekyll_picture_tag/parsers.rb +5 -0
  91. data/lib/jekyll_picture_tag/{instructions → parsers}/arg_splitter.rb +4 -3
  92. data/lib/jekyll_picture_tag/parsers/configuration.rb +28 -0
  93. data/lib/jekyll_picture_tag/{instructions → parsers}/html_attributes.rb +1 -1
  94. data/lib/jekyll_picture_tag/parsers/preset.rb +43 -0
  95. data/lib/jekyll_picture_tag/{instructions → parsers}/tag_parser.rb +15 -12
  96. data/lib/jekyll_picture_tag/router.rb +35 -92
  97. data/lib/jekyll_picture_tag/srcsets/basic.rb +11 -8
  98. data/lib/jekyll_picture_tag/utils.rb +24 -20
  99. data/lib/jekyll_picture_tag/version.rb +1 -1
  100. data/readme.md +15 -12
  101. metadata +206 -95
  102. data/.travis.yml +0 -8
  103. data/Dockerfile +0 -9
  104. data/docs/_layouts/directory.html +0 -32
  105. data/docs/assets/style.css +0 -31
  106. data/docs/contributing.md +0 -109
  107. data/docs/example_presets.md +0 -116
  108. data/docs/global_configuration.md +0 -173
  109. data/docs/installation.md +0 -45
  110. data/docs/notes.md +0 -91
  111. data/docs/output.md +0 -63
  112. data/docs/presets.md +0 -361
  113. data/docs/releases.md +0 -65
  114. data/docs/usage.md +0 -143
  115. data/jekyll-picture-tag.gemspec +0 -52
  116. data/lib/jekyll-picture-tag.rb +0 -25
  117. data/lib/jekyll_picture_tag/defaults/global.yml +0 -11
  118. data/lib/jekyll_picture_tag/defaults/presets.yml +0 -10
  119. data/lib/jekyll_picture_tag/generated_image.rb +0 -161
  120. data/lib/jekyll_picture_tag/instructions/configuration.rb +0 -121
  121. data/lib/jekyll_picture_tag/instructions/preset.rb +0 -102
  122. data/lib/jekyll_picture_tag/instructions/set.rb +0 -71
  123. data/lib/jekyll_picture_tag/source_image.rb +0 -87
@@ -3,6 +3,8 @@ module PictureTag
3
3
  # Represents a bare <img> tag with a srcset attribute.
4
4
  # Used when <picture> is unnecessary.
5
5
  class Img < Basic
6
+ private
7
+
6
8
  def srcset
7
9
  @srcset ||= build_srcset(
8
10
  PictureTag.source_images.first, PictureTag.formats.first
@@ -17,8 +19,17 @@ module PictureTag
17
19
 
18
20
  img.attributes << PictureTag.html_attributes['parent']
19
21
 
22
+ add_dimensions(img, srcset)
23
+
20
24
  img
21
25
  end
26
+
27
+ def add_dimensions(img, srcset)
28
+ return unless PictureTag.preset['dimension_attributes']
29
+
30
+ img.width = srcset.width_attribute
31
+ img.height = srcset.height_attribute
32
+ end
22
33
  end
23
34
  end
24
35
  end
@@ -3,7 +3,13 @@ module PictureTag
3
3
  # Represents a <picture> tag, enclosing at least 2 <source> tags and an
4
4
  # <img> tag.
5
5
  class Picture < Basic
6
+ private
7
+
6
8
  def srcsets
9
+ @srcsets ||= build_srcsets
10
+ end
11
+
12
+ def build_srcsets
7
13
  formats = PictureTag.formats
8
14
  # Source images are provided in reverse order and must be flipped:
9
15
  images = PictureTag.source_images.reverse
@@ -37,6 +43,22 @@ module PictureTag
37
43
  source
38
44
  end
39
45
 
46
+ def build_base_img
47
+ img = super
48
+
49
+ # Only add source dimensions if there is a single source image.
50
+ # Currently you can't use both art direction and intrinsic image sizes.
51
+ if PictureTag.preset['dimension_attributes'] &&
52
+ PictureTag.source_images.length == 1
53
+ img.attributes << {
54
+ width: srcsets.first.width_attribute,
55
+ height: srcsets.first.height_attribute
56
+ }
57
+ end
58
+
59
+ img
60
+ end
61
+
40
62
  def base_markup
41
63
  picture = DoubleTag.new(
42
64
  'picture',
@@ -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
  #
@@ -51,9 +51,10 @@ module PictureTag
51
51
  end
52
52
 
53
53
  def handle_special(char)
54
- if char == '\\'
54
+ case char
55
+ when '\\'
55
56
  @escaped = true
56
- elsif char == '"'
57
+ when '"'
57
58
  @in_quotes = !@in_quotes
58
59
  @word << char
59
60
  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
@@ -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,114 +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
- # Context forwarding
12
+ # These two attributes encompass everything passed in by Jekyll.
13
+ attr_accessor :raw_params, :context
13
14
 
14
- # Global site data
15
- def site
16
- @context.registers[:site]
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
17
21
  end
18
22
 
19
- # Page which tag is called from
20
- def page
21
- @context.registers[:page]
23
+ def respond_to_missing?(method_name, *args)
24
+ instruction_exists?(method_name) || super
22
25
  end
23
26
 
24
- # Instructions forwarding
25
-
26
- def config
27
- @instructions.config
27
+ # Required at least for testing; instructions are persisted between tags
28
+ # otherwise.
29
+ def clear_instructions
30
+ instructions.clear
28
31
  end
29
32
 
30
- def preset
31
- @instructions.preset
32
- end
33
-
34
- def media_presets
35
- @instructions.media_presets
36
- end
37
-
38
- def html_attributes
39
- @instructions.html_attributes
40
- end
41
-
42
- def output_class
43
- @instructions.output_class
44
- end
45
-
46
- def source_images
47
- @instructions.source_images
48
- end
49
-
50
- def crop(media = nil)
51
- @instructions.crop(media)
52
- end
53
-
54
- def gravity(media = nil)
55
- @instructions.gravity(media)
56
- end
57
-
58
- # Config Forwarding
59
-
60
- def source_dir
61
- config.source_dir
62
- end
63
-
64
- def dest_dir
65
- config.dest_dir
66
- end
67
-
68
- def continue_on_missing?
69
- config.continue_on_missing?
70
- end
71
-
72
- def cdn?
73
- config.cdn?
74
- end
75
-
76
- def pconfig
77
- config.pconfig
78
- end
79
-
80
- def disabled?
81
- config.disabled?
82
- end
83
-
84
- def fast_build?
85
- config.fast_build?
86
- end
87
-
88
- # Preset forwarding
89
-
90
- def widths(media)
91
- preset.widths(media)
92
- end
33
+ private
93
34
 
94
- def formats
95
- preset.formats
35
+ def instruction(method_name)
36
+ instructions[method_name] ||= instruction_class(method_name).new
96
37
  end
97
38
 
98
- def fallback_format
99
- preset.fallback_format
39
+ def instructions
40
+ @instructions ||= {}
100
41
  end
101
42
 
102
- def fallback_width
103
- preset.fallback_width
43
+ def instruction_exists?(method_name)
44
+ Object.const_defined? instruction_class_name(method_name.to_sym)
104
45
  end
105
46
 
106
- def nomarkdown?
107
- 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)
108
50
  end
109
51
 
110
- def quality(format)
111
- preset.quality(format)
52
+ def instruction_class_name(method_name)
53
+ 'PictureTag::Instructions::' +
54
+ Utils.titleize(method_name.to_s.delete_suffix('?'))
112
55
  end
113
56
  end
114
57
  end
@@ -23,6 +23,7 @@ module PictureTag
23
23
  @format ||= files.first.format
24
24
  end
25
25
 
26
+ # GeneratedImage class
26
27
  def files
27
28
  @files ||= build_files
28
29
  end
@@ -51,6 +52,14 @@ module PictureTag
51
52
  "(#{PictureTag.media_presets[@media]})"
52
53
  end
53
54
 
55
+ def width_attribute
56
+ source_image.width.to_s
57
+ end
58
+
59
+ def height_attribute
60
+ source_image.height.to_s
61
+ end
62
+
54
63
  private
55
64
 
56
65
  def build_files
@@ -77,11 +86,7 @@ module PictureTag
77
86
  end
78
87
 
79
88
  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
89
+ source_image.width
85
90
  end
86
91
 
87
92
  def target_files
@@ -103,9 +108,7 @@ module PictureTag
103
108
  GeneratedImage.new(
104
109
  source_file: @source_image,
105
110
  width: width,
106
- format: @input_format,
107
- crop: PictureTag.crop(@media),
108
- gravity: PictureTag.gravity(@media)
111
+ format: @input_format
109
112
  )
110
113
  end
111
114
  end
@@ -2,28 +2,10 @@ 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
-
23
5
  class << self
24
6
  # Configure Jekyll to keep our generated files
25
7
  def keep_files
26
- dest_dir = PictureTag.config['picture']['output']
8
+ dest_dir = PictureTag.pconfig['output']
27
9
 
28
10
  # Chop a slash off the end, if it's there. Doesn't work otherwise.
29
11
  dest_dir = dest_dir[0..-2] if dest_dir =~ %r{/\z}
@@ -35,7 +17,7 @@ module PictureTag
35
17
 
36
18
  # Print a warning to the console
37
19
  def warning(message)
38
- return if PictureTag.config['picture']['suppress_warnings']
20
+ return if PictureTag.pconfig['suppress_warnings']
39
21
 
40
22
  warn 'Jekyll Picture Tag Warning: '.yellow + message
41
23
  end
@@ -63,6 +45,28 @@ module PictureTag
63
45
  def titleize(input)
64
46
  input.split('_').map(&:capitalize).join
65
47
  end
48
+
49
+ def snakeize(input)
50
+ input.scan(/[A-Z][a-z]+/).map(&:downcase).join('_')
51
+ end
52
+
53
+ # Linear interpolator. Pass it 2 values in the x array, 2 values
54
+ # in the y array, and an x value, returns a y value.
55
+ def interpolate(xvals, yvals, xval)
56
+ xvals.map!(&:to_f)
57
+ yvals.map!(&:to_f)
58
+
59
+ # Slope
60
+ m = (yvals.last - yvals.first) / (xvals.last - xvals.first)
61
+ # Value of y when x=0
62
+ b = yvals.first - (m * xvals.first)
63
+ # y = mx + b
64
+ (m * xval) + b
65
+ end
66
+
67
+ def aspect_float(width, height)
68
+ width.to_f / height
69
+ end
66
70
  end
67
71
  end
68
72
  end