distorted-jekyll 0.5.6 → 0.5.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +661 -0
  3. data/README.md +6 -10
  4. data/lib/distorted-jekyll.rb +79 -0
  5. data/lib/distorted-jekyll/13th-style.rb +58 -0
  6. data/lib/distorted-jekyll/_config_default.yml +79 -0
  7. data/lib/distorted-jekyll/blocks.rb +16 -0
  8. data/lib/distorted-jekyll/error_code.rb +24 -0
  9. data/lib/distorted-jekyll/floor.rb +148 -0
  10. data/lib/distorted-jekyll/injection_of_love.rb +305 -0
  11. data/lib/distorted-jekyll/invoker.rb +400 -0
  12. data/lib/distorted-jekyll/molecule/abstract.rb +238 -0
  13. data/lib/distorted-jekyll/molecule/font.rb +29 -0
  14. data/lib/distorted-jekyll/molecule/image.rb +105 -0
  15. data/lib/distorted-jekyll/molecule/last-resort.rb +54 -0
  16. data/lib/distorted-jekyll/molecule/pdf.rb +88 -0
  17. data/lib/distorted-jekyll/molecule/svg.rb +59 -0
  18. data/lib/distorted-jekyll/molecule/text.rb +74 -0
  19. data/lib/distorted-jekyll/molecule/video.rb +43 -0
  20. data/lib/distorted-jekyll/monkey_business/jekyll/cleaner.rb +54 -0
  21. data/lib/distorted-jekyll/static/font.rb +42 -0
  22. data/lib/distorted-jekyll/static/image.rb +55 -0
  23. data/lib/distorted-jekyll/static/lastresort.rb +28 -0
  24. data/lib/distorted-jekyll/static/pdf.rb +53 -0
  25. data/lib/distorted-jekyll/static/state.rb +141 -0
  26. data/lib/distorted-jekyll/static/svg.rb +52 -0
  27. data/lib/distorted-jekyll/static/text.rb +57 -0
  28. data/lib/distorted-jekyll/static/video.rb +90 -0
  29. data/lib/distorted-jekyll/template/13th-style.css +78 -0
  30. data/lib/distorted-jekyll/template/error_code.liquid +3 -0
  31. data/lib/distorted-jekyll/template/font.liquid +32 -0
  32. data/lib/distorted-jekyll/template/image.liquid +32 -0
  33. data/lib/distorted-jekyll/template/lastresort.liquid +20 -0
  34. data/lib/distorted-jekyll/template/pdf.liquid +14 -0
  35. data/lib/distorted-jekyll/template/svg.liquid +32 -0
  36. data/lib/distorted-jekyll/template/text.liquid +32 -0
  37. data/lib/distorted-jekyll/template/video.liquid +11 -0
  38. metadata +41 -6
@@ -0,0 +1,59 @@
1
+ require 'set'
2
+
3
+ require 'distorted/svg'
4
+ require 'distorted-jekyll/static/svg'
5
+
6
+ module Jekyll
7
+ module DistorteD
8
+ module Molecule
9
+ module SVG
10
+
11
+ # Reference these instead of reassigning them. Consistency is mandatory.
12
+ MEDIA_TYPE = Cooltrainer::DistorteD::SVG::MEDIA_TYPE
13
+ SUB_TYPE = Cooltrainer::DistorteD::SVG::SUB_TYPE
14
+ MIME_TYPES = Cooltrainer::DistorteD::SVG::MIME_TYPES
15
+
16
+ ATTRS = Cooltrainer::DistorteD::SVG::ATTRS
17
+ ATTRS_DEFAULT = Cooltrainer::DistorteD::SVG::ATTRS_DEFAULT
18
+ ATTRS_VALUES = Cooltrainer::DistorteD::SVG::ATTRS_VALUES
19
+
20
+
21
+ def render_to_output_buffer(context, output)
22
+ super
23
+ begin
24
+ # Liquid doesn't seem able to reference symbolic keys,
25
+ # so convert everything to string for template.
26
+ # Not stripping :full tags like Image because all of our
27
+ # SVG variations will be full-res for now.
28
+ filez = files.map{ |f|
29
+ f.transform_values(&:to_s).transform_keys(&:to_s)
30
+ }
31
+ output << parse_template.render({
32
+ 'name' => @name,
33
+ 'path' => @dd_dest,
34
+ 'alt' => attr_value(:alt),
35
+ 'title' => attr_value(:title),
36
+ 'href' => attr_value(:href),
37
+ 'caption' => attr_value(:caption),
38
+ 'loading' => attr_value(:loading),
39
+ 'sources' => filez,
40
+ 'fallback_img' => @name,
41
+ })
42
+ rescue Liquid::SyntaxError => l
43
+ unless Jekyll.env == 'production'.freeze
44
+ output << parse_template(name: 'error_code'.freeze).render({
45
+ 'message' => l.message,
46
+ })
47
+ end
48
+ end
49
+ output
50
+ end
51
+
52
+ def static_file(*args)
53
+ Jekyll::DistorteD::Static::SVG.new(*args)
54
+ end
55
+
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,74 @@
1
+ require 'set'
2
+
3
+ require 'distorted/text'
4
+ require 'distorted-jekyll/static/text'
5
+
6
+ module Jekyll
7
+ module DistorteD
8
+ module Molecule
9
+ module Text
10
+
11
+ DRIVER = Cooltrainer::DistorteD::Text
12
+
13
+ MEDIA_TYPE = DRIVER::MEDIA_TYPE
14
+ MIME_TYPES = DRIVER::MIME_TYPES
15
+
16
+ ATTRS = DRIVER::ATTRS
17
+ ATTRS_DEFAULT = DRIVER::ATTRS_DEFAULT
18
+ ATTRS_VALUES = DRIVER::ATTRS_VALUES
19
+
20
+
21
+ def render_to_output_buffer(context, output)
22
+ super
23
+ begin
24
+ filez = files.keep_if{ |f|
25
+ # Strip out all non-displayable media-types, e.g. the actual text/whatever.
26
+ f.key?(:type) && f&.dig(:type)&.media_type == 'image'.freeze
27
+ }.keep_if{ |f|
28
+ # Strip out full-size images (will have `nil`) — only display thumbnail vers
29
+ f.key?(:width) or f.key?(:height)
30
+ }.map{ |f|
31
+ # Stringify to make Liquid happy
32
+ f.transform_values(&:to_s).transform_keys(&:to_s)
33
+ }
34
+ output << parse_template.render({
35
+ 'name' => @name,
36
+ 'path' => @dd_dest,
37
+ 'alt' => attr_value(:alt),
38
+ 'title' => attr_value(:title),
39
+ 'sources' => filez,
40
+ 'fallback_img' => fallback_img,
41
+ })
42
+ rescue Liquid::SyntaxError => l
43
+ unless Jekyll.env == 'production'.freeze
44
+ output << parse_template(name: 'error_code'.freeze).render({
45
+ 'message' => l.message,
46
+ })
47
+ end
48
+ end
49
+ output
50
+ end
51
+
52
+ # Return the filename of the most-compatible output image
53
+ # for use as the fallback <img> tag inside our <picture>.
54
+ def fallback_img
55
+ best_ver = nil
56
+ files.keep_if{|f| f.key?(:type) && f&.dig(:type)&.media_type == 'image'.freeze}.each{ |f|
57
+ # PNG > WebP
58
+ if f&.dig(:type)&.sub_type == 'png'.freeze || best_ver.nil?
59
+ best_ver = f
60
+ end
61
+ }
62
+ # Return the filename of the biggest matched variation,
63
+ # otherwise use the original filename.
64
+ best_ver&.dig(:name) || @name
65
+ end
66
+
67
+ def static_file(*args)
68
+ Jekyll::DistorteD::Static::Text.new(*args)
69
+ end
70
+
71
+ end # Text
72
+ end # Molecule
73
+ end # DistorteD
74
+ end # Jekyll
@@ -0,0 +1,43 @@
1
+ require 'distorted/video'
2
+ require 'distorted-jekyll/static/video'
3
+
4
+ module Jekyll
5
+ module DistorteD
6
+ module Molecule
7
+ module Video
8
+
9
+ # Reference these instead of reassigning them. Consistency is mandatory.
10
+ MEDIA_TYPE = Cooltrainer::DistorteD::Video::MEDIA_TYPE
11
+ MIME_TYPES = Cooltrainer::DistorteD::Video::MIME_TYPES
12
+
13
+ ATTRS = Cooltrainer::DistorteD::Video::ATTRS
14
+ ATTRS_DEFAULT = Cooltrainer::DistorteD::Video::ATTRS_DEFAULT
15
+ ATTRS_VALUES = Cooltrainer::DistorteD::Video::ATTRS_VALUES
16
+
17
+ def render_to_output_buffer(context, output)
18
+ super
19
+ begin
20
+ output << parse_template.render({
21
+ 'name' => @name,
22
+ 'basename' => File.basename(@name, '.*'),
23
+ 'path' => @url,
24
+ 'caption' => attr_value(:caption),
25
+ })
26
+ rescue Liquid::SyntaxError => l
27
+ unless Jekyll.env == 'production'.freeze
28
+ output << parse_template(name: 'error_code'.freeze).render({
29
+ 'message' => l.message,
30
+ })
31
+ end
32
+ end
33
+ output
34
+ end
35
+
36
+ def static_file(*args)
37
+ Jekyll::DistorteD::Static::Video.new(*args)
38
+ end
39
+
40
+ end
41
+ end
42
+ end
43
+ end
@@ -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,42 @@
1
+ require 'fileutils'
2
+ require 'set'
3
+
4
+ require 'distorted/font'
5
+ require 'distorted-jekyll/static/text'
6
+
7
+ module Jekyll
8
+ module DistorteD
9
+ module Static
10
+ class Font < Text
11
+
12
+ DRIVER = Cooltrainer::DistorteD::Font
13
+
14
+ MEDIA_TYPE = DRIVER::MEDIA_TYPE
15
+ MIME_TYPES = DRIVER::MIME_TYPES
16
+
17
+ ATTRS = DRIVER::ATTRS
18
+ ATTRS_DEFAULT = DRIVER::ATTRS_DEFAULT
19
+ ATTRS_VALUES = DRIVER::ATTRS_VALUES
20
+
21
+
22
+ # dest: String realpath to `_site` directory
23
+ def write(dest)
24
+ orig_dest = destination(dest)
25
+
26
+ return false if !modified?
27
+ self.class.mtimes[path] = mtime
28
+
29
+ @distorted = DRIVER.new(
30
+ path,
31
+ demo: attr_value(:title),
32
+ )
33
+ FileUtils.cp(path, File.join(dd_dest(dest), @name))
34
+
35
+ super
36
+
37
+ end
38
+
39
+ end # Text
40
+ end # Static
41
+ end # DistorteD
42
+ end # Jekyll
@@ -0,0 +1,55 @@
1
+ require 'fileutils'
2
+ require 'set'
3
+
4
+ require 'distorted/image'
5
+ require 'distorted-jekyll/static/state'
6
+
7
+ module Jekyll
8
+ module DistorteD
9
+ module Static
10
+ class Image < Jekyll::DistorteD::Static::State
11
+
12
+ DRIVER = Cooltrainer::DistorteD::Image
13
+
14
+ MEDIA_TYPE = DRIVER::MEDIA_TYPE
15
+ MIME_TYPES = DRIVER::MIME_TYPES
16
+
17
+ ATTRS = DRIVER::ATTRS
18
+ ATTRS_DEFAULT = DRIVER::ATTRS_DEFAULT
19
+ ATTRS_VALUES = DRIVER::ATTRS_VALUES
20
+
21
+
22
+ # dest: string realpath to `_site_` directory
23
+ def write(dest)
24
+ return false if File.exist?(path) && !modified?
25
+ self.class.mtimes[path] = mtime
26
+
27
+ # Create any directories to the depth of the intended destination.
28
+ FileUtils.mkdir_p(dd_dest(dest))
29
+
30
+ unless defined? @distorted
31
+ @distorted = DRIVER.new(path)
32
+ end
33
+
34
+ Jekyll.logger.debug(@tag_name, "Rotating #{@name} if tagged.")
35
+ @distorted.rotate(angle: :auto)
36
+
37
+ # Save every desired variation of this image.
38
+ # This will be a Set of Hashes each describing the name, type,
39
+ # dimensions, attributes, etc of each output variation we want.
40
+ # Full-size outputs will have the special tag `:full`.
41
+ for variation in files
42
+ if DRIVER::MIME_TYPES.include?(variation&.dig(:type))
43
+ filename = File.join(dd_dest(dest), variation&.dig(:name) || @name)
44
+ Jekyll.logger.debug('DistorteD Writing:', filename)
45
+ @distorted.save(filename, width: variation&.dig(:width), crop: variation&.dig(:crop))
46
+ end
47
+ end
48
+
49
+ true
50
+ end
51
+
52
+ end # Image
53
+ end # Static
54
+ end # DistorteD
55
+ end # Jekyll
@@ -0,0 +1,28 @@
1
+ require 'fileutils'
2
+ require 'set'
3
+
4
+ require 'distorted-jekyll/static/state'
5
+
6
+ module Jekyll
7
+ module DistorteD
8
+ module Static
9
+ class LastResort < Jekyll::DistorteD::Static::State
10
+
11
+ # dest: string realpath to `_site_` directory
12
+ def write(dest)
13
+ return false if File.exist?(path) && !modified?
14
+ self.class.mtimes[path] = mtime
15
+
16
+ # Create any directories to the depth of the intended destination.
17
+ FileUtils.mkdir_p(dd_dest(dest))
18
+
19
+ Jekyll.logger.debug(@tag_name, "Copying #{@name} to #{dd_dest(dest)}")
20
+ FileUtils.cp(path, File.join(dd_dest(dest), @name))
21
+
22
+ true
23
+ end
24
+
25
+ end # Image
26
+ end # Static
27
+ end # DistorteD
28
+ end # Jekyll
@@ -0,0 +1,53 @@
1
+ require 'fileutils'
2
+ require 'set'
3
+
4
+ require 'distorted/pdf'
5
+ require 'distorted-jekyll/static/pdf'
6
+
7
+
8
+ module Jekyll
9
+ module DistorteD
10
+ module Static
11
+ class PDF < Jekyll::DistorteD::Static::State
12
+
13
+ DRIVER = Cooltrainer::DistorteD::PDF
14
+
15
+ MEDIA_TYPE = DRIVER::MEDIA_TYPE
16
+ SUB_TYPE = DRIVER::SUB_TYPE
17
+ MIME_TYPES = DRIVER::MIME_TYPES
18
+
19
+ ATTRS = DRIVER::ATTRS
20
+ ATTRS_DEFAULT = DRIVER::ATTRS_DEFAULT
21
+ ATTRS_VALUES = DRIVER::ATTRS_VALUES
22
+
23
+
24
+ # dest: string realpath to `_site_` directory
25
+ def write(dest)
26
+ return false if File.exist?(path) && !modified?
27
+ self.class.mtimes[path] = mtime
28
+
29
+ # Create any directories to the depth of the intended destination.
30
+ FileUtils.mkdir_p(dd_dest(dest))
31
+
32
+ for variation in files
33
+ if DRIVER::MIME_TYPES.include?(variation[:type])
34
+ pdf_dest_path = File.join(dd_dest(dest), variation[:name])
35
+
36
+ if true # TODO: Make this configurable
37
+ Jekyll.logger.debug(@tag_name, "Optimizing #{@name} and copying to #{dd_dest(dest)}")
38
+ # TODO: Make optimizations/plugins configurable
39
+ DRIVER::optimize(path, pdf_dest_path)
40
+ else
41
+ Jekyll.logger.debug(@tag_name, "Copying #{@name} to #{dd_dest(dest)}")
42
+ FileUtils.cp(path, pdf_dest_path)
43
+ end
44
+ end
45
+ end
46
+
47
+ true
48
+ end
49
+
50
+ end # PDF
51
+ end # Static
52
+ end # DistorteD
53
+ end # Jekyll
@@ -0,0 +1,141 @@
1
+ require 'set'
2
+
3
+ require 'distorted-jekyll/molecule/abstract'
4
+
5
+
6
+ module Jekyll
7
+ module DistorteD
8
+ module Static
9
+ class State < Jekyll::StaticFile
10
+
11
+ include Jekyll::DistorteD::Molecule::Abstract
12
+
13
+ def initialize(
14
+ site,
15
+ base,
16
+ dir,
17
+ name,
18
+ mime,
19
+ attrs,
20
+ dd_dest,
21
+ url,
22
+ collection: nil
23
+ )
24
+ # e.g. 'DistorteD::Static::Image' or 'DistorteD::Static::Video'
25
+ @tag_name = self.class.name.split('::').drop(1).join('::').to_sym.freeze
26
+
27
+ # String path to Jekyll site root
28
+ @base = base
29
+
30
+ # String container dir (under `base`) of original file
31
+ @dir = dir
32
+
33
+ # String filename of original file
34
+ @name = name
35
+
36
+ # Union Set of MIME::Types between the original media file
37
+ # and the plugged MediaMolecule.
38
+ @mime = mime
39
+
40
+ # Attributes provided to our Liquid tag
41
+ @attrs = attrs
42
+
43
+ # String path to media generation output dir
44
+ # under Site.dest (which is currently unknown)
45
+ @dd_dest = dd_dest
46
+
47
+ # String destination URL for the post/page on which the media appears.
48
+ @url = url
49
+
50
+ # Hello yes
51
+ Jekyll.logger.debug(@tag_name, "#{base}/#{dir}/#{name} -> #{url}})")
52
+
53
+ # Construct Jekyll::StaticFile with only the args it takes:
54
+ super(
55
+ site,
56
+ base,
57
+ dir,
58
+ name,
59
+ )
60
+ end
61
+
62
+ def basename
63
+ File.basename(@name, '.*')
64
+ end
65
+
66
+ def extname
67
+ File.extname(@name)
68
+ end
69
+
70
+ # Returns the to-be-written path of a single standard StaticFile.
71
+ # The value returned by this method is only the 'main' or 'original'
72
+ # (even if modified somehow) file and does not include the
73
+ # path/filenames of any variations.
74
+ # This method will be called by jekyll/lib/cleaner#new_files
75
+ # to generate the list of files that need to be build or rebuilt
76
+ # for a site. For this reason, this method shouldn't do any kind
77
+ # of checking the real filesystem, since e.g. its URL-based
78
+ # destdir might not exist yet if the Site.dest is completely blank.
79
+ def destination(dest)
80
+ File.join(dest, @dd_dest, @name)
81
+ end
82
+
83
+ # Return the absolute path to the top-level destination directory
84
+ # of the currently-working media. This will usually be the same path
85
+ # as the Jekyll post/page's generated HTML output.
86
+ def dd_dest(dest)
87
+ File.join(dest, @dd_dest)
88
+ end
89
+
90
+ # This method will be called by our monkey-patched Jekyll::Cleaner#new_files
91
+ # in place of the single-destination method usually used.
92
+ # This allows us to tell Jekyll about more than a single file
93
+ # that should be kept when regenerating the site.
94
+ # This makes DistorteD fast!
95
+ def destinations(dest)
96
+ # TODO: Make outputting the original file optional. Will need to change
97
+ # templates, `modified?`s, and `generate`s to do that.
98
+ filenames.map{|f| File.join(dd_dest(dest), f)} << destination(dest)
99
+ end
100
+
101
+ # HACK HACK HACK
102
+ # Jekyll does not pass this method a site.dest like it does write() and
103
+ # others, but I want to be able to short-circuit here if all the
104
+ # to-be-generated files already exist.
105
+ def modified?
106
+ # Assume modified for the sake of freshness :)
107
+ modified = true
108
+
109
+ site_dest = Jekyll::DistorteD::Floor::config(:destination).to_s
110
+ if Dir.exist?(site_dest)
111
+
112
+ dd_dest = dd_dest(site_dest)
113
+ if Dir.exist?(dd_dest)
114
+
115
+ # TODO: Make outputting the original file conditional.
116
+ # Doing that will require changing the default href handling
117
+ # in the template, Jekyll::DistorteD::Static::State.destinations,
118
+ # as well as Cooltrainer::DistorteD::Image.generate
119
+ wanted_files = Set[@name].merge(filenames)
120
+ extant_files = Dir.entries(dd_dest).to_set
121
+
122
+ # TODO: Make this smarter. It's not enough that all the generated
123
+ # filenames should exist. Try a few more ways to detect subtler
124
+ # "changes to the source file since generation of variations.
125
+ if wanted_files.subset?(extant_files)
126
+ Jekyll.logger.debug(@name, "All variations present: #{wanted_files}")
127
+ modified = false
128
+ else
129
+ Jekyll.logger.debug(@name, "Missing variations: #{wanted_files - extant_files}")
130
+ end
131
+
132
+ end # dd_dest.exists?
133
+ end # site_dest.exists?
134
+ Jekyll.logger.debug("#{@name} modified?", modified)
135
+ return modified
136
+ end
137
+
138
+ end # state
139
+ end # Static
140
+ end # DistorteD
141
+ end # Jekyll