distorted-jekyll 0.5.4 → 0.7.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.
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