derived_images 0.3.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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +38 -0
- data/Rakefile +12 -0
- data/lib/derived_images/cache.rb +111 -0
- data/lib/derived_images/dsl.rb +31 -0
- data/lib/derived_images/manifest.rb +58 -0
- data/lib/derived_images/manifest_entry.rb +66 -0
- data/lib/derived_images/processor.rb +74 -0
- data/lib/derived_images/railtie.rb +38 -0
- data/lib/derived_images/version.rb +5 -0
- data/lib/derived_images/worker.rb +56 -0
- data/lib/derived_images.rb +20 -0
- data/lib/install/derived_images.rb +5 -0
- data/lib/install/install.rb +12 -0
- data/lib/tasks/derived_images.rake +16 -0
- metadata +106 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 6de1adc7c9f26d102288dc2d6eccc8581fbe3b0f8ae33934d5ffb3c0b4842b6d
|
4
|
+
data.tar.gz: 5f45a38c7566beaf40dc61861e4b29567add7ff87bcc12d14a5cb7bb53a6ee1f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 91401997195e143d4daa30e136d8b55e206f3b98f209488bc4a8545f457a6bf9391363d64d281678af5df2bd4c1a0ad047953e1c9fb5c7526eecdf276fb60f53
|
7
|
+
data.tar.gz: 5f24b1d1028eafcd016277b0c98ff26abd660c708675374e4360fb640ca12802efdf3412a77710ea0aea2c0a0c54836b4070cd2550f9a272a0df441f91db7a83
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2023 Michael Kitson
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# DerivedImages
|
2
|
+
|
3
|
+
ActiveStorage's `variant` functionality is awesome, so why not have it for images in the Rails asset pipeline too?
|
4
|
+
|
5
|
+
Use DerivedImages to programmatically create image assets by applying transformations to other assets.
|
6
|
+
Resize images, lower quality to save bytes, rotate, crop, convert between formats, and anything else that the
|
7
|
+
[image_processing](https://rubygems.org/gems/image_processing) gem supports.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
1. Run `./bin/bundle add derived_images`
|
12
|
+
2. Run `./bin/rails derived_images:install`
|
13
|
+
3. Install Vips or ImageMagick (if not already installed for ActiveStorage/ImageProcessing)
|
14
|
+
- MacOS: `brew install vips` or `brew install imagemagick`
|
15
|
+
- Debian/Ubuntu: `apt install libvips42` or `apt install imagemagick`
|
16
|
+
|
17
|
+
## Usage
|
18
|
+
|
19
|
+
After installing, specify images to create in the config file at `config/derived_images.rb`.
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
derive 'my_derived_image.webp', from: 'my_source_image.jpg'
|
23
|
+
resize 'tiny.png', from: 'original.png', width: 400, height: 300
|
24
|
+
derive 'fully_custom.jpg', from: 'original.jpg' do |pipeline|
|
25
|
+
pipeline.saver(quality: 50).resize_to_fill(80, 80).rotate(180)
|
26
|
+
end
|
27
|
+
```
|
28
|
+
|
29
|
+
DerivedImages watches this file and the source images, compiling new assets into `app/assets/builds` where the asset
|
30
|
+
pipeline can pick them up with the normal asset helpers.
|
31
|
+
|
32
|
+
```erbruby
|
33
|
+
<%= image_tag('tiny.png') %>
|
34
|
+
```
|
35
|
+
|
36
|
+
## License
|
37
|
+
|
38
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DerivedImages
|
4
|
+
# Build cache for derived image files.
|
5
|
+
class Cache
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
# @param [Pathname, String, nil] path The cache directory, which will be created if missing. Nil disables caching.
|
9
|
+
def initialize(path = DerivedImages.config.cache_path)
|
10
|
+
@path = path && Pathname.new(path)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Copy a cached file out to another path.
|
14
|
+
#
|
15
|
+
# @param [String] key Cache key
|
16
|
+
# @param [Pathname, String] path
|
17
|
+
def copy(key, path)
|
18
|
+
enabled? && FileUtils.copy(key_path(key), path)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Check for the presence of a cached file.
|
22
|
+
#
|
23
|
+
# @param [String] key Cache key
|
24
|
+
# @return [Boolean]
|
25
|
+
def exist?(key)
|
26
|
+
enabled? && key_path(key).file?
|
27
|
+
end
|
28
|
+
|
29
|
+
# Remove a cached file, if it exists.
|
30
|
+
#
|
31
|
+
# @param [String] key Cache key
|
32
|
+
def remove(key)
|
33
|
+
return unless enabled?
|
34
|
+
|
35
|
+
key_path(key).delete if key_path(key).exist?
|
36
|
+
maybe_clean_dir_for(key)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Copy a file into the cache.
|
40
|
+
#
|
41
|
+
# @param [String] key Cache key
|
42
|
+
# @param [Pathname, String] path
|
43
|
+
def store(key, path)
|
44
|
+
return unless enabled?
|
45
|
+
|
46
|
+
mkdir_for(key)
|
47
|
+
FileUtils.copy(path, key_path(key))
|
48
|
+
end
|
49
|
+
|
50
|
+
# Move (not copy) a file into the cache.
|
51
|
+
#
|
52
|
+
# @param [String] key Cache key
|
53
|
+
# @param [Pathname, String] path
|
54
|
+
def take_and_store(key, path)
|
55
|
+
return unless enabled?
|
56
|
+
|
57
|
+
mkdir_for(key)
|
58
|
+
FileUtils.mv(path, key_path(key))
|
59
|
+
end
|
60
|
+
|
61
|
+
# Convert a cache key into a filesystem path of where it would be stored.
|
62
|
+
#
|
63
|
+
# @param [String] key Cache key
|
64
|
+
# @return [Pathname]
|
65
|
+
def key_path(key)
|
66
|
+
path.join(key[0...2], key[2..])
|
67
|
+
end
|
68
|
+
|
69
|
+
# Iterates over cache keys.
|
70
|
+
#
|
71
|
+
# @yieldparam key [String] the cache key
|
72
|
+
# @return [DerivedImages::Cache, Enumerator]
|
73
|
+
def each
|
74
|
+
return enum_for(:each) unless block_given?
|
75
|
+
|
76
|
+
path.glob('*/*') { |path| yield path.to_s.last(65).delete('/') }
|
77
|
+
self
|
78
|
+
end
|
79
|
+
|
80
|
+
# Get the SHA256 hash of a cached file.
|
81
|
+
#
|
82
|
+
# @param [String] key Cache key
|
83
|
+
# @return [String, nil] The hex-encoded digest
|
84
|
+
def digest(key)
|
85
|
+
Digest::SHA256.file(key_path(key)).hexdigest if exist?(key)
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
attr_reader :path
|
91
|
+
|
92
|
+
# Create parent directories so that we can store a value at the given key.
|
93
|
+
#
|
94
|
+
# @param [String] key Cache key
|
95
|
+
def mkdir_for(key)
|
96
|
+
FileUtils.mkdir_p(key_path(key).dirname)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Clean up any unnecessary parent directories above the given cache key.
|
100
|
+
#
|
101
|
+
# @param [String] key Cache key
|
102
|
+
def maybe_clean_dir_for(key)
|
103
|
+
dir = key_path(key).dirname
|
104
|
+
dir.rmdir if dir.directory? && dir.empty?
|
105
|
+
end
|
106
|
+
|
107
|
+
def enabled?
|
108
|
+
!@path.nil?
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DerivedImages
|
4
|
+
# Use these DSL functions in your `config/derived_images.rb` file to define images to derive.
|
5
|
+
#
|
6
|
+
# @note An image format conversions can be automatically inferred from the `target` file extension in these methods.
|
7
|
+
module Dsl
|
8
|
+
# Resize an image, preserving its aspect ratio.
|
9
|
+
#
|
10
|
+
# @param [String] target The relative file name of the target (derived) image
|
11
|
+
# @param [String] from The relative file name of the source image. Must be in one of the configured `image_paths`
|
12
|
+
# @param [Integer] width The max width of the derived image
|
13
|
+
# @param [Integer] height The max height of the derived image
|
14
|
+
# @return [ManifestEntry]
|
15
|
+
def resize(target, from:, width:, height:)
|
16
|
+
derive(target, from: from) { _1.resize_to_limit(width, height) }
|
17
|
+
end
|
18
|
+
|
19
|
+
# Derive one image from another, with full customization abilities.
|
20
|
+
#
|
21
|
+
# @param [String] target The relative file name of the target (derived) image
|
22
|
+
# @param [String] from The relative file name of the source image. Must be in one of the configured `image_paths`
|
23
|
+
# @yieldparam pipeline [ImageProcessing::Chainable] The pipeline you can use to further customize the transformation
|
24
|
+
# @return [ManifestEntry]
|
25
|
+
def derive(target, from:, &block)
|
26
|
+
pipeline = ManifestEntry.empty_pipeline
|
27
|
+
pipeline = yield(pipeline) if block
|
28
|
+
add_entry(ManifestEntry.new(from, target, pipeline))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DerivedImages
|
4
|
+
# The Manifest creates and holds a list of {ManifestEntry} instances, describing every derived image to create.
|
5
|
+
class Manifest
|
6
|
+
include Dsl
|
7
|
+
|
8
|
+
def initialize(path = Rails.root.join(DerivedImages.config.manifest_path))
|
9
|
+
@path = path
|
10
|
+
@entry_map = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def draw(&block)
|
14
|
+
@entry_map.clear
|
15
|
+
if block
|
16
|
+
instance_eval(&block)
|
17
|
+
else
|
18
|
+
instance_eval(File.read(path), path.to_s)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_entry(entry)
|
23
|
+
entry_map[entry.target] = entry
|
24
|
+
end
|
25
|
+
|
26
|
+
delegate :[], :count, :each, :each_value, :filter_map, :key?, :length, to: :entry_map
|
27
|
+
|
28
|
+
def produced_from(source_path)
|
29
|
+
source_names = []
|
30
|
+
DerivedImages.config.image_paths.each do |path|
|
31
|
+
dir = Pathname.new(path).expand_path.realpath
|
32
|
+
contains_source_file = source_path.ascend.any? { _1 == dir }
|
33
|
+
source_names << source_path.relative_path_from(dir).to_s if contains_source_file
|
34
|
+
end
|
35
|
+
entry_map.filter_map { |_target, entry| source_names.include?(entry.source) ? entry : nil }
|
36
|
+
end
|
37
|
+
|
38
|
+
attr_reader :path
|
39
|
+
|
40
|
+
def diff_from(former_manifest)
|
41
|
+
changed = []
|
42
|
+
removed = []
|
43
|
+
former_manifest.each do |target, entry|
|
44
|
+
if key?(target)
|
45
|
+
changed << entry if entry_map[target] != entry
|
46
|
+
else
|
47
|
+
removed << entry
|
48
|
+
end
|
49
|
+
end
|
50
|
+
added = entry_map.filter_map { |target, entry| former_manifest.key?(target) ? nil : entry }
|
51
|
+
[added, changed, removed]
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
attr_reader :entry_map
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DerivedImages
|
4
|
+
# A ManifestEntry describes how to create one derived image.
|
5
|
+
class ManifestEntry
|
6
|
+
attr_accessor :source, :target, :pipeline
|
7
|
+
|
8
|
+
def initialize(source, target, pipeline)
|
9
|
+
@source = source
|
10
|
+
@target = target
|
11
|
+
@pipeline = pipeline
|
12
|
+
end
|
13
|
+
|
14
|
+
def ==(other)
|
15
|
+
source == other.source && target == other.target && options_hash == other.options_hash
|
16
|
+
end
|
17
|
+
|
18
|
+
def source_path
|
19
|
+
DerivedImages.config.image_paths.each do |path|
|
20
|
+
path = Pathname.new(path).join(source).expand_path
|
21
|
+
return path if path.file?
|
22
|
+
end
|
23
|
+
nil
|
24
|
+
end
|
25
|
+
|
26
|
+
def target_path
|
27
|
+
Pathname.new(DerivedImages.config.build_path).join(target).expand_path
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns a cache key for the result of the image transformation. It will vary if the source file's content varies
|
31
|
+
# or if the operations applied to generate the target vary.
|
32
|
+
#
|
33
|
+
# @return [String, nil] A 64 character hexdigest, or nil if the source file can't be found
|
34
|
+
def cache_key
|
35
|
+
return nil unless source_present?
|
36
|
+
|
37
|
+
Digest::SHA256.hexdigest({ source: source_digest, pipeline: options_hash }.to_json)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.empty_pipeline
|
41
|
+
PROCESSORS.fetch(DerivedImages.config.processor).dup
|
42
|
+
end
|
43
|
+
|
44
|
+
PROCESSORS = { mini_magick: ImageProcessing::MiniMagick, vips: ImageProcessing::Vips }.freeze
|
45
|
+
|
46
|
+
def target_digest
|
47
|
+
Digest::SHA256.file(target_path).hexdigest
|
48
|
+
end
|
49
|
+
|
50
|
+
def source_present?
|
51
|
+
source_path&.file?
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def source_digest
|
57
|
+
Digest::SHA256.file(source_path).hexdigest
|
58
|
+
end
|
59
|
+
|
60
|
+
protected
|
61
|
+
|
62
|
+
def options_hash
|
63
|
+
pipeline.branch.options
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DerivedImages
|
4
|
+
# The Processor manages a {Manifest}, watches the filesystem for changes, and manages a pool of {Worker} instances
|
5
|
+
# which perform the image tasks.
|
6
|
+
class Processor
|
7
|
+
def initialize
|
8
|
+
@cache = Cache.new
|
9
|
+
@manifest = Manifest.new.tap(&:draw)
|
10
|
+
@queue = Thread::Queue.new
|
11
|
+
@workers = ThreadGroup.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def watch
|
15
|
+
watch_manifest
|
16
|
+
watch_images
|
17
|
+
process_all
|
18
|
+
end
|
19
|
+
|
20
|
+
def unwatch
|
21
|
+
manifest_listener&.stop
|
22
|
+
image_listener&.stop
|
23
|
+
end
|
24
|
+
|
25
|
+
def run_once
|
26
|
+
process_all
|
27
|
+
queue.close
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
attr_reader :cache, :image_listener, :manifest, :manifest_listener, :queue, :workers
|
33
|
+
|
34
|
+
def watch_manifest
|
35
|
+
dir, file = manifest.path.split.map(&:to_s)
|
36
|
+
@manifest_listener = Listen.to(dir, only: Regexp.new(file)) do
|
37
|
+
DerivedImages.config.logger.debug('Reloading changed manifest')
|
38
|
+
@manifest = Manifest.new.tap(&:draw)
|
39
|
+
process_all
|
40
|
+
end
|
41
|
+
manifest_listener.start
|
42
|
+
end
|
43
|
+
|
44
|
+
def watch_images
|
45
|
+
@image_listener = Listen.to(*DerivedImages.config.image_paths) do |modified, added, removed|
|
46
|
+
(modified + added + removed).each do |path|
|
47
|
+
source_path = Pathname.new(path).expand_path.realpath
|
48
|
+
manifest.produced_from(source_path).each { enqueue(_1) }
|
49
|
+
end
|
50
|
+
prune_cache
|
51
|
+
end
|
52
|
+
image_listener.start
|
53
|
+
end
|
54
|
+
|
55
|
+
def process_all
|
56
|
+
manifest.each_value { enqueue(_1) }
|
57
|
+
prune_cache
|
58
|
+
end
|
59
|
+
|
60
|
+
def enqueue(entry)
|
61
|
+
should_expand = queue.num_waiting.zero? && workers.list.length < DerivedImages.config.threads
|
62
|
+
queue << entry
|
63
|
+
Worker.start(workers, queue) if should_expand
|
64
|
+
end
|
65
|
+
|
66
|
+
def prune_cache
|
67
|
+
expected_keys = manifest.filter_map { |_target, entry| entry.cache_key }
|
68
|
+
(cache.to_a - expected_keys).each do |key|
|
69
|
+
DerivedImages.config.logger.debug("Removing cached file at #{key}")
|
70
|
+
cache.remove(key)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DerivedImages
|
4
|
+
# The Railtie runs the {Processor} as part of a rails server.
|
5
|
+
class Railtie < ::Rails::Railtie
|
6
|
+
config.derived_images = ActiveSupport::OrderedOptions.new.update(
|
7
|
+
build_path: 'app/assets/builds',
|
8
|
+
cache_path: Rails.env.development? ? 'tmp/cache/derived_images' : nil,
|
9
|
+
enabled?: Rails.env.development? || Rails.env.test?,
|
10
|
+
image_paths: ['app/assets/images'],
|
11
|
+
manifest_path: 'config/derived_images.rb',
|
12
|
+
processor: :vips,
|
13
|
+
threads: 5,
|
14
|
+
watch?: Rails.env.development?
|
15
|
+
)
|
16
|
+
|
17
|
+
initializer 'derived_images' do |app|
|
18
|
+
derived_images = app.config.derived_images
|
19
|
+
derived_images.logger = Rails.logger.tagged('derived_images')
|
20
|
+
end
|
21
|
+
|
22
|
+
rake_tasks do
|
23
|
+
load 'tasks/derived_images.rake'
|
24
|
+
end
|
25
|
+
|
26
|
+
server do |app|
|
27
|
+
derived_images = app.config.derived_images
|
28
|
+
next unless derived_images.enabled?
|
29
|
+
|
30
|
+
processor = DerivedImages::Processor.new
|
31
|
+
if derived_images.watch?
|
32
|
+
processor.watch
|
33
|
+
else
|
34
|
+
processor.run_once
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DerivedImages
|
4
|
+
# A Worker represents a thread in a thread pool that completes image creation tasks.
|
5
|
+
class Worker
|
6
|
+
def initialize(queue, cache = Cache.new)
|
7
|
+
@queue = queue
|
8
|
+
@cache = cache
|
9
|
+
end
|
10
|
+
|
11
|
+
def run
|
12
|
+
until queue.closed? && queue.empty?
|
13
|
+
entry = queue.pop
|
14
|
+
process(entry) if entry
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.start(thread_group, queue)
|
19
|
+
DerivedImages.config.logger.debug('Starting a new worker thread')
|
20
|
+
thread_group.add(Thread.new { Worker.new(queue).run })
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
attr_reader :cache, :queue
|
26
|
+
|
27
|
+
def process(entry)
|
28
|
+
unless entry.source_present?
|
29
|
+
return DerivedImages.config.logger.error("Can't find #{entry.source} to build #{entry.target}")
|
30
|
+
end
|
31
|
+
|
32
|
+
cache_key = entry.cache_key
|
33
|
+
if cache.exist?(cache_key)
|
34
|
+
restore(entry, cache_key)
|
35
|
+
else
|
36
|
+
generate(entry, cache_key)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def restore(entry, cache_key)
|
41
|
+
return if entry.target_path.file? && cache.digest(cache_key) == entry.target_digest
|
42
|
+
|
43
|
+
cache.copy(cache_key, entry.target_path)
|
44
|
+
DerivedImages.config.logger.debug("Restored #{entry.target} from cache")
|
45
|
+
end
|
46
|
+
|
47
|
+
def generate(entry, cache_key)
|
48
|
+
time = Benchmark.realtime do
|
49
|
+
tempfile = entry.pipeline.loader(fail: true).call(entry.source_path.to_s)
|
50
|
+
FileUtils.mv(tempfile.path, entry.target_path)
|
51
|
+
end
|
52
|
+
cache.store(cache_key, entry.target_path)
|
53
|
+
DerivedImages.config.logger.info("Created #{entry.target} from #{entry.source} in #{time.round(3)}s")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'image_processing'
|
4
|
+
require 'listen'
|
5
|
+
|
6
|
+
require 'derived_images/cache'
|
7
|
+
require 'derived_images/dsl'
|
8
|
+
require 'derived_images/manifest'
|
9
|
+
require 'derived_images/manifest_entry'
|
10
|
+
require 'derived_images/processor'
|
11
|
+
require 'derived_images/railtie'
|
12
|
+
require 'derived_images/worker'
|
13
|
+
require 'derived_images/version'
|
14
|
+
|
15
|
+
# DerivedImages programmatically creates derived image assets by applying transformations to other assets.
|
16
|
+
module DerivedImages
|
17
|
+
def self.config
|
18
|
+
Rails.application.config.derived_images
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
say 'Create derived_images.rb manifest'
|
4
|
+
copy_file "#{__dir__}/derived_images.rb", 'config/derived_images.rb'
|
5
|
+
|
6
|
+
say 'Compile into app/assets/builds'
|
7
|
+
empty_directory 'app/assets/builds'
|
8
|
+
keep_file 'app/assets/builds'
|
9
|
+
|
10
|
+
if (sprockets_manifest_path = Rails.root.join('app/assets/config/manifest.js')).exist?
|
11
|
+
append_to_file sprockets_manifest_path, %(//= link_tree ../builds\n)
|
12
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
namespace :derived_images do
|
4
|
+
desc 'install derived_images'
|
5
|
+
task :install do
|
6
|
+
system "#{RbConfig.ruby} ./bin/rails app:template LOCATION=#{File.expand_path('../install/install.rb',
|
7
|
+
__dir__)}"
|
8
|
+
end
|
9
|
+
|
10
|
+
desc 'build assets from derived_images once'
|
11
|
+
task :build do
|
12
|
+
DerivedImages::Processor.new.run_once
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
Rake::Task['assets:precompile'].enhance(['derived_images:build']) if Rake::Task.task_defined?('assets:precompile')
|
metadata
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: derived_images
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Michael Kitson
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-05-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: image_processing
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: listen
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: railties
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '6.1'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '6.1'
|
55
|
+
description: |-
|
56
|
+
Resize images, lower quality to save bytes, rotate, crop, convert between formats, and anything \
|
57
|
+
else that the image_processing gem supports.
|
58
|
+
email:
|
59
|
+
executables: []
|
60
|
+
extensions: []
|
61
|
+
extra_rdoc_files: []
|
62
|
+
files:
|
63
|
+
- MIT-LICENSE
|
64
|
+
- README.md
|
65
|
+
- Rakefile
|
66
|
+
- lib/derived_images.rb
|
67
|
+
- lib/derived_images/cache.rb
|
68
|
+
- lib/derived_images/dsl.rb
|
69
|
+
- lib/derived_images/manifest.rb
|
70
|
+
- lib/derived_images/manifest_entry.rb
|
71
|
+
- lib/derived_images/processor.rb
|
72
|
+
- lib/derived_images/railtie.rb
|
73
|
+
- lib/derived_images/version.rb
|
74
|
+
- lib/derived_images/worker.rb
|
75
|
+
- lib/install/derived_images.rb
|
76
|
+
- lib/install/install.rb
|
77
|
+
- lib/tasks/derived_images.rake
|
78
|
+
homepage: https://github.com/michaelkitson/derived_images
|
79
|
+
licenses:
|
80
|
+
- MIT
|
81
|
+
metadata:
|
82
|
+
homepage_uri: https://github.com/michaelkitson/derived_images
|
83
|
+
source_code_uri: https://github.com/michaelkitson/derived_images
|
84
|
+
changelog_uri: https://github.com/michaelkitson/derived_images/releases
|
85
|
+
rubygems_mfa_required: 'true'
|
86
|
+
post_install_message:
|
87
|
+
rdoc_options: []
|
88
|
+
require_paths:
|
89
|
+
- lib
|
90
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: 2.7.0
|
95
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
100
|
+
requirements: []
|
101
|
+
rubygems_version: 3.4.13
|
102
|
+
signing_key:
|
103
|
+
specification_version: 4
|
104
|
+
summary: Programmatically create derived image assets by applying transformations
|
105
|
+
to other assets.
|
106
|
+
test_files: []
|