jekyll_picture_tag 1.6.0 → 1.7.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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +2 -0
  3. data/.travis.yml +11 -0
  4. data/Gemfile +2 -2
  5. data/Rakefile +28 -0
  6. data/contributing.md +67 -0
  7. data/docs/examples/_config.yml +10 -0
  8. data/{examples → docs/examples}/_data/picture.yml +39 -19
  9. data/docs/examples/post.md +46 -0
  10. data/docs/global_configuration.md +115 -0
  11. data/docs/installation.md +30 -0
  12. data/docs/migration.md +178 -0
  13. data/docs/notes.md +85 -0
  14. data/docs/presets.md +407 -0
  15. data/docs/readme.md +23 -0
  16. data/docs/usage.md +131 -0
  17. data/jekyll-picture-tag.gemspec +3 -12
  18. data/jekyll_picture_tag.gemspec +8 -3
  19. data/lib/jekyll-picture-tag.rb +5 -3
  20. data/lib/jekyll_picture_tag.rb +45 -42
  21. data/lib/jekyll_picture_tag/defaults/global.yml +0 -3
  22. data/lib/jekyll_picture_tag/defaults/presets.yml +1 -0
  23. data/lib/jekyll_picture_tag/generated_image.rb +60 -39
  24. data/lib/jekyll_picture_tag/img_uri.rb +55 -0
  25. data/lib/jekyll_picture_tag/instructions.rb +1 -102
  26. data/lib/jekyll_picture_tag/instructions/configuration.rb +30 -74
  27. data/lib/jekyll_picture_tag/instructions/html_attributes.rb +18 -27
  28. data/lib/jekyll_picture_tag/instructions/preset.rb +14 -3
  29. data/lib/jekyll_picture_tag/instructions/set.rb +61 -0
  30. data/lib/jekyll_picture_tag/instructions/tag_parser.rb +80 -23
  31. data/lib/jekyll_picture_tag/output_formats.rb +1 -1
  32. data/lib/jekyll_picture_tag/output_formats/{basics.rb → basic.rb} +24 -19
  33. data/lib/jekyll_picture_tag/output_formats/data_attributes.rb +2 -2
  34. data/lib/jekyll_picture_tag/output_formats/direct_url.rb +1 -3
  35. data/lib/jekyll_picture_tag/output_formats/img.rb +4 -4
  36. data/lib/jekyll_picture_tag/output_formats/naked_srcset.rb +5 -4
  37. data/lib/jekyll_picture_tag/output_formats/picture.rb +6 -16
  38. data/lib/jekyll_picture_tag/output_formats/readme.md +8 -15
  39. data/lib/jekyll_picture_tag/router.rb +98 -0
  40. data/lib/jekyll_picture_tag/source_image.rb +15 -23
  41. data/lib/jekyll_picture_tag/srcsets.rb +1 -1
  42. data/lib/jekyll_picture_tag/srcsets/{basics.rb → basic.rb} +22 -13
  43. data/lib/jekyll_picture_tag/srcsets/pixel_ratio.rb +6 -11
  44. data/lib/jekyll_picture_tag/srcsets/width.rb +3 -11
  45. data/lib/jekyll_picture_tag/utils.rb +32 -49
  46. data/lib/jekyll_picture_tag/version.rb +1 -1
  47. data/readme.md +70 -70
  48. metadata +97 -16
  49. data/bin/console +0 -14
  50. data/bin/setup +0 -7
  51. data/examples/_config.yml +0 -4
  52. data/examples/post.md +0 -18
@@ -1,47 +1,104 @@
1
1
  module PictureTag
2
2
  module Instructions
3
- # This class takes the string given to the jekyll tag, and extracts useful
4
- # information from it.
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.
5
7
  class TagParser
6
- attr_reader :preset_name, :source_images, :html_attributes_raw
8
+ attr_reader :preset_name, :source_names, :media_presets
7
9
  def initialize(raw_params)
8
- @params = PictureTag::Utils.liquid_lookup(raw_params).split
10
+ build_params PictureTag::Utils.liquid_lookup(raw_params)
9
11
 
10
12
  @preset_name = grab_preset_name
11
13
 
12
- # source_image keys are media queries, values are source images. The
13
- # first param specified will be our base image, so it has no associated
14
- # media query. Yes, nil can be a hash key.
15
- source_image_names = { nil => @params.shift }
14
+ # The first param specified will be our base image, so it has no
15
+ # associated media query.
16
+ @media_presets = []
17
+ @source_names = [] << strip_quotes(@params.shift)
16
18
 
17
- # Store source keys which fit a pattern in a hash.
18
- while @params.first =~ /[\w\-]+:/
19
- media_query = @params.shift[0..-2]
20
- source_image_names[media_query] = @params.shift
21
- end
22
-
23
- @source_images = build_sources(source_image_names)
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
24
23
 
25
- # Anything left will be html attributes
26
- @html_attributes_raw = @params.join(' ')
24
+ def leftovers
25
+ @params
27
26
  end
28
27
 
29
28
  private
30
29
 
30
+ def add_media_source
31
+ @media_presets << @params.shift.delete_suffix(':')
32
+ @source_names << strip_quotes(@params.shift)
33
+ end
34
+
31
35
  # First param is the preset name, unless it's a filename.
32
36
  def grab_preset_name
33
- if @params.first =~ %r{[\w./]+\.\w+}
37
+ if @params.first.include? '.'
34
38
  'default'
35
39
  else
36
40
  @params.shift
37
41
  end
38
42
  end
39
43
 
40
- # Takes filenames relative to JPT source directory. SourceImage instances
41
- # persist within a tag, allowing us to only perform some expensive File
42
- # operations once.
43
- def build_sources(names)
44
- names.transform_values { |n| PictureTag::SourceImage.new n }
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
54
+
55
+ raw_params.each_char { |c| handle_char(c) }
56
+
57
+ add_word # We have to explicitly add the last one.
58
+ end
59
+
60
+ def handle_char(char)
61
+ # last character was a backslash:
62
+ if @escaped
63
+ close_escape char
64
+
65
+ # char is a backslash or a quote:
66
+ elsif char.match(/["\\]/)
67
+ handle_special char
68
+
69
+ # Character isn't whitespace, or it's inside double quotes:
70
+ elsif @in_quotes || char.match(/\S/)
71
+ @word << char
72
+
73
+ # Character is whitespace outside of double quotes:
74
+ else
75
+ add_word
76
+ end
77
+ end
78
+
79
+ def handle_special(char)
80
+ if char == '\\'
81
+ @escaped = true
82
+ elsif char == '"'
83
+ @in_quotes = !@in_quotes
84
+ @word << char
85
+ end
86
+ end
87
+
88
+ def add_word
89
+ return if @word.empty?
90
+
91
+ @params << @word
92
+ @word = ''
93
+ end
94
+
95
+ def close_escape(char)
96
+ @word << char
97
+ @escaped = false
98
+ end
99
+
100
+ def strip_quotes(name)
101
+ name.delete_prefix('"').delete_suffix('"')
45
102
  end
46
103
  end
47
104
  end
@@ -1,4 +1,4 @@
1
- require_relative 'output_formats/basics'
1
+ require_relative 'output_formats/basic'
2
2
  require_relative 'output_formats/data_attributes'
3
3
  require_relative 'output_formats/auto'
4
4
  require_relative 'output_formats/img'
@@ -3,7 +3,7 @@ module PictureTag
3
3
  # own option.
4
4
  module OutputFormats
5
5
  # Generic functions common to all output formats.
6
- module Basics
6
+ class Basic
7
7
  include ObjectiveElements
8
8
 
9
9
  # Used for both the fallback image, and for the complete markup.
@@ -16,7 +16,7 @@ module PictureTag
16
16
 
17
17
  fallback = build_fallback_image
18
18
 
19
- add_src(img, fallback.name)
19
+ add_src(img, fallback.uri)
20
20
 
21
21
  add_alt(img, attributes['alt'])
22
22
 
@@ -40,26 +40,17 @@ module PictureTag
40
40
  end
41
41
 
42
42
  # Media is the media query associated with the desired source image.
43
- def build_srcset(media, format)
43
+ def build_srcset(source_image, format)
44
44
  if PictureTag.preset['pixel_ratios']
45
- build_pixel_ratio_srcset(media, format)
45
+ Srcsets::PixelRatio.new(source_image, format)
46
46
  else
47
- build_width_srcset(media, format)
47
+ Srcsets::Width.new(source_image, format)
48
48
  end
49
49
  end
50
50
 
51
- def build_pixel_ratio_srcset(media, format)
52
- Srcsets::PixelRatio.new(media: media, format: format)
53
- end
54
-
55
- def build_width_srcset(media, format)
56
- Srcsets::Width.new(media: media, format: format)
57
- end
58
-
59
51
  # Extracting these functions to their own methods for easy overriding.
60
- # They are destructive.
61
- def add_src(element, name)
62
- element.src = PictureTag.build_url name
52
+ def add_src(element, uri)
53
+ element.src = uri
63
54
  end
64
55
 
65
56
  def add_srcset(element, srcset)
@@ -81,9 +72,9 @@ module PictureTag
81
72
  # File, not HTML
82
73
  def build_fallback_image
83
74
  GeneratedImage.new(
84
- source_file: PictureTag.source_images[nil],
75
+ source_file: PictureTag.source_images.first,
85
76
  format: PictureTag.fallback_format,
86
- width: PictureTag.fallback_width
77
+ width: checked_fallback_width
87
78
  )
88
79
  end
89
80
 
@@ -93,7 +84,7 @@ module PictureTag
93
84
  # Kramdown is super picky about the {::nomarkdown} extension-- we have to
94
85
  # strip line breaks or nothing works.
95
86
  def nomarkdown_wrapper(content)
96
- "{::nomarkdown}#{content.delete("\n")}{:/nomarkdown}"
87
+ "{::nomarkdown}#{content.delete("\n").gsub(/> </, '><')}{:/nomarkdown}"
97
88
  end
98
89
 
99
90
  def anchor_tag(content)
@@ -103,6 +94,20 @@ module PictureTag
103
94
 
104
95
  content.add_parent anchor
105
96
  end
97
+
98
+ def checked_fallback_width
99
+ source = PictureTag.source_images.first
100
+ target = PictureTag.fallback_width
101
+
102
+ if target > source.width
103
+ Utils.warning "#{source.shortname} is smaller than the " \
104
+ "requested fallback width of #{target}px. Using #{source.width}" \
105
+ ' px instead.'
106
+ source.width
107
+ else
108
+ target
109
+ end
110
+ end
106
111
  end
107
112
  end
108
113
  end
@@ -9,8 +9,8 @@ module PictureTag
9
9
 
10
10
  private
11
11
 
12
- def add_src(element, name)
13
- element.attributes << { 'data-src' => PictureTag.build_url(name) }
12
+ def add_src(element, uri)
13
+ element.attributes << { 'data-src' => uri }
14
14
  end
15
15
 
16
16
  def add_srcset(element, srcset)
@@ -2,9 +2,7 @@ module PictureTag
2
2
  module OutputFormats
3
3
  # Represents a bare url you can use in another context, such as a direct
4
4
  # link, but keep the resizing functionality
5
- class DirectUrl
6
- include PictureTag::OutputFormats::Basics
7
-
5
+ class DirectUrl < Basic
8
6
  def to_s
9
7
  build_base_img.src
10
8
  end
@@ -2,11 +2,11 @@ module PictureTag
2
2
  module OutputFormats
3
3
  # Represents a bare <img> tag with a srcset attribute.
4
4
  # Used when <picture> is unnecessary.
5
- class Img
6
- include PictureTag::OutputFormats::Basics
7
-
5
+ class Img < Basic
8
6
  def srcset
9
- build_srcset(nil, PictureTag.preset['formats'].first)
7
+ @srcset ||= build_srcset(
8
+ PictureTag.source_images.first, PictureTag.formats.first
9
+ )
10
10
  end
11
11
 
12
12
  def base_markup
@@ -1,11 +1,12 @@
1
1
  module PictureTag
2
2
  module OutputFormats
3
3
  # Returns only a srcset attribute, for more custom or complicated markup.
4
- class NakedSrcset
5
- include Basics
6
-
4
+ class NakedSrcset < Basic
7
5
  def to_s
8
- build_srcset(nil, PictureTag.preset['formats'].first).to_s
6
+ image = PictureTag.source_images.first
7
+ format = PictureTag.formats.first
8
+
9
+ build_srcset(image, format).to_s
9
10
  end
10
11
  end
11
12
  end
@@ -2,25 +2,15 @@ module PictureTag
2
2
  module OutputFormats
3
3
  # Represents a <picture> tag, enclosing at least 2 <source> tags and an
4
4
  # <img> tag.
5
- class Picture
6
- include Basics
7
-
5
+ class Picture < Basic
8
6
  def srcsets
7
+ formats = PictureTag.formats
8
+ # Source images are provided in reverse order and must be flipped:
9
+ images = PictureTag.source_images.reverse
9
10
  sets = []
10
11
 
11
- PictureTag.preset['formats'].each do |format|
12
- # We have 2 dimensions here: formats, and source images. Formats are
13
- # provided in the order they must be returned, source images are
14
- # provided in the reverse (least to most preferable) and must be
15
- # flipped. We'll use an intermediate value to accomplish this.
16
- format_set = []
17
-
18
- # Source images are defined in the tag params, and associated with
19
- # media queries. The base (first provided) image has a key of nil.
20
- PictureTag.source_images.each_key do |media|
21
- format_set << build_srcset(media, format)
22
- end
23
- sets.concat format_set.reverse
12
+ formats.each do |format|
13
+ images.each { |image| sets << build_srcset(image, format) }
24
14
  end
25
15
 
26
16
  sets
@@ -1,16 +1,9 @@
1
1
  # Writing Output Formats
2
2
 
3
-
4
3
  ## Naming and Instantiating
5
4
 
6
- Names from the configuration wiAn output format is instantiated will be
7
- converted from snake case to title case (I'm not sure what it's called.) And
8
- instantiated.
9
-
10
- Example:
11
-
12
- In `_data/picture.yml`: `markup: example_format` will cause the plugin to use
13
- an instance of `ExampleFormat` (with no arguments.)
5
+ In the relevant `_data/picture.yml` preset, `markup: example_format` will cause
6
+ the plugin to use an instance of `ExampleFormat` (with no arguments.)
14
7
 
15
8
  You'll need to add an appropriate `require_relative` statement to
16
9
  `../output_formats.rb`
@@ -24,13 +17,13 @@ it this way because information flow was getting arduous; I was passing a lot
24
17
  of information to classes which only needed it to pass on to classes they
25
18
  instantiate.
26
19
 
27
- `PictureTag.source_images` returns a hash of the source images provided in the
28
- liquid tag. This relies on 2 properties of ruby hashes: They maintain their
29
- order, and `nil` is a perfectly good key. The first image (unqualified) is
30
- stored under `PictureTag.source_images[nil]`, and the rest of the keys are
31
- media queries named in `_data/picture.yml`.
20
+ `PictureTag.source_images` returns an array of the source images provided in
21
+ the liquid tag. The first one is the base image, the rest that follow are
22
+ associated with media queries. Check out `source_image.rb` to see what they
23
+ offer.
32
24
 
33
- There's a lot of information available, dig around in `../instructions.rb`. Output formats should only consume this information, never modify it.
25
+ There's a lot of information available, dig around in `../router.rb`.
26
+ Output formats should only consume this information, never modify it.
34
27
 
35
28
  ## Producing output
36
29
 
@@ -0,0 +1,98 @@
1
+ module PictureTag
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.
10
+ module Router
11
+ attr_accessor :instructions, :context
12
+ # Context forwarding
13
+
14
+ # Global site data
15
+ def site
16
+ @context.registers[:site]
17
+ end
18
+
19
+ # Page which tag is called from
20
+ def page
21
+ @context.registers[:page]
22
+ end
23
+
24
+ # Instructions forwarding
25
+
26
+ def config
27
+ @instructions.config
28
+ end
29
+
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
+ # Config Forwarding
51
+
52
+ def source_dir
53
+ config.source_dir
54
+ end
55
+
56
+ def dest_dir
57
+ config.dest_dir
58
+ end
59
+
60
+ def continue_on_missing?
61
+ config.continue_on_missing?
62
+ end
63
+
64
+ def cdn?
65
+ config.cdn?
66
+ end
67
+
68
+ def pconfig
69
+ config.pconfig
70
+ end
71
+
72
+ # Preset forwarding
73
+
74
+ def widths(media)
75
+ preset.widths(media)
76
+ end
77
+
78
+ def formats
79
+ preset.formats
80
+ end
81
+
82
+ def fallback_format
83
+ preset.fallback_format
84
+ end
85
+
86
+ def fallback_width
87
+ preset.fallback_width
88
+ end
89
+
90
+ def nomarkdown?
91
+ preset.nomarkdown?
92
+ end
93
+
94
+ def quality(format)
95
+ preset.quality(format)
96
+ end
97
+ end
98
+ end