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
@@ -0,0 +1,90 @@
1
+ module PictureTag
2
+ # Basically a wrapper class for vips. Handles image operations.
3
+ # Vips returns new images for Crop, resize, and autorotate operations.
4
+ # Quality, metadata stripping, and format are applied on write.
5
+ class ImageFile
6
+ def initialize(source, base)
7
+ @source = source
8
+ @base = base
9
+
10
+ build
11
+ end
12
+
13
+ private
14
+
15
+ attr_reader :source, :base
16
+
17
+ def build
18
+ notify
19
+
20
+ mkdir
21
+
22
+ image = load_image
23
+
24
+ image = process(image)
25
+
26
+ write(image)
27
+ end
28
+
29
+ # Processing pipeline
30
+ def process(image)
31
+ image = crop(image) if source.crop?
32
+
33
+ image = resize(image)
34
+
35
+ image.autorot
36
+ end
37
+
38
+ def write_opts
39
+ opts = PictureTag.preset['image_options'][@base.format] || {}
40
+
41
+ opts[:strip] = PictureTag.preset['strip_metadata']
42
+
43
+ # gifs don't accept a quality setting.
44
+ opts[:Q] = base.quality unless base.format == 'gif'
45
+
46
+ opts.transform_keys(&:to_sym)
47
+ end
48
+
49
+ def load_image
50
+ Vips::Image.new_from_file source.name
51
+ end
52
+
53
+ def write(image)
54
+ begin
55
+ image.write_to_file(base.absolute_filename, **write_opts)
56
+ rescue Vips::Error
57
+ # If vips can't handle it, fall back to imagemagick.
58
+ opts = write_opts.transform_keys do |key|
59
+ key == :Q ? :quality : key
60
+ end
61
+
62
+ image.magicksave(base.absolute_filename, **opts)
63
+ end
64
+
65
+ # Fix permissions. TODO - still necessary?
66
+ FileUtils.chmod(0o644, base.absolute_filename)
67
+ end
68
+
69
+ def notify
70
+ puts 'Generating new image file: ' + base.name
71
+ end
72
+
73
+ def resize(image)
74
+ image.resize(scale_value)
75
+ end
76
+
77
+ def crop(image)
78
+ image.smartcrop(*source.dimensions,
79
+ interesting: PictureTag.keep(@source.media_preset))
80
+ end
81
+
82
+ def scale_value
83
+ base.width.to_f / source.width
84
+ end
85
+
86
+ def mkdir
87
+ FileUtils.mkdir_p(File.dirname(base.absolute_filename))
88
+ end
89
+ end
90
+ end
@@ -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
@@ -17,7 +18,7 @@ module PictureTag
17
18
  # | domain | baseurl | directory | filename
18
19
  def to_s
19
20
  Addressable::URI.escape(
20
- File.join(domain, baseurl, directory, @filename)
21
+ File.join(domain, PictureTag.baseurl, directory, @filename)
21
22
  )
22
23
  end
23
24
 
@@ -28,21 +29,14 @@ module PictureTag
28
29
  # | domain | baseurl | j-p-t output dir | filename
29
30
  def domain
30
31
  if PictureTag.cdn?
31
- PictureTag.pconfig['cdn_url']
32
- elsif PictureTag.pconfig['relative_url']
32
+ PictureTag.cdn_url
33
+ elsif PictureTag.relative_url
33
34
  ''
34
35
  else
35
36
  PictureTag.config['url'] || ''
36
37
  end
37
38
  end
38
39
 
39
- # https://example.com/my-base-path/assets/generated-images/image.jpg
40
- # ^^^^^^^^^^^^^
41
- # | domain | baseurl | directory | filename
42
- def baseurl
43
- PictureTag.config['baseurl'] || ''
44
- end
45
-
46
40
  # https://example.com/my-base-path/assets/generated-images/image.jpg
47
41
  # ^^^^^^^^^^^^^^^^^^^^^^^^
48
42
  # | domain | baseurl | directory | filename
@@ -0,0 +1,138 @@
1
+ module PictureTag
2
+ # Handles a given source image file and its properties. Provides a speed
3
+ # advantage by storing expensive file reads and writes in instance variables,
4
+ # to be reused by many different generated images.
5
+ class SourceImage
6
+ attr_reader :shortname, :missing, :media_preset
7
+
8
+ # include MiniMagick
9
+
10
+ def initialize(relative_filename, media_preset = nil)
11
+ # /home/dave/my_blog/assets/images/somefolder/myimage.jpg
12
+ # ^^^^^^^^^^^^^^^^^^^^^^
13
+ @shortname = relative_filename
14
+ @media_preset = media_preset
15
+
16
+ @missing = missing?
17
+ check_cache
18
+ end
19
+
20
+ def digest
21
+ @digest ||= cache[:digest] || ''
22
+ end
23
+
24
+ def crop
25
+ PictureTag.crop(media_preset)
26
+ end
27
+
28
+ def crop?
29
+ !crop.nil?
30
+ end
31
+
32
+ def keep
33
+ PictureTag.keep(media_preset)
34
+ end
35
+
36
+ def dimensions
37
+ [width, height]
38
+ end
39
+
40
+ def width
41
+ return raw_width unless crop?
42
+
43
+ [raw_width, (raw_height * cropped_aspect)].min.round
44
+ end
45
+
46
+ def height
47
+ return raw_height unless crop?
48
+
49
+ [raw_height, (raw_width / cropped_aspect)].min.round
50
+ end
51
+
52
+ def cropped_aspect
53
+ return Utils.aspect_float(raw_width, raw_height) unless crop?
54
+
55
+ Utils.aspect_float(*crop.split(':').map(&:to_f))
56
+ end
57
+
58
+ # /home/dave/my_blog/assets/images/somefolder/myimage.jpg
59
+ # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
60
+ def name
61
+ @name ||= File.join(PictureTag.source_dir, @shortname)
62
+ end
63
+
64
+ # /home/dave/my_blog/assets/images/somefolder/myimage.jpg
65
+ # ^^^^^^^^^^^^^^^^^^
66
+ def base_name
67
+ @shortname.delete_suffix File.extname(@shortname)
68
+ end
69
+
70
+ # /home/dave/my_blog/assets/images/somefolder/myimage.jpg
71
+ # ^^^
72
+ def ext
73
+ @ext ||= File.extname(name)[1..].downcase
74
+ end
75
+
76
+ private
77
+
78
+ # pre-crop
79
+ def raw_width
80
+ @raw_width ||= @missing ? 999_999 : image.width
81
+ end
82
+
83
+ # pre-crop
84
+ def raw_height
85
+ @raw_height ||= @missing ? 999_999 : image.height
86
+ end
87
+
88
+ def cache
89
+ @cache ||= Cache.new(@shortname)
90
+ end
91
+
92
+ def missing?
93
+ if File.exist? name
94
+ false
95
+
96
+ elsif PictureTag.continue_on_missing?
97
+ Utils.warning(missing_image_warning)
98
+ true
99
+
100
+ else
101
+ raise ArgumentError, missing_image_error
102
+ end
103
+ end
104
+
105
+ def check_cache
106
+ return if @missing
107
+ return if cache[:digest] && PictureTag.fast_build?
108
+
109
+ update_cache if source_digest != cache[:digest]
110
+ end
111
+
112
+ def update_cache
113
+ cache[:digest] = source_digest
114
+
115
+ cache.write
116
+ end
117
+
118
+ def image
119
+ @image ||= Vips::Image.new_from_file(name)
120
+ end
121
+
122
+ def source_digest
123
+ @source_digest ||= Digest::MD5.hexdigest(File.read(name))
124
+ end
125
+
126
+ def missing_image_warning
127
+ "JPT Could not find #{name}. " \
128
+ 'Your site will have broken images. Continuing.'
129
+ end
130
+
131
+ def missing_image_error
132
+ <<~HEREDOC
133
+ Could not find #{name}. You can force the build to continue anyway by
134
+ setting "picture: ignore_missing_images: true" in "_config.yml".
135
+ HEREDOC
136
+ end
137
+ end
138
+ end
@@ -1,6 +1,70 @@
1
- require_relative './instructions/set'
2
- require_relative './instructions/configuration'
3
- require_relative './instructions/html_attributes'
4
- require_relative './instructions/preset'
5
- require_relative './instructions/tag_parser'
6
- require_relative './instructions/arg_splitter'
1
+ module PictureTag
2
+ # Instructions obtain, validate, and typecast/coerce input values. These
3
+ # inputs are either taken directly from jekyll's inputs, or handled by parsers
4
+ # first.
5
+ #
6
+ # Logic which affects only a single setting belongs in Instructions, while
7
+ # logic which affects multiple settings belongs in Parsers.
8
+ #
9
+ # Since instruction classes are so small, we define several per file in the
10
+ # instructions directory to save on boilerplate. All fall under the
11
+ # Instructions module namespace.
12
+ module Instructions
13
+ # Generic instruction, meant to be inherited. Children of this class must
14
+ # override the source method, and likely want to override valid?, coerce,
15
+ # and error_message as applicable.
16
+ class Instruction
17
+ # Memoized value of the given instruction. This is the public API.
18
+ def value
19
+ return @value if defined?(@value)
20
+
21
+ raise ArgumentError, error_message unless valid?
22
+
23
+ @value = coerced
24
+ end
25
+
26
+ private
27
+
28
+ # Source(s) of truth - where does this setting come from? Logic does not
29
+ # belong here. If information comes from muliple places, return an array
30
+ # or a hash.
31
+ def source
32
+ raise NotImplementedError
33
+ end
34
+
35
+ # Determine whether or not the input(s) are valid.
36
+ def valid?
37
+ true
38
+ end
39
+
40
+ # Convert input(s) to output.
41
+ def coerce
42
+ source
43
+ end
44
+
45
+ # Message returned if validation fails. Override this with something more
46
+ # helpful.
47
+ def error_message
48
+ "JPT - #{setting_name} received an invalid argument: #{source}"
49
+ end
50
+
51
+ def coerced
52
+ return @coerced if defined?(@coerced)
53
+
54
+ @coerced = coerce
55
+ end
56
+
57
+ def setting_name
58
+ Utils.snakeize(self.class.to_s.split('::').last)
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ # Load Parents
65
+ Dir[File.dirname(__FILE__) + '/instructions/parents/*.rb']
66
+ .sort.each { |file| require file }
67
+
68
+ # Load children:
69
+ Dir[File.dirname(__FILE__) + '/instructions/children/*.rb']
70
+ .sort.each { |file| require file }
@@ -0,0 +1,128 @@
1
+ module PictureTag
2
+ module Instructions
3
+ # PictureTag configuration in _config.yml
4
+ class Pconfig < Instruction
5
+ def source
6
+ PictureTag.config['picture']
7
+ end
8
+ end
9
+
10
+ # https://example.com/my-base-path/assets/generated-images/image.jpg
11
+ # ^^^^^^^^^^^^^
12
+ # | domain | baseurl | directory | filename
13
+ class Baseurl < Instruction
14
+ def source
15
+ {
16
+ ignore: PictureTag.pconfig['ignore_baseurl'],
17
+ key: PictureTag.pconfig['baseurl_key']
18
+ }
19
+ end
20
+
21
+ def coerce
22
+ return '' if source[:ignore]
23
+
24
+ PictureTag.config[source[:key]] || ''
25
+ end
26
+ end
27
+
28
+ # Whether to use relative or absolute URLs for images.
29
+ class RelativeUrl < EnvInstruction
30
+ def source
31
+ PictureTag.pconfig['relative_url']
32
+ end
33
+ end
34
+
35
+ # Image source directory
36
+ class SourceDir < Instruction
37
+ private
38
+
39
+ def source
40
+ [
41
+ PictureTag.site.source,
42
+ PictureTag.pconfig['source']
43
+ ]
44
+ end
45
+
46
+ def coerce
47
+ File.join(*source.map(&:to_s))
48
+ end
49
+
50
+ def setting_name
51
+ 'source directory'
52
+ end
53
+ end
54
+
55
+ # Image output directory
56
+ class DestDir < Instruction
57
+ private
58
+
59
+ def source
60
+ [
61
+ PictureTag.site.config['destination'],
62
+ PictureTag.pconfig['output']
63
+ ]
64
+ end
65
+
66
+ def coerce
67
+ File.join(*source.map(&:to_s))
68
+ end
69
+ end
70
+
71
+ # Whether to continue if a source image is missing
72
+ class ContinueOnMissing < EnvInstruction
73
+ def source
74
+ PictureTag.pconfig['ignore_missing_images']
75
+ end
76
+ end
77
+
78
+ # Whether to use a CDN
79
+ class Cdn < EnvInstruction
80
+ def source
81
+ {
82
+ url: PictureTag.pconfig['cdn_url'],
83
+ setting: PictureTag.pconfig['cdn_environments']
84
+ }
85
+ end
86
+
87
+ def coerce
88
+ source[:url] && super
89
+ end
90
+ end
91
+
92
+ # CDN URL
93
+ class CdnUrl < Instruction
94
+ def source
95
+ PictureTag.pconfig['cdn_url']
96
+ end
97
+
98
+ def valid?
99
+ require 'uri'
100
+ uri = URI(source)
101
+
102
+ # If the URI library can't parse it, it's not valid.
103
+ uri.scheme && uri.host
104
+ end
105
+
106
+ def error_message
107
+ <<~HEREDOC
108
+ cdn_url must be a valid URI in the following format: https://example.com/
109
+ current setting: #{source}
110
+ HEREDOC
111
+ end
112
+ end
113
+
114
+ # Disable JPT?
115
+ class Disabled < EnvInstruction
116
+ def source
117
+ PictureTag.pconfig['disabled']
118
+ end
119
+ end
120
+
121
+ # Fast build?
122
+ class FastBuild < EnvInstruction
123
+ def source
124
+ PictureTag.pconfig['fast_build']
125
+ end
126
+ end
127
+ end
128
+ end