distorted-jekyll 0.5.6 → 0.5.7

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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +661 -0
  3. data/README.md +6 -10
  4. data/lib/distorted-jekyll.rb +79 -0
  5. data/lib/distorted-jekyll/13th-style.rb +58 -0
  6. data/lib/distorted-jekyll/_config_default.yml +79 -0
  7. data/lib/distorted-jekyll/blocks.rb +16 -0
  8. data/lib/distorted-jekyll/error_code.rb +24 -0
  9. data/lib/distorted-jekyll/floor.rb +148 -0
  10. data/lib/distorted-jekyll/injection_of_love.rb +305 -0
  11. data/lib/distorted-jekyll/invoker.rb +400 -0
  12. data/lib/distorted-jekyll/molecule/abstract.rb +238 -0
  13. data/lib/distorted-jekyll/molecule/font.rb +29 -0
  14. data/lib/distorted-jekyll/molecule/image.rb +105 -0
  15. data/lib/distorted-jekyll/molecule/last-resort.rb +54 -0
  16. data/lib/distorted-jekyll/molecule/pdf.rb +88 -0
  17. data/lib/distorted-jekyll/molecule/svg.rb +59 -0
  18. data/lib/distorted-jekyll/molecule/text.rb +74 -0
  19. data/lib/distorted-jekyll/molecule/video.rb +43 -0
  20. data/lib/distorted-jekyll/monkey_business/jekyll/cleaner.rb +54 -0
  21. data/lib/distorted-jekyll/static/font.rb +42 -0
  22. data/lib/distorted-jekyll/static/image.rb +55 -0
  23. data/lib/distorted-jekyll/static/lastresort.rb +28 -0
  24. data/lib/distorted-jekyll/static/pdf.rb +53 -0
  25. data/lib/distorted-jekyll/static/state.rb +141 -0
  26. data/lib/distorted-jekyll/static/svg.rb +52 -0
  27. data/lib/distorted-jekyll/static/text.rb +57 -0
  28. data/lib/distorted-jekyll/static/video.rb +90 -0
  29. data/lib/distorted-jekyll/template/13th-style.css +78 -0
  30. data/lib/distorted-jekyll/template/error_code.liquid +3 -0
  31. data/lib/distorted-jekyll/template/font.liquid +32 -0
  32. data/lib/distorted-jekyll/template/image.liquid +32 -0
  33. data/lib/distorted-jekyll/template/lastresort.liquid +20 -0
  34. data/lib/distorted-jekyll/template/pdf.liquid +14 -0
  35. data/lib/distorted-jekyll/template/svg.liquid +32 -0
  36. data/lib/distorted-jekyll/template/text.liquid +32 -0
  37. data/lib/distorted-jekyll/template/video.liquid +11 -0
  38. metadata +41 -6
@@ -0,0 +1,238 @@
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
@@ -0,0 +1,29 @@
1
+ require 'set'
2
+
3
+ require 'distorted-jekyll/molecule/text'
4
+ require 'distorted-jekyll/static/font'
5
+
6
+ module Jekyll
7
+ module DistorteD
8
+ module Molecule
9
+ module Font
10
+
11
+ include Text
12
+
13
+ DRIVER = Cooltrainer::DistorteD::Font
14
+
15
+ MEDIA_TYPE = DRIVER::MEDIA_TYPE
16
+ MIME_TYPES = DRIVER::MIME_TYPES
17
+
18
+ ATTRS = DRIVER::ATTRS
19
+ ATTRS_DEFAULT = DRIVER::ATTRS_DEFAULT
20
+ ATTRS_VALUES = DRIVER::ATTRS_VALUES
21
+
22
+
23
+ def static_file(*args)
24
+ Jekyll::DistorteD::Static::Font.new(*args)
25
+ end
26
+ end # Font
27
+ end # Molecule
28
+ end # DistorteD
29
+ end # Jekyll
@@ -0,0 +1,105 @@
1
+ require 'set'
2
+
3
+ require 'distorted/image'
4
+ require 'distorted-jekyll/static/image'
5
+
6
+ module Jekyll
7
+ module DistorteD
8
+ module Molecule
9
+ module Image
10
+
11
+ # Reference these instead of reassigning them. Consistency is mandatory.
12
+ MEDIA_TYPE = Cooltrainer::DistorteD::Image::MEDIA_TYPE
13
+ MIME_TYPES = Cooltrainer::DistorteD::Image::MIME_TYPES
14
+
15
+ ATTRS = Cooltrainer::DistorteD::Image::ATTRS
16
+ ATTRS_DEFAULT = Cooltrainer::DistorteD::Image::ATTRS_DEFAULT
17
+ ATTRS_VALUES = Cooltrainer::DistorteD::Image::ATTRS_VALUES
18
+
19
+
20
+ # Returns the filename we should use in the oldschool <img> tag
21
+ # as a fallback for <picture> sources. This file should be a cropped
22
+ # variation, the same MIME::Type as the input media, with the largest
23
+ # resolution possible.
24
+ # Failing that, use the filename of the original media.
25
+ # TODO: Handle situations when the input media_type is not in the
26
+ # Set of output media_types. We should pick the largest cropped variation
27
+ # of any type in that case.
28
+ def fallback_img
29
+ biggest_ver = nil
30
+
31
+ # Computes a Set of non-nil MIME::Type.sub_types for all MIME::Types
32
+ # detected for the original media file.
33
+ sub_types = @mime.keep_if{ |m|
34
+ m.media_type == self.singleton_class.const_get(:MEDIA_TYPE)
35
+ }.map { |m|
36
+ m.sub_type
37
+ }.compact.to_set
38
+ files.keep_if{|f| f.key?(:width) or f.key?(:height)}.each{ |f|
39
+ if sub_types.include?(f[:type]&.sub_type)
40
+ if biggest_ver
41
+ if f[:width] > biggest_ver[:width]
42
+ biggest_ver = f
43
+ end
44
+ else
45
+ biggest_ver = f
46
+ end
47
+ end
48
+ }
49
+ # Return the filename of the biggest matched variation,
50
+ # otherwise use the original filename.
51
+ biggest_ver&.dig(:name) || @name
52
+ end
53
+
54
+ def outer_limits(*keys)
55
+ config = super
56
+ if config.empty?
57
+ Set[{
58
+ tag: :full,
59
+ crop: :none,
60
+ }]
61
+ else
62
+ config
63
+ end
64
+ end
65
+
66
+ def render_to_output_buffer(context, output)
67
+ super
68
+ begin
69
+ # Liquid doesn't seem able to reference symbolic keys,
70
+ # so convert everything to string for template.
71
+ # Remove full-size images from <sources> list before generating.
72
+ # Those should only be linked to, not displayed.
73
+ filez = files.keep_if{|f| f.key?(:width) or f.key?(:height)}.map{ |f|
74
+ f.transform_values(&:to_s).transform_keys(&:to_s)
75
+ }
76
+
77
+ output << parse_template.render({
78
+ 'name' => @name,
79
+ 'path' => @dd_dest,
80
+ 'alt' => attr_value(:alt),
81
+ 'title' => attr_value(:title),
82
+ 'href' => attr_value(:href),
83
+ 'caption' => attr_value(:caption),
84
+ 'loading' => attr_value(:loading),
85
+ 'sources' => filez,
86
+ 'fallback_img' => fallback_img,
87
+ })
88
+ rescue Liquid::SyntaxError => l
89
+ unless Jekyll.env == 'production'.freeze
90
+ output << parse_template(name: 'error_code'.freeze).render({
91
+ 'message' => l.message,
92
+ })
93
+ end
94
+ end
95
+ output
96
+ end
97
+
98
+ def static_file(*args)
99
+ Jekyll::DistorteD::Static::Image.new(*args)
100
+ end
101
+
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,54 @@
1
+ require 'set'
2
+
3
+ require 'distorted-jekyll/static/lastresort'
4
+
5
+ module Jekyll
6
+ module DistorteD
7
+ module Molecule
8
+ module LastResort
9
+
10
+ MEDIA_TYPE = 'lastresort'.freeze
11
+
12
+ # HACK HACK HACK
13
+ # Image Maps are a '90s Web relic, but I'm using this
14
+ # MIME::Type here to represent the generic fallback state.
15
+ # The MIME::Types library doesn't let me register custom
16
+ # types without shipping an entire custom type database,
17
+ # so I'm just going to use this since it will never
18
+ # be detected for a real file, and if it does then it will
19
+ # get an <img> tag anyway :)
20
+ MIME_TYPES = MIME::Types['application/x-imagemap'].to_set
21
+
22
+ ATTRS = Set[:alt, :title, :href, :caption]
23
+ ATTRS_DEFAULT = {}
24
+ ATTRS_VALUES = {}
25
+
26
+ def render_to_output_buffer(context, output)
27
+ super
28
+ begin
29
+ output << parse_template.render({
30
+ 'name' => @name,
31
+ 'basename' => File.basename(@name, '.*'),
32
+ 'path' => @url,
33
+ 'alt' => attr_value(:alt),
34
+ 'title' => attr_value(:title),
35
+ 'href' => attr_value(:href),
36
+ 'caption' => attr_value(:caption),
37
+ })
38
+ rescue Liquid::SyntaxError => l
39
+ unless Jekyll.env == 'production'.freeze
40
+ output << parse_template(name: 'error_code'.freeze).render({
41
+ 'message' => l.message,
42
+ })
43
+ end
44
+ end
45
+ output
46
+ end
47
+
48
+ def static_file(*args)
49
+ Jekyll::DistorteD::Static::LastResort.new(*args)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,88 @@
1
+ require 'set'
2
+
3
+ require 'distorted/pdf'
4
+ require 'distorted-jekyll/static/pdf'
5
+
6
+ module Jekyll
7
+ module DistorteD
8
+ module Molecule
9
+ module PDF
10
+
11
+ # Reference these instead of reassigning them. Consistency is mandatory.
12
+ MEDIA_TYPE = Cooltrainer::DistorteD::PDF::MEDIA_TYPE
13
+ SUB_TYPE = Cooltrainer::DistorteD::PDF::SUB_TYPE
14
+ MIME_TYPES = Cooltrainer::DistorteD::PDF::MIME_TYPES
15
+
16
+ PDF_OPEN_PARAMS = Cooltrainer::DistorteD::PDF::PDF_OPEN_PARAMS
17
+ ATTRS = Cooltrainer::DistorteD::PDF::ATTRS
18
+ ATTRS_DEFAULT = Cooltrainer::DistorteD::PDF::ATTRS_DEFAULT
19
+ ATTRS_VALUES = Cooltrainer::DistorteD::PDF::ATTRS_VALUES
20
+
21
+
22
+ def render_to_output_buffer(context, output)
23
+ super
24
+ begin
25
+ # TODO: iOS treats our <object> like an <img>,
26
+ # showing only the first page with transparency and stretched to the
27
+ # size of the container element.
28
+ # We will need something like PDF.js in an <iframe> to handle this.
29
+
30
+ # Generate a Hash of our PDF Open Params based on any given to the Liquid tag
31
+ # and any loaded from the defaults.
32
+ # https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/pdf_open_parameters.pdf
33
+ pdf_open_params = PDF_OPEN_PARAMS.map{ |p|
34
+ if ATTRS_VALUES.dig(p) == Cooltrainer::DistorteD::PDF::BOOLEAN_SET
35
+ # Support multiple ways people might want to express a boolean
36
+ if Set[0, '0'.freeze, false, 'false'.freeze].include?(attr_value(p))
37
+ [p, '0'.freeze]
38
+ elsif Set[1, '1'.freeze, true, 'true'.freeze].include?(attr_value(p))
39
+ [p, '1'.freeze]
40
+ end
41
+ else
42
+ [p, attr_value(p)]
43
+ end
44
+ }.to_h
45
+
46
+ # Generate the URL fragment version of the PDF Open Params.
47
+ # This would be difficult / impossible to construct within Liquid
48
+ # from the individual variables, so let's just do it out here.
49
+ pdf_open_params_url = pdf_open_params.keep_if{ |p,v|
50
+ v != nil && v != ""
51
+ }.map{ |k,v|
52
+ # The PDF Open Params docs specify `search` should be quoted.
53
+ if k == :search
54
+ "#{k}=\"#{v}\""
55
+ else
56
+ "#{k}=#{v}"
57
+ end
58
+ }.join('&')
59
+ Jekyll.logger.debug("#{@name} PDF Open Params:", "#{pdf_open_params} #{"\u21e8".encode('utf-8').freeze} #{pdf_open_params_url}")
60
+
61
+ output << parse_template.render({
62
+ 'name' => @name,
63
+ 'path' => @dd_dest,
64
+ 'alt' => attr_value(:alt),
65
+ 'title' => attr_value(:title),
66
+ 'height' => attr_value(:height),
67
+ 'width' => attr_value(:width),
68
+ 'caption' => attr_value(:caption),
69
+ 'pdf_open_params' => pdf_open_params_url,
70
+ })
71
+ rescue Liquid::SyntaxError => l
72
+ unless Jekyll.env == 'production'.freeze
73
+ output << parse_template(name: 'error_code'.freeze).render({
74
+ 'message' => l.message,
75
+ })
76
+ end
77
+ end
78
+ output
79
+ end
80
+
81
+ def static_file(*args)
82
+ Jekyll::DistorteD::Static::PDF.new(*args)
83
+ end
84
+
85
+ end # PDF
86
+ end # Molecule
87
+ end # DistorteD
88
+ end # Jekyll