distorted-jekyll 0.5.4 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +661 -0
  3. data/README.md +7 -11
  4. data/lib/distorted-jekyll.rb +75 -0
  5. data/lib/distorted-jekyll/13th-style.css +79 -0
  6. data/lib/distorted-jekyll/13th-style.rb +58 -0
  7. data/lib/distorted-jekyll/_config_default.yml +63 -0
  8. data/lib/distorted-jekyll/blocks.rb +16 -0
  9. data/lib/distorted-jekyll/invoker.rb +234 -0
  10. data/lib/distorted-jekyll/liquid_liquid.rb +255 -0
  11. data/lib/distorted-jekyll/liquid_liquid/anchor.liquid +5 -0
  12. data/lib/distorted-jekyll/liquid_liquid/anchor_inline.liquid +1 -0
  13. data/lib/distorted-jekyll/liquid_liquid/embed.liquid +1 -0
  14. data/lib/distorted-jekyll/liquid_liquid/img.liquid +1 -0
  15. data/lib/distorted-jekyll/liquid_liquid/object.liquid +5 -0
  16. data/lib/distorted-jekyll/liquid_liquid/picture.liquid +15 -0
  17. data/lib/distorted-jekyll/liquid_liquid/picture.rb +48 -0
  18. data/lib/distorted-jekyll/liquid_liquid/picture_source.liquid +1 -0
  19. data/lib/distorted-jekyll/liquid_liquid/root.liquid +5 -0
  20. data/lib/distorted-jekyll/liquid_liquid/video.liquid +5 -0
  21. data/lib/distorted-jekyll/liquid_liquid/video_source.liquid +1 -0
  22. data/lib/distorted-jekyll/md_injection.rb +310 -0
  23. data/lib/distorted-jekyll/media_molecule.rb +20 -0
  24. data/lib/distorted-jekyll/media_molecule/font.rb +21 -0
  25. data/lib/distorted-jekyll/media_molecule/image.rb +15 -0
  26. data/lib/distorted-jekyll/media_molecule/never_let_you_down.rb +28 -0
  27. data/lib/distorted-jekyll/media_molecule/pdf.rb +108 -0
  28. data/lib/distorted-jekyll/media_molecule/svg.rb +20 -0
  29. data/lib/distorted-jekyll/media_molecule/text.rb +23 -0
  30. data/lib/distorted-jekyll/media_molecule/video.rb +45 -0
  31. data/lib/distorted-jekyll/monkey_business/jekyll/cleaner.rb +121 -0
  32. data/lib/distorted-jekyll/static_state.rb +160 -0
  33. data/lib/distorted-jekyll/the_setting_sun.rb +179 -0
  34. metadata +37 -34
data/README.md CHANGED
@@ -1,19 +1,16 @@
1
- # Cooltrainer::DistorteD
1
+ # Jekyll::DistorteD
2
2
 
3
- `DistorteD` is a multimedia framework for Jekyll websites.
4
-
5
- Right now this repo contains two Gems:
6
- - [`DistorteD-Jekyll`](https://rubygems.org/gems/distorted-jekyll) contains anything and everything that depends on Jekyll.
7
- - [`DistorteD-Ruby`](https://rubygems.org/gems/distorted) contains just the abstract media file format handling code. It's separate so I can use those functions in other contexts and/or easily replace the Ruby core if necessary.
3
+ `DistorteD-Jekyll` is a multimedia framework for Jekyll websites.
8
4
 
9
5
  ## Motivation
10
6
 
11
- DD is my solution for displaying photos, videos, and other types of media on [cooltrainer.org](https://cooltrainer.org) due to my dissatisfaction with every other solution I could find.
7
+ DistorteD is my solution for displaying photos, videos, and other types of media on [cooltrainer.org](https://cooltrainer.org) due to my dissatisfaction with every other solution I could find.
12
8
 
13
9
  My previous approach was similar to what's [described here](https://eduardoboucas.com/blog/2014/12/07/including-and-managing-images-in-jekyll.html), with small/medium/large image size variations generated with [Jekyll-MiniMagick](https://github.com/MattKevan/Jekyll-MiniMagick-new).
14
10
 
15
11
  Here are some already-existing ways to put pictures on your Jekyll site that are worth your consideration before choosing DistorteD:
16
12
 
13
+ - Octopress' [image_tag](https://github.com/imathis/octopress/blob/master/plugins/image_tag.rb) plugin.
17
14
  - [jekyll-responsive-image](https://github.com/wildlyinaccurate/jekyll-responsive-image)
18
15
  - [jekyll_picture_tag](https://rbuchberger.github.io/jekyll_picture_tag/)
19
16
  - [jekyll-gallery-generator](https://github.com/ggreer/jekyll-gallery-generator)
@@ -95,7 +92,7 @@ will be transformed into.
95
92
  %}
96
93
  ```
97
94
 
98
- Or, for a DD grid:
95
+ or, for a DD grid:
99
96
 
100
97
  ```
101
98
  {% distort %}
@@ -157,11 +154,10 @@ Clone the DistorteD repository and modify your Jekyll `Gemfile` to refer to your
157
154
 
158
155
  ```
159
156
  gem 'distorted-jekyll', :path => '~/repos/DistorteD/DistorteD-Jekyll/'[, :branch => 'NEW-SENSATION']
160
- gem 'distorted', :path => '~/repos/DistorteD/DistorteD-Ruby/'[, :branch => 'NEW-SENSATION']
161
157
  ```
162
158
 
163
- The `DistorteD-Jekyll` Gem will automatically use its local sibling `DistorteD-Ruby` Gem if used in this way.
159
+ The `DistorteD-Jekyll` Gem will automatically use its local sibling `DistorteD-Floor` Gem if used in this way.
164
160
 
165
161
  ## License
166
162
 
167
- DistorteD is available as open source under the terms of the [GNU Affero General Public License version 3](https://opensource.org/licenses/AGPL-3.0).
163
+ The gem is available as open source under the terms of the [GNU Affero General Public License version 3](https://opensource.org/licenses/AGPL-3.0).
@@ -0,0 +1,75 @@
1
+ require 'distorted-jekyll/13th-style'
2
+ require 'distorted-jekyll/blocks'
3
+ require 'distorted-jekyll/md_injection'
4
+ require 'distorted-jekyll/invoker'
5
+
6
+
7
+ FATAL_FURY = true
8
+ UPDATE_RUBY = "Please use DistorteD with Ruby 2.7.0 or later"
9
+ def update_ruby
10
+ if defined? RUBY_PLATFORM
11
+ if (/freebsd/ =~ RUBY_PLATFORM) != nil
12
+ return 'pkg install lang/ruby27'
13
+ elsif (/darwin/ =~ RUBY_PLATFORM) != nil
14
+ return 'brew upgrade ruby'
15
+ elsif (/win/ =~ RUBY_PLATFORM) != nil
16
+ return 'https://rubyinstaller.org/'
17
+ elsif (/linux/ =~ RUBY_PLATFORM) != nil
18
+ if File.exists?('/etc/lsb-release')
19
+ lsb = File.read('/etc/lsb-release')
20
+ if (/Ubuntu|LinuxMint/ =~ lsb) != nil
21
+ return 'https://www.brightbox.com/docs/ruby/ubuntu/#installation'
22
+ end
23
+ end
24
+ end
25
+ end
26
+ return 'https://github.com/rbenv/ruby-build'
27
+ end
28
+
29
+
30
+ # I want to be able to use:
31
+ # - Array#dig and Hash#dig (Ruby 2.3): https://bugs.ruby-lang.org/issues/11643
32
+ # - Lonely operator (Ruby 2.3): https://bugs.ruby-lang.org/issues/11537
33
+ # - Hash#transform_keys (Ruby 2.5): https://bugs.ruby-lang.org/issues/13583
34
+ # - Enumerable#filter_map (Ruby 2.7): https://bugs.ruby-lang.org/issues/5663
35
+ # https://blog.saeloun.com/2019/05/25/ruby-2-7-enumerable-filter-map.html
36
+ # - 'Real' kwargs in preparation for Ruby 3: https://bugs.ruby-lang.org/issues/14183
37
+ # https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/
38
+ if [
39
+ Hash.method_defined?(:dig), # 2.3
40
+ Hash.method_defined?(:transform_keys), # 2.5
41
+ Enumerable.method_defined?(:filter_map), # 2.7
42
+ ].all?
43
+ # Monkey-patch Jekyll::Cleaner to not nuke DistorteD-generated variations
44
+ # for our media files. This makes DistorteD fast!
45
+ require 'distorted-jekyll/monkey_business/jekyll/cleaner'
46
+
47
+ # Register DistorteD's entrypoint class with Liquid.
48
+ # `Invoker` will mix in the proper handler module for the given media.
49
+ Liquid::Template.register_tag('distorted', Jekyll::DistorteD::Invoker)
50
+
51
+ # Register a block version for arranging multiple pieces of media.
52
+ Liquid::Template.register_tag('distort', Jekyll::DistorteD::BLOCKS)
53
+
54
+ # Register a tag for basic DistorteD CSS.
55
+ Liquid::Template.register_tag('13th_style', Jekyll::DistorteD::ThirteenthStyle)
56
+
57
+ # Transform Markdown image syntax ![alt](url.jpg "title")
58
+ # to instances of our liquid tag {% distorted %}
59
+ # Available hooks can be seen here:
60
+ # https://github.com/jekyll/jekyll/blob/master/lib/jekyll/hooks.rb
61
+ # `:documents` does not seem to include `_pages` but does include `_posts`.
62
+ Jekyll::Hooks.register(:pages, :pre_render, &md_injection)
63
+ Jekyll::Hooks.register(:posts, :pre_render, &md_injection)
64
+
65
+ else
66
+ # Example of how this looks with the outdated Ruby 2.5 on my Mint 19 laptop:
67
+ #
68
+ # Bundler::GemRequireError: There was an error while trying to load the gem 'distorted-jekyll'.
69
+ # Gem Load Error is: Please use DistorteD with Ruby 2.7.0 or later: https://www.brightbox.com/docs/ruby/ubuntu/#installation
70
+ if FATAL_FURY
71
+ raise RuntimeError.new("#{UPDATE_RUBY}: #{update_ruby}")
72
+ else
73
+ Jekyll.logger.info('DistorteD', "#{UPDATE_RUBY}: #{update_ruby}")
74
+ end
75
+ end
@@ -0,0 +1,79 @@
1
+ div.distorted {text-align: center;}
2
+ div.distorted::after {
3
+ content: '';
4
+ display: block;
5
+ clear: left;
6
+ }
7
+ div.distorted.svg {background-color: #fbfbf8;}
8
+ div.distorted.pdf > object {min-height: 76vh;}
9
+ div.distorted > video {object-fit: contain;}
10
+ div.distorted-caption {
11
+ color: #8888888;
12
+ margin-top: 0.5em;
13
+ }
14
+ div.distorted-block {
15
+ width: 100%;
16
+ clear: both;
17
+ }
18
+ div.distorted-block > div.distorted {
19
+ display: block;
20
+ box-sizing: border-box;
21
+ float: left;
22
+ border: 4px solid transparent;
23
+ margin: 0px;
24
+ text-align: center;
25
+ width: 100%;
26
+ }
27
+ @media screen and (min-width: 40em) {
28
+ div.distorted-block > div.distorted {
29
+ width: 50%;
30
+ }
31
+ div.distorted-block > div.distorted:only-child {
32
+ width: 100%;
33
+ }
34
+ div.distorted-block > div.distorted:first-child:nth-last-child(3),
35
+ div.distorted-block > div.distorted:first-child:nth-last-child(3) ~ div.distorted,
36
+ div.distorted-block > div.distorted:first-child:nth-last-child(6),
37
+ div.distorted-block > div.distorted:first-child:nth-last-child(6) ~ div.distorted,
38
+ div.distorted-block > div.distorted:first-child:nth-last-child(9),
39
+ div.distorted-block > div.distorted:first-child:nth-last-child(9) ~ div.distorted {
40
+ width: 33.3333%;
41
+ }
42
+ div.distorted-block > div.distorted:first-child:nth-last-child(5),
43
+ div.distorted-block > div.distorted:first-child:nth-last-child(5) ~ div.distorted:nth-child(2) {
44
+ width: 50%;
45
+ }
46
+ div.distorted-block > div.distorted:first-child:nth-last-child(5) ~ div.distorted {
47
+ width: 33.3333%;
48
+ }
49
+ div.distorted-block > div.distorted:first-child:nth-last-child(7) ~ div.distorted:nth-child(3),
50
+ div.distorted-block > div.distorted:first-child:nth-last-child(7) ~ div.distorted:nth-child(4),
51
+ div.distorted-block > div.distorted:first-child:nth-last-child(7) ~ div.distorted:nth-child(5) {
52
+ width: 33.3333%;
53
+ }
54
+ }
55
+ @media screen and (min-width: 76em) {
56
+ div.distorted-block > div.distorted:first-child:nth-last-child(4),
57
+ div.distorted-block > div.distorted:first-child:nth-last-child(4) ~ div.distorted,
58
+ div.distorted-block > div.distorted:first-child:nth-last-child(8),
59
+ div.distorted-block > div.distorted:first-child:nth-last-child(8) ~ div.distorted {
60
+ width: 25%;
61
+ }
62
+ div.distorted-block > div.distorted:first-child:nth-last-child(5),
63
+ div.distorted-block > div.distorted:first-child:nth-last-child(5) ~ div.distorted:nth-child(2),
64
+ div.distorted-block > div.distorted:first-child:nth-last-child(5) ~ div.distorted,
65
+ div.distorted-block > div.distorted:first-child:nth-last-child(10),
66
+ div.distorted-block > div.distorted:first-child:nth-last-child(10) ~ div.distorted {
67
+ width: 20%;
68
+ }
69
+ div.distorted-block > div.distorted:first-child:nth-last-child(7),
70
+ div.distorted-block > div.distorted:first-child:nth-last-child(7) ~ div.distorted:nth-child(2),
71
+ div.distorted-block > div.distorted:first-child:nth-last-child(7) ~ div.distorted:nth-child(3) {
72
+ width: 33.3333%;
73
+ }
74
+ div.distorted-block > div.distorted:first-child:nth-last-child(7) ~ div.distorted,
75
+ div.distorted-block > div.distorted:first-child:nth-last-child(7) ~ div.distorted:nth-child(4),
76
+ div.distorted-block > div.distorted:first-child:nth-last-child(7) ~ div.distorted:nth-child(5) {
77
+ width: 25%;
78
+ }
79
+ }
@@ -0,0 +1,58 @@
1
+
2
+ # Slip in and out of phenomenon
3
+ require 'liquid/tag'
4
+ require 'liquid/tag/parser'
5
+
6
+ # Explicitly required for l/t/parser since a1cfa27c27cf4d4c308da2f75fbae88e9d5ae893
7
+ require 'shellwords'
8
+
9
+
10
+ module Jekyll
11
+ module DistorteD
12
+ class ThirteenthStyle < Liquid::Tag
13
+
14
+ TAB_SEQUENCE = ' '.freeze # two spaces
15
+
16
+ def initialize(tag_name, arguments, liquid_options)
17
+ super
18
+
19
+ # Liquid leaves argument parsing totally up to us.
20
+ # Use the envygeeks/liquid-tag-parser library to wrangle them.
21
+ parsed_arguments = Liquid::Tag::Parser.new(arguments)
22
+
23
+ # Specify how many levels to indent printed output.
24
+ # Indentation will apply to all lines after the first,
25
+ # because the first line's output will fall at the same
26
+ # place as our Liquid tag invocation.
27
+ @tabs = parsed_arguments[:tabs] || 0
28
+ end
29
+
30
+ # This is going to go away in a future Liquid version
31
+ # and render_to_output_buffer will be the standard approach.
32
+ # I'm going ahead and using it since we are building strings here.
33
+ def render(context)
34
+ return render_to_output_buffer(context, '')
35
+ end
36
+
37
+ def render_to_output_buffer(context, output)
38
+ css_filename = File.join(File.dirname(__FILE__), '13th-style.css'.freeze)
39
+
40
+ # Use IO.foreach() to call a block on each line of our template file
41
+ # without slurping the entire file into memory like File.read() / File.readlines()
42
+ File.foreach(css_filename).with_index do |line, line_num|
43
+ # Don't indent the first line of the CSS file, because the first line
44
+ # will print starting at the position of our {% 13thStyle %} Liquid tag.
45
+ unless line_num == 0
46
+ output << TAB_SEQUENCE * @tabs
47
+ end
48
+ output << line
49
+ end
50
+ # Remove CSS comments from output so I can leave notes there
51
+ # without bloating up my output.
52
+ # Based on C-shebang-style comment regex from MRE3
53
+ return output.gsub(/\/\*[^*]*\*+(?:[^*\/][^*]*\*+)*\//, '')
54
+ end
55
+
56
+ end # ThirteenthStyle
57
+ end # DistorteD
58
+ end # Jekyll
@@ -0,0 +1,63 @@
1
+ # Override any or all of this default configuration in your Jekyll site's `_config.yml`!
2
+
3
+ # Is it possible to do a Set of Hashes using the Set syntax in YAML?
4
+ # It works with the Array syntax, so that's what I'm using here,
5
+ # but keep in mind any Arrays will be converted to Sets when loaded,
6
+ # so duplicate Array values will be compacted!
7
+
8
+ standard_image: &standard_image
9
+ - crop: none
10
+ breaks: [333, 555, 777, 1111]
11
+
12
+ distorted:
13
+
14
+ # Should unrecognized media-types fall back to a bare
15
+ # <img> tag around the original media file?
16
+ # If not, the site build will fail when an unrecognized
17
+ # file is encountered.
18
+ never_let_you_down: true
19
+
20
+ # Configure DistorteD format changes by media_type, then by sub_type.
21
+ # The list of target formats is plain text, media_type/sub_type.
22
+ # These are mostly based on IANA's official Media Types list:
23
+ # https://www.iana.org/assignments/media-types/media-types.xhtml
24
+ # but with some custom additions like using 'gif-sequence' for
25
+ # animated GIF and leaving 'image/gif' to refer to single-frame GIFs.
26
+ changes:
27
+ image:
28
+ jpeg:
29
+ ? image/jpeg
30
+ ? image/webp
31
+ png:
32
+ ? image/png
33
+ ? image/webp
34
+ gif:
35
+ ? image/gif
36
+ ? image/png
37
+ ? image/webp
38
+ gif-sequence:
39
+ ? image/gif-sequence
40
+ svg:
41
+ ? image/svg+xml
42
+ ? image/png
43
+ ? image/webp
44
+ text:
45
+ plain:
46
+ ? text/plain
47
+ ? image/png
48
+ ? image/webp
49
+ x-nfo:
50
+ ? text/x-nfo
51
+ ? image/png
52
+ ? image/webp
53
+ font:
54
+ ttf:
55
+ ? font/ttf
56
+ ? image/png
57
+ ? image/webp
58
+
59
+ outer_limits:
60
+ image:
61
+ jpeg: *standard_image
62
+ png: *standard_image
63
+ webp: *standard_image
@@ -0,0 +1,16 @@
1
+
2
+ module Jekyll
3
+ module DistorteD
4
+ class BLOCKS < Liquid::Block
5
+
6
+ def initialize(tag_name, arguments, liquid_options)
7
+ super
8
+ end
9
+
10
+ def render(context)
11
+ "<div class=\"distorted-block\">#{super}</div>"
12
+ end
13
+
14
+ end # BLOCKS
15
+ end # DistorteD
16
+ end # Jekyll
@@ -0,0 +1,234 @@
1
+ # Our custom Exceptions
2
+ require 'distorted/error_code'
3
+
4
+ # Molecule loading and plugging functionality
5
+ require 'distorted/invoker'
6
+
7
+ # MIME::Typer
8
+ require 'distorted/checking_you_out'
9
+
10
+ # Configuration-loading code
11
+ require 'distorted-jekyll/the_setting_sun'
12
+ require 'distorted-jekyll/static_state'
13
+
14
+ # Slip in and out of phenomenon
15
+ require 'liquid/tag'
16
+ require 'liquid/tag/parser'
17
+ require 'distorted-jekyll/liquid_liquid'
18
+
19
+ require 'distorted-jekyll/media_molecule'
20
+
21
+ # Explicitly required for l/t/parser since a1cfa27c27cf4d4c308da2f75fbae88e9d5ae893
22
+ require 'shellwords'
23
+
24
+ # Set is in stdlib but is not in core.
25
+ require 'set'
26
+ # Set.to_hash
27
+ require 'distorted/monkey_business/set'
28
+
29
+ # I mean, this is why we're here, right?
30
+ require 'jekyll'
31
+
32
+
33
+ class Jekyll::DistorteD::Invoker < Liquid::Tag
34
+
35
+ GEM_ROOT = File.dirname(__FILE__).freeze
36
+
37
+ include Jekyll::DistorteD::Setting # Config-loading methods.
38
+ include Jekyll::DistorteD::StaticState # Jekyll::StaticFile impersonation methods.
39
+ include Cooltrainer::DistorteD::Invoker # Instance-setup methods.
40
+
41
+
42
+ # 𝘏𝘖𝘞 𝘈𝘙𝘌 𝘠𝘖𝘜 𝘎𝘌𝘕𝘛𝘓𝘌𝘔𝘌𝘕 !!
43
+ def initialize(tag_name, arguments, liquid_options)
44
+ super
45
+ # Tag name as given to Liquid::Template.register_tag().
46
+ @tag_name = tag_name.to_sym
47
+
48
+ # Liquid leaves argument parsing totally up to us.
49
+ # Use the envygeeks/liquid-tag-parser library to wrangle them.
50
+ parsed_arguments = Liquid::Tag::Parser.new(arguments)
51
+
52
+ # Filename is the only non-keyword argument our tag should ever get.
53
+ # It's spe-shul and gets its own definition outside the attr loop.
54
+ if parsed_arguments.key?(:src)
55
+ @name = parsed_arguments.delete(:src)
56
+ else
57
+ @name = parsed_arguments.delete(:argv1)
58
+ end
59
+
60
+ # Load contextual variables for abstract()
61
+ @tag_arguments = parsed_arguments.select{ |attr, val|
62
+ not [nil, ''.freeze].include?(val)
63
+ }.transform_keys(&:to_sym).transform_values { |val|
64
+ case val
65
+ when 'true' then true
66
+ when 'false' then false
67
+ when String then (val.length <= Jekyll::DistorteD::ARBITRARY_ATTR_SYMBOL_STRING_LENGTH_BOUNDARY) ? val.to_sym : val.freeze
68
+ else val
69
+ end
70
+ }
71
+
72
+ # If we didn't get one of the two above options there is nothing we
73
+ # can do but bail.
74
+ unless @name
75
+ raise "Failed to get a usable filename from #{arguments}"
76
+ end
77
+
78
+ end
79
+
80
+ # Returns a Set of DD MIME::Types descriving our file,
81
+ # optionally falling through to a plain file copy.
82
+ def type_mars
83
+ @type_mars ||= (CHECKING::YOU::OUT(path, so_deep: true) & lower_world.keys.to_set).tap { |gemini|
84
+ if gemini.empty? && the_setting_sun(:never_let_you_down)
85
+ gemini << CHECKING::YOU::OUT['application/x.distorted.never-let-you-down']
86
+ end
87
+ }
88
+ raise MediaTypeNotImplementedError.new(@name) if @type_mars.empty?
89
+ @type_mars
90
+ end
91
+
92
+ # Returns an Array[Change] for every intended output Type
93
+ # and every variation (e.g. resolution, bitrate) on each Type.
94
+ def changes
95
+ # The available/desired output Media Types and (variations on those Types)
96
+ # are based on the input Type and the Molecule(s) available to service those Types.
97
+ # Use an Array, since order might be important here when generating many variations
98
+ # at multiple levels of the DistorteD stack, e.g. the actual files on the Floor level
99
+ # and the templates/markup here in the Jekyll level.
100
+ @changes ||= type_mars.each_with_object(Array[]) { |lower, wanted|
101
+ # Query our configuration for Type changes, e.g. image/webp to (image/png and image/webp).
102
+ # Handle empty sub_types by compacting and splatting a sub-Array.
103
+ change_config = the_setting_sun(:changes, *(lower.settings_paths))
104
+ # If there is no config, treat it as a change to the same Type as the input,
105
+ # otherwise instantiate each "mediatype/subtype" config String to a MIME::Type.
106
+ ((change_config.nil? || change_config&.empty?) ? Set[lower] : change_config.map {|t| CHECKING::YOU::OUT[t]}).each { |type|
107
+ # Query our configuration again for variations on each Type.
108
+ # For example, one single image Type may want multiple resolutions to enable responsive <picture> tags,
109
+ # or a single video Type may want multiple bitrates for adaptive streaming.
110
+ limit_breaks = the_setting_sun(:outer_limits, *(type&.settings_paths)) || Array[Hash[]]
111
+ # Which MediaMolecule Modules support this Type as an output? Probably just one.
112
+ outer_limits.keep_if { |k, v| v.has_key?(type) }.keys.each { |molecule|
113
+ # As before, if there is nothing in the config just treat it as a Change to
114
+ # the full resolution/bitrate/whatever as the input, so this will always run at least once.
115
+ limit_breaks.each { |limit_break|
116
+ # Merge each variation's config with any/all attributes given to our Liquid Tag,
117
+ # as well as any Jekyll Stuff™ like the relative destination path.
118
+ change_arguments = limit_break.merge(Hash[:dir => @relative_dest]).merge(context_arguments)
119
+ # Each Change will carry instance Compound data in Atom Structs so we can avoid modifying
120
+ # the Compound Struct with any variation-specific values since they will be reused.
121
+ atoms = Hash.new
122
+ # We will always want an Atom from every Compound even if it only carries the :default.
123
+ Cooltrainer::DistorteD::IMPLANTATION(:OUTER_LIMITS, molecule)&.dig(type)&.each_pair { |aka, compound|
124
+ next if aka != compound.element # Skip alias Compounds since they will all be handled at once.
125
+ # Look for a user-given argument matching any supported alias of a Compound,
126
+ # and check those values against the Compound for validity.
127
+ atoms.store(compound.element, Cooltrainer::Atom.new(compound.isotopes.reduce(nil) { |value, isotope|
128
+ # TODO: valid?
129
+ value || change_arguments&.delete(isotope)
130
+ }, compound.default))
131
+ }
132
+ # After looping through the Compounds and calling :delete for matched values,
133
+ # this bag will be left with only the freeform non-Compound-associated arguments, if any.
134
+ # Separate those into arguments that match Change member names, and arguments that don't.
135
+ change_member_keys, atom_keys = change_arguments.keys.partition(&Cooltrainer::Change.members.method(:include?))
136
+ # Instantiate a no-default Atom for every remaining argument that isn't a Change member.
137
+ atom_keys.each { |attribute| atoms.store(attribute, Cooltrainer::Atom.new(change_arguments.delete(attribute), nil)) }
138
+ # Instantiate each variation of each Type into a Change struct
139
+ # that will handle some of the details like output-filename generation.
140
+ wanted.append(Cooltrainer::Change.new(type, src: @name, molecule: molecule, **change_arguments, **atoms))
141
+ }
142
+ }
143
+ }
144
+ wanted
145
+ }
146
+ end
147
+
148
+ # Return any arguments given by the user to our Liquid tag.
149
+ # This method name is generic across all DD entrypoints so it can be
150
+ # referenced from lower layers in the pile.
151
+ def context_arguments
152
+ @tag_arguments ||= Hash[]
153
+ end
154
+
155
+ # Returns a context-only setting from our Liquid attributes.
156
+ def abstract(key)
157
+ context_arguments.dig(key)
158
+ end
159
+
160
+ # Called by Jekyll::Renderer
161
+ # https://github.com/jekyll/jekyll/blob/HEAD/lib/jekyll/renderer.rb
162
+ # https://jekyllrb.com/tutorials/orderofinterpretation/
163
+ def render(context)
164
+ # Get Jekyll Site object back from tag rendering context registers so we
165
+ # can get configuration data and path information from it and
166
+ # then pass it along to our StaticFile subclass.
167
+ @site = context.registers[:site]
168
+
169
+ # The rendering context's `first` page will be the one that invoked us.
170
+ page_data = context.environments.first['page'.freeze]
171
+
172
+ #
173
+ # Our subclass' additional args:
174
+ # dest - The String path to the generated `url` folder of the page HTML output
175
+ @base = @site.source
176
+
177
+ # `relative_path` doesn't seem to always exist, but `path` does? idk.
178
+ # I was testing with `relative_path` only with `_posts`, but it broke
179
+ # when I invoked DD on a _page. Both have `path`.
180
+ @dir = File.dirname(page_data['path'.freeze])
181
+
182
+ # Every one of Ruby's `File.directory?` / `Pathname.directory?` /
183
+ # `FileTest.directory?` methods actually tests that path on the
184
+ # real filesystem, but we shouldn't look at the FS here because
185
+ # this function gets called when the Site.dest directory does
186
+ # not exist yet!
187
+ # Hackily look at the last character to see if the URL is a
188
+ # directory (like configured on cooltrainer) or a `.html`
189
+ # (or other extension) like the default Jekyll config.
190
+ # Get the dirname if the url is not a dir itself.
191
+ @relative_dest = page_data['url'.freeze]
192
+ unless @relative_dest[-1] == Jekyll::DistorteD::PATH_SEPARATOR
193
+ @relative_dest = File.dirname(@relative_dest)
194
+ # Append the trailing slash so we don't have to do it
195
+ # in the Liquid templates.
196
+ @relative_dest << Jekyll::DistorteD::PATH_SEPARATOR
197
+ end
198
+
199
+ # Add our new file to the list that will be handled
200
+ # by Jekyll's built-in StaticFile generator.
201
+ @site.static_files << self
202
+ render_to_output_buffer(context, '')
203
+ end
204
+
205
+ # A future Liquid version (5.0?) will call this function directly
206
+ # instead of calling render()
207
+ def render_to_output_buffer(context, output)
208
+ roots_of_my_way = Cooltrainer::ElementalCreation.new(:root).tap { |wrapper|
209
+ wrapper.dan = "distorted #{changes.reduce(Set[]) { |classes, change|
210
+ classes.add(change.molecule&.name.split('::'.freeze).last.downcase)
211
+ classes.add(change.type.sub_type.to_s.split(MIME::Type::SUB_TYPE_SEPARATORS)[0])
212
+ }.to_a.join(' ')}"
213
+ }
214
+
215
+ changes&.each { |change|
216
+ unless self.respond_to_missing?(change.type.distorted_template_method)
217
+ Jekyll.logger.error(@name, "Missing template method #{change.type.distorted_template_method}")
218
+ raise MediaTypeOutputNotImplementedError.new(@name, type_mars, self.class.name)
219
+ end
220
+ Jekyll.logger.debug("DistorteD::#{change.type.distorted_template_method}", File.join(change.dir, change.name))
221
+
222
+ # Get an ElementalCreation Struct from the MediaMolecule's render method.
223
+ # WISHLIST: Remove the empty final positional Hash argument once we require a Ruby version
224
+ # that will not perform the implicit Change-to-Hash conversion due to Change's
225
+ # implementation of :to_hash. Ruby 2.7 will complain but still do the conversion,
226
+ # breaking downstream callers that want a Struct they can call arbitrary key methods on.
227
+ # https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/
228
+ roots_of_my_way.mad_child(self.send(change.type.distorted_template_method, change, **{}))
229
+ }
230
+
231
+ output << roots_of_my_way.render
232
+ end
233
+
234
+ end