distorted-jekyll 0.5.3 → 0.6.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.
@@ -0,0 +1,54 @@
1
+ require 'set'
2
+
3
+ module Jekyll
4
+ # Handles the cleanup of a site's destination before it is built or re-built.
5
+ class Cleaner
6
+
7
+ # Private: The list of files to be created when site is built.
8
+ #
9
+ # Returns a Set with the file paths
10
+ #
11
+ # Monkey-patch this to look for DD's unique `destinations` which is similar
12
+ # to the original `destination` method except it returns a Set of destination
13
+ # paths instead of a single destination path.
14
+ # Do the patch with `define_method` instead of just `def` because the block's
15
+ # closure of the local scope lets it carry a binding to the original overriden
16
+ # method which I use to bail out iff the monkey-patch fails.
17
+ # This is an attempt to avoid breaking future Jekyll versions as much as
18
+ # possible, since any Exception in the monkey-patched code will just cause
19
+ # the original Jekyll implementation to be called instead.
20
+ # The new worst case scenario is slow site builds due to media variation generation!
21
+ #
22
+ # If a StaticFile responds to `destinations` then use it and merge the result.
23
+ # I'm defining my own separate method for multi-destinations for now,
24
+ # but I also considered just overriding `destination` to return the Set and
25
+ # then doing this as a one-liner that handles either case (single or
26
+ # multiple destinations) with `files.merge(Set[*(item.destination(site.dest))])`.
27
+ # This is the safer choice though since we avoid changing the outout type of the
28
+ # regular `:destination` method.
29
+ the_old_new_thing = instance_method(:new_files)
30
+ define_method(:new_files) do
31
+ begin
32
+ @new_files ||= Set.new.tap do |files|
33
+ site.each_site_file { |item|
34
+ if item.respond_to?(:destinations)
35
+ files.merge(item.destinations(site.dest))
36
+ elsif item.respond_to?(:destination)
37
+ files << item.destination(site.dest)
38
+ else
39
+ # Something unrelated has gone wrong for us to end up sending
40
+ # `destination` to something that doesn't respond to it.
41
+ # We should fall back to the original implementation of `new_files`
42
+ # in this case so the failure doesn't appear to be here.
43
+ the_old_new_thing.bind(self).()
44
+ end
45
+ }
46
+ end
47
+ rescue RuntimeError => e
48
+ Jekyll.logger.warn('DistorteD', "Monkey-patching Jekyll::Cleaner#new_files failed: #{e.message}")
49
+ Jekyll.logger.debug('DistorteD', "Monkey-patched Jekyll::Cleaner#new_files backtrace: #{e.backtrace}")
50
+ the_old_new_thing.bind(self).()
51
+ end
52
+ end
53
+ end # Cleaner
54
+ end # Jekyll
@@ -0,0 +1,201 @@
1
+
2
+ require 'fileutils'
3
+ require 'set'
4
+
5
+ require 'distorted/error_code'
6
+
7
+
8
+ module Jekyll; end
9
+ module Jekyll::DistorteD; end
10
+
11
+ # This module implements the methods our tag needs in order to
12
+ # pretend to be a Jekyll::StaticFile so we don't need to
13
+ # redundantly re-implement a Generator and Jekyll::Cleaner.
14
+ module Jekyll::DistorteD::StaticState
15
+
16
+
17
+ ATTRIBUTES = Set[:title]
18
+
19
+
20
+ # Returns the to-be-written path of a single standard StaticFile.
21
+ # The value returned by this method is only the 'main' or 'original'
22
+ # (even if modified somehow) file and does not include the
23
+ # path/filenames of any variations.
24
+ # This method will be called by jekyll/lib/cleaner#new_files
25
+ # to generate the list of files that need to be build or rebuilt
26
+ # for a site. For this reason, this method shouldn't do any kind
27
+ # of checking the real filesystem, since e.g. its URL-based
28
+ # destdir might not exist yet if the Site.dest is completely blank.
29
+ def destination(dest_root)
30
+ File.join(dest_root, @relative_dest, @name)
31
+ end
32
+
33
+ # This method will be called by our monkey-patched Jekyll::Cleaner#new_files
34
+ # in place of the single-destination method usually used.
35
+ # This allows us to tell Jekyll about more than a single file
36
+ # that should be kept when regenerating the site.
37
+ # This makes DistorteD fast!
38
+ def destinations(dest_root)
39
+ wanted_files.map{|f| File.join(dest_root, @relative_dest, f)}
40
+ end
41
+
42
+ # HACK HACK HACK
43
+ # Jekyll does not pass this method a site.dest like it does write() and
44
+ # others, but I want to be able to short-circuit here if all the
45
+ # to-be-generated files already exist.
46
+ def modified?
47
+ # Assume modified for the sake of freshness :)
48
+ modified = true
49
+
50
+ site_dest = Jekyll::DistorteD::Floor::config(:destination).to_s
51
+ if Dir.exist?(site_dest)
52
+ if Dir.exist?(File.join(site_dest, @relative_dest))
53
+ extant_files = Dir.entries(File.join(site_dest, @relative_dest)).to_set
54
+
55
+ # TODO: Make this smarter. It's not enough that all the generated
56
+ # filenames should exist. Try a few more ways to detect subtler
57
+ # "changes to the source file since generation of variations.
58
+ if wanted_files.subset?(extant_files)
59
+ Jekyll.logger.debug(@name, "All variations present: #{wanted_files}")
60
+ modified = false
61
+ else
62
+ Jekyll.logger.debug(@name, "Missing variations: #{wanted_files - extant_files}")
63
+ end
64
+
65
+ end # relative_dest.exists?
66
+ end # site_dest.exists?
67
+ Jekyll.logger.debug("#{@name} modified?", modified)
68
+ return modified
69
+ end # modified?
70
+
71
+ # Whether to write the file to the filesystem
72
+ #
73
+ # Returns true unless the defaults for the destination path from
74
+ # _config.yml contain `published: false`.
75
+ def write?
76
+ publishable = defaults.fetch('published'.freeze, true)
77
+ return publishable unless @collection
78
+
79
+ publishable && @collection.write?
80
+ end
81
+
82
+ # Write the static file to the destination directory (if modified).
83
+ #
84
+ # dest - The String path to the destination dir.
85
+ #
86
+ # Returns false if the file was not modified since last time (no-op).
87
+ def write(dest_root)
88
+ plug
89
+ return false if File.exist?(path) && !modified?
90
+
91
+ # Create any directories to the depth of the intended destination.
92
+ FileUtils.mkdir_p(File.join(dest_root, @relative_dest))
93
+ # Save every desired variation of this image.
94
+ # This will be a Set of Hashes each describing the name, type,
95
+ # dimensions, attributes, etc of each output variation we want.
96
+ # Full-size outputs will have the special tag `:full`.
97
+ files.each { |variation|
98
+ type = variation&.dig(:type)
99
+ filename = File.join(dest_root, @relative_dest, variation&.dig(:name) || @name)
100
+
101
+ if self.respond_to?(type.distorted_method)
102
+ Jekyll.logger.debug("DistorteD::#{type.distorted_method}", filename)
103
+ self.send(type.distorted_method, filename, **variation)
104
+ elsif extname == ".#{type.preferred_extension}"
105
+ Jekyll.logger.debug(@name, <<~RAWCOPY
106
+ No #{type.distorted_method} method is defined,
107
+ but the intended output type #{type.to_s} is the same
108
+ as the input type, so I will fall back to copying the raw file.
109
+ RAWCOPY
110
+ )
111
+ copy_file(filename)
112
+ else
113
+ Jekyll.logger.error(@name, "Missing rendering method #{type.distorted_method}")
114
+ raise MediaTypeOutputNotImplementedError.new(filename, type, self.class.name)
115
+ end
116
+ }
117
+ end # write
118
+
119
+ private
120
+
121
+ def copy_file(dest_path, *a, **k)
122
+ if @site.safe || Jekyll.env == "production"
123
+ FileUtils.cp(path, dest_path)
124
+ else
125
+ FileUtils.copy_entry(path, dest_path)
126
+ end
127
+ end # copy_file
128
+
129
+ # Basic file properties
130
+
131
+ # Filename without the dot-and-extension.
132
+ def basename
133
+ File.basename(@name, '.*')
134
+ end
135
+
136
+ # Returns the extname /!\ including the dot /!\
137
+ def extname
138
+ File.extname(@name)
139
+ end
140
+
141
+ # Returns last modification time for this file.
142
+ def mtime
143
+ (@modified_time ||= File.stat(path).mtime).to_i
144
+ end
145
+
146
+ # Returns source file path.
147
+ def path
148
+ @path ||= begin
149
+ # Static file is from a collection inside custom collections directory
150
+ if !@collection.nil? && !@site.config['collections_dir'.freeze].empty?
151
+ File.join(*[@base, @site.config['collections_dir'.freeze], @dir, @name].compact)
152
+ else
153
+ File.join(*[@base, @dir, @name].compact)
154
+ end
155
+ end
156
+ end
157
+
158
+ # Returns a Hash keyed by MIME::Type objects with value as a Set of Hashes
159
+ # describing the media's output variations to be generated for each Type.
160
+ def variations
161
+ changes(abstract(:changes)).map{ |t|
162
+ [t, outer_limits(abstract(:outer_limits)).map{ |d|
163
+
164
+ # Don't change the filename of full-size variations
165
+ tag = d&.dig(:tag) != :full ? '-'.concat(d&.dig(:tag).to_s) : ''.freeze
166
+ # Use the original extname for LastResort
167
+ ext = t == CHECKING::YOU::OUT('application/x.distorted.last-resort') ? File.extname(@name) : t.preferred_extension
168
+ # Handle LastResort for files that might be a bare name with no extension
169
+ dot = '.'.freeze unless ext.nil? || ext&.empty?
170
+
171
+ d.merge({
172
+ # e.g. 'SomeImage-medium.jpg` but just `SomeImage.jpg` and not `SomeImage-full.jpg`
173
+ # for the full-resolution outputs.
174
+ # The default `.jpeg` preferred_extension is monkey-patched to `.jpg` because lol
175
+ :name => "#{basename}#{tag}#{dot}#{ext}",
176
+ })
177
+
178
+ }]
179
+ }.to_h
180
+ end
181
+
182
+ # Returns a flat Set of Hashes that each describe one variant of
183
+ # media file output that should exist for a given input file.
184
+ def files
185
+ filez = Set[]
186
+ variations.each_pair{ |t,v|
187
+ # Merge the type in to each variation Hash since we will no longer
188
+ # have it as the key to this Set in its container Hash.
189
+ v.each{ |d| filez.add(d.merge({:type => t})) }
190
+ }
191
+ filez
192
+ end
193
+
194
+ # Returns a Set of just the String filenames we want for this media.
195
+ # This will be used by `modified?` among others.
196
+ def wanted_files
197
+ files.map{|f| f[:name]}.to_set
198
+ end
199
+
200
+
201
+ 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,3 @@
1
+ <div class="distorted error">
2
+ <p>{{ message }}</p>
3
+ </div>
@@ -0,0 +1,32 @@
1
+ <div class="distorted font">
2
+ {%- if href != true %}
3
+ {%- capture href %}{{ path }}{{ name }}{%- endcapture -%}
4
+ {%- endif %}
5
+ {%- if alt != nil and alt != "" %}
6
+ {%- capture attr_alt %} alt="{{ alt }}"{%- endcapture -%}
7
+ {%- endif %}
8
+ {%- if title != nil and title != "" %}
9
+ {%- capture attr_title %} title="{{ title }}"{%- endcapture -%}
10
+ {%- endif %}
11
+ {%- if loading != nil and loading != "" %}
12
+ {%- capture attr_loading %} loading="{{ loading }}"{%- endcapture -%}
13
+ {%- endif %}
14
+ <a href="{{ href }}"{{ attr_title }} target="_blank">
15
+ <picture>
16
+ {%- if sources %}
17
+ {%- for source in sources %}
18
+ {%- if source.media != nil and source.media != "" %}
19
+ {%- capture attr_media %} media="{{ source.media }}"{%- endcapture -%}
20
+ {%- else %}
21
+ {%- capture attr_media %}{%- endcapture -%}
22
+ {%- endif %}
23
+ <source srcset="{{ path }}{{ source.name }}" type="{{ source.type }}"{{ attr_media }}/>
24
+ {%- endfor %}
25
+ {%- endif %}
26
+ <img src="{{ path }}{{ fallback_img }}"{{ attr_alt }}{{ attr_title }}{{ attr_loading }}/>
27
+ </picture>
28
+ </a>
29
+ {%- if caption != nil and caption != "" %}
30
+ <p class="distorted-caption">{{ caption }}</p>
31
+ {%- endif %}
32
+ </div>
@@ -0,0 +1,32 @@
1
+ <div class="distorted image">
2
+ {%- if href != true %}
3
+ {%- capture href %}{{ path }}{{ name }}{%- endcapture -%}
4
+ {%- endif %}
5
+ {%- if alt != nil and alt != "" %}
6
+ {%- capture attr_alt %} alt="{{ alt }}"{%- endcapture -%}
7
+ {%- endif %}
8
+ {%- if title != nil and title != "" %}
9
+ {%- capture attr_title %} title="{{ title }}"{%- endcapture -%}
10
+ {%- endif %}
11
+ {%- if loading != nil and loading != "" %}
12
+ {%- capture attr_loading %} loading="{{ loading }}"{%- endcapture -%}
13
+ {%- endif %}
14
+ <a href="{{ href }}"{{ attr_title }} target="_blank">
15
+ <picture>
16
+ {%- if sources %}
17
+ {%- for source in sources %}
18
+ {%- if source.media != nil and source.media != "" %}
19
+ {%- capture attr_media %} media="{{ source.media }}"{%- endcapture -%}
20
+ {%- else %}
21
+ {%- capture attr_media %}{%- endcapture -%}
22
+ {%- endif %}
23
+ <source srcset="{{ path }}{{ source.name }}" type="{{ source.type }}"{{ attr_media }}/>
24
+ {%- endfor %}
25
+ {%- endif %}
26
+ <img src="{{ path }}{{ fallback_img }}"{{ attr_alt }}{{ attr_title }}{{ attr_loading }}/>
27
+ </picture>
28
+ </a>
29
+ {%- if caption != nil and caption != "" %}
30
+ <p class="distorted-caption">{{ caption }}</p>
31
+ {%- endif %}
32
+ </div>
@@ -0,0 +1,20 @@
1
+ <div class="distorted lastresort">
2
+ {%- if href != true %}
3
+ {%- capture href %}{{ path }}{{ name }}{%- endcapture -%}
4
+ {%- endif %}
5
+ {%- if alt != nil and alt != "" %}
6
+ {%- capture attr_alt %} alt="{{ alt }}"{%- endcapture -%}
7
+ {%- endif %}
8
+ {%- if title != nil and title != "" %}
9
+ {%- capture attr_title %} title="{{ title }}"{%- endcapture -%}
10
+ {%- endif %}
11
+ {%- if loading != nil and loading != "" %}
12
+ {%- capture attr_loading %} loading="{{ loading }}"{%- endcapture -%}
13
+ {%- endif %}
14
+ <a href="{{ href }}"{{ attr_title }} target="_blank">
15
+ <img src="{{ path }}{{ name }}"{{ attr_alt }}{{ attr_title }}{{ attr_loading }}/>
16
+ </a>
17
+ {%- if caption != nil and caption != "" %}
18
+ <p class="distorted-caption">{{ caption }}</p>
19
+ {%- endif %}
20
+ </div>
@@ -0,0 +1,14 @@
1
+ <div class="distorted pdf">
2
+ {%- if title != nil and title != "" %}
3
+ {%- capture pdf_link %}{{ title }}{%- endcapture -%}
4
+ {%- elsif alt != nil and alt != "" %}
5
+ {%- capture pdf_link %}{{ alt }}{%- endcapture -%}
6
+ {%- endif %}
7
+ <object data="{{ path }}{{ name }}#{{ pdf_open_params }}" type="application/pdf" width="{{ width }}" height="{{ height }}">
8
+ <embed src="{{ path }}{{ name }}#{{ pdf_open_params }}" type="application/pdf" width="{{ width }}" height="{{ height }}"/>
9
+ <a href="{{ path }}{{ name }}#{{ pdf_open_params }}">{{ pdf_link }} [PDF]</a>
10
+ </object>
11
+ {%- if caption != nil and caption != "" %}
12
+ <p class="distorted-caption">{{ caption }}</p>
13
+ {%- endif %}
14
+ </div>
@@ -0,0 +1,32 @@
1
+ <div class="distorted svg">
2
+ {%- if href != true %}
3
+ {%- capture href %}{{ path }}{{ name }}{%- endcapture -%}
4
+ {%- endif %}
5
+ {%- if alt != nil and alt != "" %}
6
+ {%- capture attr_alt %} alt="{{ alt }}"{%- endcapture -%}
7
+ {%- endif %}
8
+ {%- if title != nil and title != "" %}
9
+ {%- capture attr_title %} title="{{ title }}"{%- endcapture -%}
10
+ {%- endif %}
11
+ {%- if loading != nil and loading != "" %}
12
+ {%- capture attr_loading %} loading="{{ loading }}"{%- endcapture -%}
13
+ {%- endif %}
14
+ <a href="{{ href }}"{{ attr_title }} target="_blank">
15
+ <picture>
16
+ {%- if sources %}
17
+ {%- for source in sources %}
18
+ {%- if source.media != nil and source.media != "" %}
19
+ {%- capture attr_media %} media="{{ source.media }}"{%- endcapture -%}
20
+ {%- else %}
21
+ {%- capture attr_media %}{%- endcapture -%}
22
+ {%- endif %}
23
+ <source srcset="{{ path }}{{ source.name }}" type="{{ source.type }}"{{ attr_media }}/>
24
+ {%- endfor %}
25
+ {%- endif %}
26
+ <img src="{{ path }}{{ fallback_img }}"{{ attr_alt }}{{ attr_title }}{{ attr_loading }}/>
27
+ </picture>
28
+ </a>
29
+ {%- if caption != nil and caption != "" %}
30
+ <p class="distorted-caption">{{ caption }}</p>
31
+ {%- endif %}
32
+ </div>