distorted-jekyll 0.5.6 → 0.5.7

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