distorted-jekyll 0.5.3 → 0.6.0

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