jekyll_picture_tag 1.6.0 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
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