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.
- checksums.yaml +4 -4
- data/LICENSE +661 -0
- data/README.md +7 -11
- data/lib/distorted-jekyll.rb +75 -0
- data/lib/distorted-jekyll/13th-style.css +79 -0
- data/lib/distorted-jekyll/13th-style.rb +58 -0
- data/lib/distorted-jekyll/_config_default.yml +63 -0
- data/lib/distorted-jekyll/blocks.rb +16 -0
- data/lib/distorted-jekyll/invoker.rb +234 -0
- data/lib/distorted-jekyll/liquid_liquid.rb +255 -0
- data/lib/distorted-jekyll/liquid_liquid/anchor.liquid +5 -0
- data/lib/distorted-jekyll/liquid_liquid/anchor_inline.liquid +1 -0
- data/lib/distorted-jekyll/liquid_liquid/embed.liquid +1 -0
- data/lib/distorted-jekyll/liquid_liquid/img.liquid +1 -0
- data/lib/distorted-jekyll/liquid_liquid/object.liquid +5 -0
- data/lib/distorted-jekyll/liquid_liquid/picture.liquid +15 -0
- data/lib/distorted-jekyll/liquid_liquid/picture.rb +48 -0
- data/lib/distorted-jekyll/liquid_liquid/picture_source.liquid +1 -0
- data/lib/distorted-jekyll/liquid_liquid/root.liquid +5 -0
- data/lib/distorted-jekyll/liquid_liquid/video.liquid +5 -0
- data/lib/distorted-jekyll/liquid_liquid/video_source.liquid +1 -0
- data/lib/distorted-jekyll/md_injection.rb +310 -0
- data/lib/distorted-jekyll/media_molecule.rb +20 -0
- data/lib/distorted-jekyll/media_molecule/font.rb +21 -0
- data/lib/distorted-jekyll/media_molecule/image.rb +15 -0
- data/lib/distorted-jekyll/media_molecule/never_let_you_down.rb +28 -0
- data/lib/distorted-jekyll/media_molecule/pdf.rb +108 -0
- data/lib/distorted-jekyll/media_molecule/svg.rb +20 -0
- data/lib/distorted-jekyll/media_molecule/text.rb +23 -0
- data/lib/distorted-jekyll/media_molecule/video.rb +45 -0
- data/lib/distorted-jekyll/monkey_business/jekyll/cleaner.rb +121 -0
- data/lib/distorted-jekyll/static_state.rb +160 -0
- data/lib/distorted-jekyll/the_setting_sun.rb +179 -0
- metadata +37 -34
data/README.md
CHANGED
@@ -1,19 +1,16 @@
|
|
1
|
-
#
|
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
|
-
|
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
|
-
|
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-
|
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
|
-
|
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
|