distorted-jekyll 0.5.7 → 0.6.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.
@@ -0,0 +1,201 @@
1
+
2
+ require 'fileutils'
3
+ require 'set'
4
+
5
+ require 'distorted/error_code'
6
+
7
+
8
+ module Jekyll; end
9
+ module Jekyll::DistorteD; end
10
+
11
+ # This module implements the methods our tag needs in order to
12
+ # pretend to be a Jekyll::StaticFile so we don't need to
13
+ # redundantly re-implement a Generator and Jekyll::Cleaner.
14
+ module Jekyll::DistorteD::StaticState
15
+
16
+
17
+ ATTRIBUTES = Set[:title]
18
+
19
+
20
+ # Returns the to-be-written path of a single standard StaticFile.
21
+ # The value returned by this method is only the 'main' or 'original'
22
+ # (even if modified somehow) file and does not include the
23
+ # path/filenames of any variations.
24
+ # This method will be called by jekyll/lib/cleaner#new_files
25
+ # to generate the list of files that need to be build or rebuilt
26
+ # for a site. For this reason, this method shouldn't do any kind
27
+ # of checking the real filesystem, since e.g. its URL-based
28
+ # destdir might not exist yet if the Site.dest is completely blank.
29
+ def destination(dest_root)
30
+ File.join(dest_root, @relative_dest, @name)
31
+ end
32
+
33
+ # This method will be called by our monkey-patched Jekyll::Cleaner#new_files
34
+ # in place of the single-destination method usually used.
35
+ # This allows us to tell Jekyll about more than a single file
36
+ # that should be kept when regenerating the site.
37
+ # This makes DistorteD fast!
38
+ def destinations(dest_root)
39
+ wanted_files.map{|f| File.join(dest_root, @relative_dest, f)}
40
+ end
41
+
42
+ # HACK HACK HACK
43
+ # Jekyll does not pass this method a site.dest like it does write() and
44
+ # others, but I want to be able to short-circuit here if all the
45
+ # to-be-generated files already exist.
46
+ def modified?
47
+ # Assume modified for the sake of freshness :)
48
+ modified = true
49
+
50
+ site_dest = Jekyll::DistorteD::Floor::config(:destination).to_s
51
+ if Dir.exist?(site_dest)
52
+ if Dir.exist?(File.join(site_dest, @relative_dest))
53
+ extant_files = Dir.entries(File.join(site_dest, @relative_dest)).to_set
54
+
55
+ # TODO: Make this smarter. It's not enough that all the generated
56
+ # filenames should exist. Try a few more ways to detect subtler
57
+ # "changes to the source file since generation of variations.
58
+ if wanted_files.subset?(extant_files)
59
+ Jekyll.logger.debug(@name, "All variations present: #{wanted_files}")
60
+ modified = false
61
+ else
62
+ Jekyll.logger.debug(@name, "Missing variations: #{wanted_files - extant_files}")
63
+ end
64
+
65
+ end # relative_dest.exists?
66
+ end # site_dest.exists?
67
+ Jekyll.logger.debug("#{@name} modified?", modified)
68
+ return modified
69
+ end # modified?
70
+
71
+ # Whether to write the file to the filesystem
72
+ #
73
+ # Returns true unless the defaults for the destination path from
74
+ # _config.yml contain `published: false`.
75
+ def write?
76
+ publishable = defaults.fetch('published'.freeze, true)
77
+ return publishable unless @collection
78
+
79
+ publishable && @collection.write?
80
+ end
81
+
82
+ # Write the static file to the destination directory (if modified).
83
+ #
84
+ # dest - The String path to the destination dir.
85
+ #
86
+ # Returns false if the file was not modified since last time (no-op).
87
+ def write(dest_root)
88
+ plug
89
+ return false if File.exist?(path) && !modified?
90
+
91
+ # Create any directories to the depth of the intended destination.
92
+ FileUtils.mkdir_p(File.join(dest_root, @relative_dest))
93
+ # Save every desired variation of this image.
94
+ # This will be a Set of Hashes each describing the name, type,
95
+ # dimensions, attributes, etc of each output variation we want.
96
+ # Full-size outputs will have the special tag `:full`.
97
+ files.each { |variation|
98
+ type = variation&.dig(:type)
99
+ filename = File.join(dest_root, @relative_dest, variation&.dig(:name) || @name)
100
+
101
+ if self.respond_to?(type.distorted_method)
102
+ Jekyll.logger.debug("DistorteD::#{type.distorted_method}", filename)
103
+ self.send(type.distorted_method, filename, **variation)
104
+ elsif extname == ".#{type.preferred_extension}"
105
+ Jekyll.logger.debug(@name, <<~RAWCOPY
106
+ No #{type.distorted_method} method is defined,
107
+ but the intended output type #{type.to_s} is the same
108
+ as the input type, so I will fall back to copying the raw file.
109
+ RAWCOPY
110
+ )
111
+ copy_file(filename)
112
+ else
113
+ Jekyll.logger.error(@name, "Missing rendering method #{type.distorted_method}")
114
+ raise MediaTypeOutputNotImplementedError.new(filename, type, self.class.name)
115
+ end
116
+ }
117
+ end # write
118
+
119
+ private
120
+
121
+ def copy_file(dest_path, *a, **k)
122
+ if @site.safe || Jekyll.env == "production"
123
+ FileUtils.cp(path, dest_path)
124
+ else
125
+ FileUtils.copy_entry(path, dest_path)
126
+ end
127
+ end # copy_file
128
+
129
+ # Basic file properties
130
+
131
+ # Filename without the dot-and-extension.
132
+ def basename
133
+ File.basename(@name, '.*')
134
+ end
135
+
136
+ # Returns the extname /!\ including the dot /!\
137
+ def extname
138
+ File.extname(@name)
139
+ end
140
+
141
+ # Returns last modification time for this file.
142
+ def mtime
143
+ (@modified_time ||= File.stat(path).mtime).to_i
144
+ end
145
+
146
+ # Returns source file path.
147
+ def path
148
+ @path ||= begin
149
+ # Static file is from a collection inside custom collections directory
150
+ if !@collection.nil? && !@site.config['collections_dir'.freeze].empty?
151
+ File.join(*[@base, @site.config['collections_dir'.freeze], @dir, @name].compact)
152
+ else
153
+ File.join(*[@base, @dir, @name].compact)
154
+ end
155
+ end
156
+ end
157
+
158
+ # Returns a Hash keyed by MIME::Type objects with value as a Set of Hashes
159
+ # describing the media's output variations to be generated for each Type.
160
+ def variations
161
+ changes(abstract(:changes)).map{ |t|
162
+ [t, outer_limits(abstract(:outer_limits)).map{ |d|
163
+
164
+ # Don't change the filename of full-size variations
165
+ tag = d&.dig(:tag) != :full ? '-'.concat(d&.dig(:tag).to_s) : ''.freeze
166
+ # Use the original extname for LastResort
167
+ ext = t == CHECKING::YOU::OUT('application/x.distorted.last-resort') ? File.extname(@name) : t.preferred_extension
168
+ # Handle LastResort for files that might be a bare name with no extension
169
+ dot = '.'.freeze unless ext.nil? || ext&.empty?
170
+
171
+ d.merge({
172
+ # e.g. 'SomeImage-medium.jpg` but just `SomeImage.jpg` and not `SomeImage-full.jpg`
173
+ # for the full-resolution outputs.
174
+ # The default `.jpeg` preferred_extension is monkey-patched to `.jpg` because lol
175
+ :name => "#{basename}#{tag}#{dot}#{ext}",
176
+ })
177
+
178
+ }]
179
+ }.to_h
180
+ end
181
+
182
+ # Returns a flat Set of Hashes that each describe one variant of
183
+ # media file output that should exist for a given input file.
184
+ def files
185
+ filez = Set[]
186
+ variations.each_pair{ |t,v|
187
+ # Merge the type in to each variation Hash since we will no longer
188
+ # have it as the key to this Set in its container Hash.
189
+ v.each{ |d| filez.add(d.merge({:type => t})) }
190
+ }
191
+ filez
192
+ end
193
+
194
+ # Returns a Set of just the String filenames we want for this media.
195
+ # This will be used by `modified?` among others.
196
+ def wanted_files
197
+ files.map{|f| f[:name]}.to_set
198
+ end
199
+
200
+
201
+ end
@@ -6,6 +6,7 @@ div.distorted::after {
6
6
  }
7
7
  div.distorted.svg {background-color: #fbfbf8;}
8
8
  div.distorted.pdf > object {min-height: 76vh;}
9
+ div.distorted > video {object-fit: contain;}
9
10
  div.distorted-caption {
10
11
  color: #8888888;
11
12
  margin-top: 0.5em;
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: distorted-jekyll
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.7
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - okeeblow
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-28 00:00:00.000000000 Z
11
+ date: 2020-10-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -86,28 +86,14 @@ dependencies:
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: 0.5.7
89
+ version: 0.6.0
90
90
  type: :runtime
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: 0.5.7
97
- - !ruby/object:Gem::Dependency
98
- name: mime-types
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - "~>"
102
- - !ruby/object:Gem::Version
103
- version: '3.0'
104
- type: :runtime
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - "~>"
109
- - !ruby/object:Gem::Version
110
- version: '3.0'
96
+ version: 0.6.0
111
97
  - !ruby/object:Gem::Dependency
112
98
  name: kramdown
113
99
  requirement: !ruby/object:Gem::Requirement
@@ -122,20 +108,6 @@ dependencies:
122
108
  - - "~>"
123
109
  - !ruby/object:Gem::Version
124
110
  version: '2.0'
125
- - !ruby/object:Gem::Dependency
126
- name: ruby-filemagic
127
- requirement: !ruby/object:Gem::Requirement
128
- requirements:
129
- - - "~>"
130
- - !ruby/object:Gem::Version
131
- version: '0.7'
132
- type: :runtime
133
- prerelease: false
134
- version_requirements: !ruby/object:Gem::Requirement
135
- requirements:
136
- - - "~>"
137
- - !ruby/object:Gem::Version
138
- version: '0.7'
139
111
  description: Jekyll::DistorteD is a Liquid tag for embedding media in a Jekyll site
140
112
  with automatic thumbnailing, cropping, and format conversion.
141
113
  email:
@@ -150,27 +122,18 @@ files:
150
122
  - lib/distorted-jekyll/13th-style.rb
151
123
  - lib/distorted-jekyll/_config_default.yml
152
124
  - lib/distorted-jekyll/blocks.rb
153
- - lib/distorted-jekyll/error_code.rb
154
125
  - lib/distorted-jekyll/floor.rb
155
- - lib/distorted-jekyll/injection_of_love.rb
156
126
  - lib/distorted-jekyll/invoker.rb
157
- - lib/distorted-jekyll/molecule/abstract.rb
127
+ - lib/distorted-jekyll/md_injection.rb
158
128
  - lib/distorted-jekyll/molecule/font.rb
159
129
  - lib/distorted-jekyll/molecule/image.rb
160
- - lib/distorted-jekyll/molecule/last-resort.rb
130
+ - lib/distorted-jekyll/molecule/lastresort.rb
161
131
  - lib/distorted-jekyll/molecule/pdf.rb
162
132
  - lib/distorted-jekyll/molecule/svg.rb
163
133
  - lib/distorted-jekyll/molecule/text.rb
164
134
  - lib/distorted-jekyll/molecule/video.rb
165
135
  - lib/distorted-jekyll/monkey_business/jekyll/cleaner.rb
166
- - lib/distorted-jekyll/static/font.rb
167
- - lib/distorted-jekyll/static/image.rb
168
- - lib/distorted-jekyll/static/lastresort.rb
169
- - lib/distorted-jekyll/static/pdf.rb
170
- - lib/distorted-jekyll/static/state.rb
171
- - lib/distorted-jekyll/static/svg.rb
172
- - lib/distorted-jekyll/static/text.rb
173
- - lib/distorted-jekyll/static/video.rb
136
+ - lib/distorted-jekyll/static_state.rb
174
137
  - lib/distorted-jekyll/template/13th-style.css
175
138
  - lib/distorted-jekyll/template/error_code.liquid
176
139
  - lib/distorted-jekyll/template/font.liquid
@@ -1,24 +0,0 @@
1
- require 'distorted/error_code'
2
-
3
-
4
- module Jekyll
5
- module DistorteD
6
- class OutOfDateLibraryError < LoadError
7
- end
8
-
9
- # The built-in NotImplementedError is for "when a feature is not implemented
10
- # on the current platform", so make our own more appropriate ones.
11
- class MediaTypeNotImplementedError < StandardDistorteDError
12
- attr_reader :media_type, :name
13
- def initialize(name)
14
- super("No supported media type for #{name}")
15
- end
16
- end
17
- class MediaTypeNotFoundError < StandardDistorteDError
18
- attr_reader :media_type, :name
19
- def initialize(name)
20
- super("Failed to detect media type for #{name}")
21
- end
22
- end
23
- end
24
- end
@@ -1,238 +0,0 @@
1
- require 'set'
2
-
3
- require 'distorted-jekyll/floor'
4
-
5
- require 'jekyll'
6
- require 'liquid/errors'
7
- require 'liquid/template'
8
- require 'mime/types'
9
-
10
-
11
- module Jekyll
12
- module DistorteD
13
- module Molecule
14
- module Abstract
15
-
16
- # This list should contain global attributes only, as symbols.
17
- # The final attribute set will be this + the media-type-specific set.
18
- # https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes
19
- GLOBAL_ATTRS = Set[:title]
20
-
21
-
22
- # Returns a Set of Arrays of search keys to try in config()
23
- def search_keys(*keys)
24
- # It's likely that we will get a default argument of [nil]
25
- # here due to the output of attr_value(:whatever) for unset attrs.
26
- keys = keys.compact
27
- # If a search key path was given, construct one based
28
- # on the MIME::Type union Set between the source media
29
- # and the plugged MediaMolecule.
30
- if keys.empty? or keys.all?{|k| k.nil?}
31
- try_keys = @mime.map{ |t|
32
- # Use only the first part of complex sub_types like 'svg+xml'
33
- [t.media_type, t.sub_type.split('+').first].compact
34
- }
35
- else
36
- # Or use a user-provided config path.
37
- try_keys = Set[keys]
38
- end
39
- end
40
-
41
- # Loads configuration data telling us how to open certain
42
- # types of files.
43
- def welcome(*keys)
44
- # Try each set of keys until we find a match
45
- for try in search_keys(*keys)
46
- tried = Jekyll::DistorteD::Floor::config(
47
- Jekyll::DistorteD::Floor::CONFIG_ROOT,
48
- :welcome,
49
- *try,
50
- )
51
- # Is the YAML config of the appropriate structure?
52
- if tried.is_a?(Hash)
53
- # Non-Hashes may not respond to `empty?`
54
- unless tried.empty?
55
- return tried
56
- end
57
- end
58
- end
59
- end
60
-
61
- # Load configuration telling us what media-types to generate
62
- # for any given media-type input.
63
- def changes(*keys)
64
- out = Set[]
65
- # `changes` media-type[sub_type] config will contain information about
66
- # what variations output format are desired for what input format,
67
- # e.g. {:image => {:jpeg => Set['image/jpeg', 'image/webp']}}
68
- # It is not automatically implied that the source format is also
69
- # an output format!
70
- for try in search_keys(*keys)
71
- tried = Jekyll::DistorteD::Floor::config(
72
- Jekyll::DistorteD::Floor::CONFIG_ROOT,
73
- :changes,
74
- *try,
75
- )
76
- if tried.is_a?(Enumerable) and tried.all?{|t| t.is_a?(String)} and not tried.empty?
77
- tried.each{ |t|
78
- # MIME::Type.new() won't give us a usable Type object:
79
- #
80
- # irb> MIME::Types['image/svg+xml'].first.preferred_extension
81
- # => "svg"
82
- # irb> MIME::Type.new('image/svg+xml').preferred_extension
83
- # => nil
84
- out.merge(MIME::Types[t])
85
- }
86
- end
87
- end
88
-
89
- # If the config didn't give us any MIME::Type changes
90
- # then we will just output the same type we loaded.
91
- if out.empty?
92
- return @mime
93
- else
94
- return out
95
- end
96
- end
97
-
98
- # Loads configuration telling us what variations to generate for any
99
- # given type of file, or for an arbitrary key hierarchy.
100
- def outer_limits(*keys)
101
- out = Set[]
102
- # See if any config data exists for each given key hierarchy,
103
- # but under the root DistorteD config key.
104
- for try in search_keys(*keys)
105
- tried = Jekyll::DistorteD::Floor::config(
106
- Jekyll::DistorteD::Floor::CONFIG_ROOT,
107
- :outer_limits,
108
- *try,
109
- )
110
-
111
- # Is the YAML config of the appropriate structure?
112
- # Merge a shallow copy of it with the Liquid-given attrs.
113
- # If we don't take a copy the attrs will be memoized into the config.
114
- if tried.is_a?(Enumerable) and tried.all?{|t| t.is_a?(Hash)} and not tried.empty?
115
- out.merge(tried.dup.map{ |d| d.merge(attrs) })
116
- end
117
- end
118
-
119
- # We should output something if the config didn't give us anything.
120
- # This is kind of a mess right now with redundancies in the call sites
121
- # of things like Molecule::Image. I'll come up with a better general-
122
- # purpose fallback solution at some point, but for now this will get
123
- # non-Image StaticFiles working with no config :)
124
- if out.empty?
125
- out << {
126
- :tag => :full,
127
- :crop => :none,
128
- }
129
- end
130
-
131
- return out
132
- end
133
-
134
- # Returns a Hash of any attribute provided to DD's Liquid tag and its value.
135
- def attrs
136
- # Value of every Molecule-defined attr will be nil if that attr
137
- # is not provided to our Liquid tag.
138
- @attrs.keep_if{|attr,val| val != nil}
139
- end
140
-
141
- # Returns the value for an attribute as given to the Liquid tag,
142
- # the default value if the given value is not in the accepted Set,
143
- # or nil for unset attrs with no default defined.
144
- def attr_value(attribute)
145
- # Set of all supported attributes:
146
- # - Global output-element attributes
147
- # - Molecule-specific output-element attributes
148
- # - Filetype change and output-template config paths
149
- accepted_attrs = self.singleton_class.const_get(:GLOBAL_ATTRS) + self.singleton_class.const_get(:ATTRS) + Set[:changes, :outer_limits]
150
-
151
- # Set of acceptable values for the given attribute, e.g. Image::loading => Set[:eager, :lazy]
152
- # Will be empty if this attribute takes freeform input (like `title` or `alt`)
153
- accepted_vals = self.singleton_class.const_get(:ATTRS_VALUES)&.dig(attribute)
154
-
155
- # The value, if any, provided to our Liquid tag for this attr.
156
- liquid_val = attrs&.dig(attribute)
157
-
158
- # Is the requested attribute name defined as an accepted attribute
159
- # either globally or within the plugged MediaMolecule?
160
- if accepted_attrs.include?(attribute.to_sym)
161
-
162
- # Does this attr define a set of acceptable values?
163
- if accepted_vals.is_a?(Set)
164
- # Yes, it does. Is the Liquid-given value in that Set of acceptable values?
165
- if accepted_vals.include?(liquid_val) or accepted_vals.include?(liquid_val&.to_sym) or accepted_vals.include?(liquid_val&.to_s)
166
-
167
- # Yes, it is! Use it.
168
- liquid_val.to_s
169
- else
170
- # No, it isn't. Warn and return the default.
171
- unless liquid_val.nil?
172
- Jekyll.logger.warn('DistorteD', "#{liquid_val.to_s} is not an acceptable value for #{attribute.to_s}: #{accepted_vals}")
173
- end
174
- self.singleton_class.const_get(:ATTRS_DEFAULT)&.dig(attribute).to_s
175
- end
176
- elsif accepted_vals.is_a?(Regexp)
177
- if accepted_vals =~ liquid_val.to_s
178
- liquid_val.to_s
179
- else
180
- unless liquid_val.nil?
181
- Jekyll.logger.warn('DistorteD', "#{liquid_val.to_s} is not a Regexp match for #{attribute.to_s}: #{accepted_vals}")
182
- end
183
- self.singleton_class.const_get(:ATTRS_DEFAULT)&.dig(attribute)
184
- end
185
- else
186
- # No, this attribute does not define a Set of acceptable values.
187
- # The freeform Liquid-given value is fine, but if it's nil
188
- # we can still try for a default.
189
- if liquid_val.nil?
190
- self.singleton_class.const_get(:ATTRS_DEFAULT)&.dig(attribute)
191
- else
192
- liquid_val
193
- end
194
- end
195
- else
196
- Jekyll.logger.error('DistorteD', "#{attribute.to_s} is not a supported attribute")
197
- nil
198
- end
199
- end
200
-
201
- # Returns a Hash keyed by MIME::Type objects with value as a Set of Hashes
202
- # describing the media's output variations to be generated for each Type.
203
- def variations
204
- changes(attr_value(:changes)).map{ |t|
205
- [t, outer_limits(attr_value(:outer_limits)).map{ |d|
206
- d.merge({
207
- # e.g. 'SomeImage-medium.jpg` but just `SomeImage.jpg` and not `SomeImage-full.jpg`
208
- # for the full-resolution outputs.
209
- # The default `.jpeg` preferred_extension is monkey-patched to `.jpg` because lol
210
- :name => "#{File.basename(@name, '.*')}#{'-'.concat(d&.dig(:tag).to_s) if d&.dig(:tag) != :full}.#{t.preferred_extension}",
211
- })
212
- }]
213
- }.to_h
214
- end
215
-
216
- # Returns a flat Set of Hashes that each describe one variant of
217
- # media file output that should exist for a given input file.
218
- def files
219
- filez = Set[]
220
- variations.each_pair{ |t,v|
221
- # Merge the type in to each variation Hash since we will no longer
222
- # have it as the key to this Set in its container Hash.
223
- v.each{ |d| filez.add(d.merge({:type => t})) }
224
- }
225
- filez
226
- end
227
-
228
- # Returns a Set of just the String filenames we want for this media.
229
- # This will be used by `modified?` among others.
230
- def filenames
231
- files.map{|f| f[:name]}.to_set
232
- end
233
-
234
-
235
- end # Abstract
236
- end # Molecule
237
- end # DistorteD
238
- end # Jekyll