distorted-jekyll 0.5.6 → 0.5.7

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 (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