distorted-jekyll 0.5.7 → 0.6.0

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