jekyll_picture_tag 1.9.0 → 1.12.0

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.
Files changed (96) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -0
  3. data/.travis.yml +4 -7
  4. data/Dockerfile +9 -0
  5. data/docs/Gemfile +4 -2
  6. data/docs/Gemfile.lock +186 -89
  7. data/docs/_config.yml +6 -10
  8. data/docs/devs/contributing/code.md +44 -0
  9. data/docs/devs/contributing/docs.md +13 -0
  10. data/docs/devs/contributing/index.md +15 -0
  11. data/docs/devs/contributing/setup.md +13 -0
  12. data/docs/devs/contributing/testing.md +41 -0
  13. data/docs/devs/index.md +7 -0
  14. data/docs/{releases.md → devs/releases.md} +35 -13
  15. data/docs/index.md +58 -36
  16. data/docs/users/configuration/cdn.md +35 -0
  17. data/docs/users/configuration/directories.md +34 -0
  18. data/docs/users/configuration/disable.md +24 -0
  19. data/docs/users/configuration/fast_build.md +28 -0
  20. data/docs/users/configuration/ignore_missing.md +23 -0
  21. data/docs/users/configuration/index.md +29 -0
  22. data/docs/users/configuration/kramdown_fix.md +20 -0
  23. data/docs/users/configuration/relative_urls.md +15 -0
  24. data/docs/users/configuration/suppress_warnings.md +16 -0
  25. data/docs/users/index.md +7 -0
  26. data/docs/users/installation.md +52 -0
  27. data/docs/users/liquid_tag/argument_reference/alternate_images.md +18 -0
  28. data/docs/users/liquid_tag/argument_reference/attributes.md +42 -0
  29. data/docs/users/liquid_tag/argument_reference/base_image.md +12 -0
  30. data/docs/users/liquid_tag/argument_reference/crop.md +48 -0
  31. data/docs/users/liquid_tag/argument_reference/link.md +16 -0
  32. data/docs/users/liquid_tag/argument_reference/preset.md +17 -0
  33. data/docs/users/liquid_tag/argument_reference/readme.md +9 -0
  34. data/docs/users/liquid_tag/examples.md +81 -0
  35. data/docs/users/liquid_tag/index.md +31 -0
  36. data/docs/users/notes/git_lfs.md +7 -0
  37. data/docs/users/notes/github_pages.md +5 -0
  38. data/docs/users/notes/html_attributes.md +5 -0
  39. data/docs/users/notes/index.md +6 -0
  40. data/docs/users/notes/input_checking.md +6 -0
  41. data/docs/users/notes/kramdown_bug.md +41 -0
  42. data/docs/users/notes/managing_images.md +21 -0
  43. data/docs/{migration.md → users/notes/migration.md} +0 -0
  44. data/docs/users/presets/cropping.md +61 -0
  45. data/docs/users/presets/default.md +23 -0
  46. data/docs/users/presets/examples.md +79 -0
  47. data/docs/users/presets/fallback_image.md +28 -0
  48. data/docs/users/presets/html_attributes.md +26 -0
  49. data/docs/users/presets/image_formats.md +21 -0
  50. data/docs/users/presets/image_quality.md +43 -0
  51. data/docs/users/presets/index.md +101 -0
  52. data/docs/users/presets/link_source.md +16 -0
  53. data/docs/users/presets/markup_formats/fragments.md +48 -0
  54. data/docs/users/presets/markup_formats/javascript_friendly.md +57 -0
  55. data/docs/users/presets/markup_formats/readme.md +43 -0
  56. data/docs/users/presets/markup_formats/standard_html.md +25 -0
  57. data/docs/users/presets/media_queries.md +36 -0
  58. data/docs/users/presets/nomarkdown_override.md +17 -0
  59. data/docs/users/presets/pixel_ratio_srcsets.md +32 -0
  60. data/docs/users/presets/width_height_attributes.md +34 -0
  61. data/docs/users/presets/width_srcsets.md +85 -0
  62. data/jekyll_picture_tag.gemspec +1 -1
  63. data/lib/jekyll_picture_tag.rb +1 -0
  64. data/lib/jekyll_picture_tag/cache.rb +3 -0
  65. data/lib/jekyll_picture_tag/cache/base.rb +59 -0
  66. data/lib/jekyll_picture_tag/cache/generated.rb +20 -0
  67. data/lib/jekyll_picture_tag/cache/source.rb +19 -0
  68. data/lib/jekyll_picture_tag/defaults/presets.yml +2 -0
  69. data/lib/jekyll_picture_tag/generated_image.rb +52 -41
  70. data/lib/jekyll_picture_tag/img_uri.rb +1 -0
  71. data/lib/jekyll_picture_tag/instructions.rb +1 -0
  72. data/lib/jekyll_picture_tag/instructions/arg_splitter.rb +69 -0
  73. data/lib/jekyll_picture_tag/instructions/configuration.rb +1 -1
  74. data/lib/jekyll_picture_tag/instructions/preset.rb +40 -15
  75. data/lib/jekyll_picture_tag/instructions/set.rb +23 -9
  76. data/lib/jekyll_picture_tag/instructions/tag_parser.rb +59 -69
  77. data/lib/jekyll_picture_tag/output_formats/basic.rb +34 -15
  78. data/lib/jekyll_picture_tag/output_formats/img.rb +11 -0
  79. data/lib/jekyll_picture_tag/output_formats/picture.rb +22 -0
  80. data/lib/jekyll_picture_tag/router.rb +9 -0
  81. data/lib/jekyll_picture_tag/source_image.rb +60 -44
  82. data/lib/jekyll_picture_tag/srcsets/basic.rb +29 -7
  83. data/lib/jekyll_picture_tag/utils.rb +18 -0
  84. data/lib/jekyll_picture_tag/version.rb +1 -1
  85. data/readme.md +40 -16
  86. metadata +67 -20
  87. data/docs/_layouts/directory.html +0 -32
  88. data/docs/assets/style.css +0 -31
  89. data/docs/contributing.md +0 -75
  90. data/docs/example_presets.md +0 -116
  91. data/docs/global_configuration.md +0 -173
  92. data/docs/installation.md +0 -30
  93. data/docs/notes.md +0 -91
  94. data/docs/output.md +0 -63
  95. data/docs/presets.md +0 -309
  96. data/docs/usage.md +0 -113
@@ -0,0 +1,20 @@
1
+ module PictureTag
2
+ module Cache
3
+ # Caches generated image details, so we can skip expensive operations whenever
4
+ # possible.
5
+ # Stored width and height are values for the source image, after cropping.
6
+ class Generated
7
+ include Base
8
+
9
+ private
10
+
11
+ def cache_dir
12
+ 'generated'
13
+ end
14
+
15
+ def template
16
+ { width: nil, height: nil }
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,19 @@
1
+ module PictureTag
2
+ module Cache
3
+ # Caches source image details, so we can skip expensive operations whenever
4
+ # possible.
5
+ class Source
6
+ include Base
7
+
8
+ private
9
+
10
+ def template
11
+ { digest: nil, width: nil, height: nil }
12
+ end
13
+
14
+ def cache_dir
15
+ 'source'
16
+ end
17
+ end
18
+ end
19
+ end
@@ -7,3 +7,5 @@ noscript: false
7
7
  link_source: false
8
8
  quality: 75
9
9
  data_sizes: true
10
+ gravity: center
11
+ dimension_attributes: false
@@ -4,12 +4,15 @@ module PictureTag
4
4
  # Represents a generated image file.
5
5
  class GeneratedImage
6
6
  attr_reader :width, :format
7
+
7
8
  include MiniMagick
8
9
 
9
- def initialize(source_file:, width:, format:)
10
+ def initialize(source_file:, width:, format:, crop: nil, gravity: '')
10
11
  @source = source_file
11
12
  @width = width
12
13
  @format = process_format format
14
+ @crop = crop
15
+ @gravity = gravity
13
16
  end
14
17
 
15
18
  def exists?
@@ -29,7 +32,7 @@ module PictureTag
29
32
  # /home/dave/my_blog/_site/generated/somefolder/myimage-100-123abc.jpg
30
33
  # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
31
34
  def name
32
- name_left + digest + name_right
35
+ @name ||= "#{@source.base_name}-#{@width}-#{id}.#{@format}"
33
36
  end
34
37
 
35
38
  # https://example.com/assets/images/myimage-100-123abc.jpg
@@ -38,54 +41,64 @@ module PictureTag
38
41
  ImgURI.new(name).to_s
39
42
  end
40
43
 
41
- private
44
+ # Post crop
45
+ def source_width
46
+ update_cache unless cache[:width]
42
47
 
43
- # /home/dave/my_blog/_site/generated/somefolder/myimage-100-123abc.jpg
44
- # ^^^^^^^^^^^^^^^^^^^^^^^
45
- def name_left
46
- "#{@source.base_name}-#{@width}-"
48
+ cache[:width]
47
49
  end
48
50
 
49
- # /home/dave/my_blog/_site/generated/somefolder/myimage-100-123abc.jpg
50
- # ^^^^^^
51
- def digest
52
- guess_digest if !@source.digest_guess && PictureTag.fast_build?
51
+ # Post crop
52
+ def source_height
53
+ update_cache unless cache[:height]
53
54
 
54
- @source.digest
55
+ cache[:height]
55
56
  end
56
57
 
57
- # /home/dave/my_blog/_site/generated/somefolder/myimage-100-123abc.jpg
58
- # ^^^^
59
- def name_right
60
- '.' + @format
58
+ private
59
+
60
+ # We exclude width and format from the cache name, since it isn't specific to them.
61
+ def cache
62
+ @cache ||= Cache::Generated.new("#{@source.base_name}-#{id}")
61
63
  end
62
64
 
63
- # Used for the fast build option: look for a file which matches everything
64
- # we know about the destination file without calculating a digest on the
65
- # source file, and if it exists we assume it's the right one.
66
- def guess_digest
67
- matches = dest_glob
68
- return unless matches.length == 1
65
+ def update_cache
66
+ return if @source.missing
69
67
 
70
- # Start and finish of the destination image's hash value
71
- finish = -name_right.length
72
- start = finish - 6
68
+ # Ensure it's generated:
69
+ image
73
70
 
74
- # The source image keeps track of this guess, so we hand it off:
75
- @source.digest_guess = matches.first[start...finish]
71
+ cache[:width] = @source_dimensions[:width]
72
+ cache[:height] = @source_dimensions[:height]
73
+
74
+ cache.write
76
75
  end
77
76
 
78
- # Returns a list of images which are probably correct.
79
- def dest_glob
80
- Dir.glob File.join(PictureTag.dest_dir, name_left + '?' * 6 + name_right)
77
+ # Hash all inputs and truncate, so we know when they change without getting too long.
78
+ # /home/dave/my_blog/_site/generated/somefolder/myimage-100-1234abcde.jpg
79
+ # ^^^^^^^^^
80
+ def id
81
+ @id ||= Digest::MD5.hexdigest([@source.digest, @crop, @gravity, quality].join)[0..8]
81
82
  end
82
83
 
84
+ # Post crop, before resizing and reformatting
83
85
  def image
84
- @image ||= Image.open(@source.name)
86
+ @image ||= open_image
85
87
  end
86
88
 
87
- def dest_dir
88
- File.dirname absolute_filename
89
+ def open_image
90
+ image_base = Image.open(@source.name)
91
+ image_base.combine_options do |i|
92
+ i.auto_orient
93
+ if @crop
94
+ i.gravity @gravity
95
+ i.crop @crop
96
+ end
97
+ end
98
+
99
+ @source_dimensions = { width: image_base.width, height: image_base.height }
100
+
101
+ image_base
89
102
  end
90
103
 
91
104
  def generate_image
@@ -94,30 +107,28 @@ module PictureTag
94
107
  write_image
95
108
  end
96
109
 
110
+ def quality
111
+ PictureTag.quality(@format)
112
+ end
113
+
97
114
  def process_image
98
115
  image.combine_options do |i|
99
116
  i.resize "#{@width}x"
100
- i.auto_orient
101
117
  i.strip
102
118
  end
103
119
 
104
120
  image.format @format
105
- image.quality PictureTag.quality(@format)
121
+ image.quality quality
106
122
  end
107
123
 
108
124
  def write_image
109
- check_dest_dir
125
+ FileUtils.mkdir_p(File.dirname(absolute_filename))
110
126
 
111
127
  image.write absolute_filename
112
128
 
113
129
  FileUtils.chmod(0o644, absolute_filename)
114
130
  end
115
131
 
116
- # Make sure destination directory exists
117
- def check_dest_dir
118
- FileUtils.mkdir_p(dest_dir) unless Dir.exist?(dest_dir)
119
- end
120
-
121
132
  def process_format(format)
122
133
  if format.casecmp('original').zero?
123
134
  @source.ext
@@ -8,6 +8,7 @@ module PictureTag
8
8
  # image. Call to_s on it to get the link.
9
9
  class ImgURI
10
10
  attr_reader :filename, :source_image
11
+
11
12
  def initialize(filename, source_image: false)
12
13
  @source_image = source_image
13
14
  @filename = filename
@@ -3,3 +3,4 @@ require_relative './instructions/configuration'
3
3
  require_relative './instructions/html_attributes'
4
4
  require_relative './instructions/preset'
5
5
  require_relative './instructions/tag_parser'
6
+ require_relative './instructions/arg_splitter'
@@ -0,0 +1,69 @@
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
+ case char
55
+ when '\\'
56
+ @escaped = true
57
+ when '"'
58
+ @in_quotes = !@in_quotes
59
+ @word << char
60
+ end
61
+ end
62
+
63
+ def close_escape(char)
64
+ @word << char
65
+ @escaped = false
66
+ end
67
+ end
68
+ end
69
+ 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.dest, pconfig['output']
33
+ File.join PictureTag.site.config['destination'], pconfig['output']
34
34
  end
35
35
 
36
36
  def nomarkdown?
@@ -3,6 +3,7 @@ module PictureTag
3
3
  # Handles the specific tag image set to construct.
4
4
  class Preset
5
5
  attr_reader :name
6
+
6
7
  def initialize(name)
7
8
  @name = name
8
9
  @content = build_preset
@@ -12,13 +13,6 @@ module PictureTag
12
13
  @content[key]
13
14
  end
14
15
 
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
16
  def formats
23
17
  @content['formats']
24
18
  end
@@ -41,15 +35,38 @@ module PictureTag
41
35
  end
42
36
  end
43
37
 
38
+ # Image widths to generate for a given media query.
39
+ def widths(media = nil)
40
+ setting_lookup('widths', 'media', media)
41
+ end
42
+
43
+ # Image quality setting, possibly dependent on format.
44
44
  def quality(format = nil)
45
- qualities = @content['format_quality'] || {}
46
- qualities.default = @content['quality']
45
+ setting_lookup('quality', 'format', format)
46
+ end
47
+
48
+ # Gravity setting (for imagemagick cropping)
49
+ def gravity(media = nil)
50
+ setting_lookup('gravity', 'media', media)
51
+ end
47
52
 
48
- qualities[format]
53
+ # Crop value
54
+ def crop(media = nil)
55
+ setting_lookup('crop', 'media', media)
49
56
  end
50
57
 
51
58
  private
52
59
 
60
+ # Return arbitrary setting values, taking their defaults into account.
61
+ # Ex: quality can be set for all image formats, or individually per
62
+ # format. Per-format settings should override the general setting.
63
+ def setting_lookup(setting, prefix, lookup)
64
+ media_values = @content[prefix + '_' + setting] || {}
65
+ media_values.default = @content[setting]
66
+
67
+ media_values[lookup]
68
+ end
69
+
53
70
  def build_preset
54
71
  # The _data/picture.yml file is optional.
55
72
  picture_data_file = grab_data_file
@@ -64,16 +81,24 @@ module PictureTag
64
81
  end
65
82
 
66
83
  def grab_data_file
84
+ search_data('presets') || search_data('markup_presets') || no_preset
85
+ end
86
+
87
+ def search_data(key)
67
88
  PictureTag.site
68
89
  .data
69
- .dig('picture', 'markup_presets', @name) || no_preset
90
+ .dig('picture', key, @name)
70
91
  end
71
92
 
72
93
  def no_preset
73
- Utils.warning(
74
- " Preset \"#{@name}\" not found in #{PictureTag.config['data_dir']}/"\
75
- + 'picture.yml under markup_presets key. Using default values.'
76
- )
94
+ unless @name == 'default'
95
+ Utils.warning(
96
+ <<~HEREDOC
97
+ Preset "#{@name}" not found in {PictureTag.config['data_dir']}/picture.yml
98
+ under markup_presets key. Using default values."
99
+ HEREDOC
100
+ )
101
+ end
77
102
 
78
103
  {}
79
104
  end
@@ -27,13 +27,35 @@ module PictureTag
27
27
  # These are our Media Query presets. It's really just a hash, and there
28
28
  # are no default values, so extracting this to its own class is overkill.
29
29
  def media_presets
30
- PictureTag.site.data.dig('picture', 'media_presets') || {}
30
+ search_data('media_queries') || search_data('media_presets') || {}
31
31
  end
32
32
 
33
33
  def source_images
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
+
55
+ def search_data(key)
56
+ PictureTag.site.data.dig('picture', key)
57
+ end
58
+
37
59
  def build_source_images
38
60
  source_names = params.source_names
39
61
  media_presets = params.media_presets
@@ -48,14 +70,6 @@ module PictureTag
48
70
 
49
71
  sources
50
72
  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
73
  end
60
74
  end
61
75
  end
@@ -1,105 +1,95 @@
1
1
  module PictureTag
2
2
  module Instructions
3
- # This tag takes the arguments handed to the liquid tag, and extracts the
4
- # preset name (if present), source image name(s), and associated media
5
- # queries (if present). The leftovers (html attributes) are handed off to
6
- # its respective class.
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
- def initialize(raw_params)
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
- @preset_name = grab_preset_name
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 = [] << strip_quotes(@params.shift)
29
+ @source_names = []
30
+ @geometries = {}
31
+ @gravities = {}
18
32
 
19
- # Detect and store arguments of the format 'media_query: img.jpg' as
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 add_media_source
31
- @media_presets << @params.shift.delete_suffix(':')
32
- @source_names << strip_quotes(@params.shift)
38
+ def split_params
39
+ ArgSplitter
40
+ .new(Utils.liquid_lookup(@raw_params))
41
+ .words
33
42
  end
34
43
 
35
- # First param is the preset name, unless it's a filename.
36
- def grab_preset_name
37
- if @params.first.include? '.'
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
- raw_params.each_char { |c| handle_char(c) }
48
+ parse_param(@params.first) until stop_here? @params.first
56
49
 
57
- add_word # We have to explicitly add the last one.
50
+ @leftovers = @params
58
51
  end
59
52
 
60
- def handle_char(char)
61
- # last character was a backslash:
62
- if @escaped
63
- close_escape char
53
+ def parse_param(param)
54
+ if param.match?(/[\w\-]+:$/)
55
+ add_media_source
64
56
 
65
- # char is a backslash or a quote:
66
- elsif char.match(/["\\]/)
67
- handle_special char
57
+ elsif Utils::GRAVITIES.include?(param.downcase)
58
+ @gravities[@media_presets.last] = @params.shift
68
59
 
69
- # Character isn't whitespace, or it's inside double quotes:
70
- elsif @in_quotes || char.match(/\S/)
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
- add_word
64
+ raise_error(param)
76
65
  end
77
66
  end
78
67
 
79
- def handle_special(char)
80
- if char == '\\'
81
- @escaped = true
82
- elsif char == '"'
83
- @in_quotes = !@in_quotes
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 add_word
89
- return if @word.empty?
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
- def close_escape(char)
96
- @word << char
97
- @escaped = false
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