asset_packer 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/.travis.yml +13 -0
- data/Changelog.md +7 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +95 -0
- data/LICENSE +9 -0
- data/README.md +58 -0
- data/Rakefile +31 -0
- data/asset_packer.gemspec +29 -0
- data/bin/asset_packer +4 -0
- data/lib/asset_packer/cli.rb +64 -0
- data/lib/asset_packer/processor/chain.rb +13 -0
- data/lib/asset_packer/processor/local.rb +47 -0
- data/lib/asset_packer/processor.rb +28 -0
- data/lib/asset_packer/version.rb +3 -0
- data/lib/asset_packer.rb +17 -0
- data/spec/asset_packer/cli_spec.rb +120 -0
- data/spec/asset_packer/processor/chain_spec.rb +11 -0
- data/spec/asset_packer/processor/local/image_spec.rb +16 -0
- data/spec/asset_packer/processor/local/script_spec.rb +15 -0
- data/spec/asset_packer/processor/local/stylesheet_spec.rb +16 -0
- data/spec/asset_packer/processor/local_spec.rb +49 -0
- data/spec/asset_packer/processor_spec.rb +71 -0
- data/spec/fixtures/dummy.png +1 -0
- data/spec/fixtures/has_all.html +9 -0
- data/spec/fixtures/has_img.html +5 -0
- data/spec/fixtures/has_script.html +5 -0
- data/spec/fixtures/has_stylesheet.html +5 -0
- data/spec/fixtures/index.html +1 -0
- data/spec/fixtures/script.js +1 -0
- data/spec/fixtures/section.css +1 -0
- data/spec/fixtures/style.css +1 -0
- data/spec/shared.rb +31 -0
- data/spec/spec_helper.rb +29 -0
- metadata +169 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: fed08b143d17f83d7b3be23dcaba82a34f975260
|
4
|
+
data.tar.gz: f4c6bb6e541dea3ad290fc7c6a8c819a4b0c98cf
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e899b50645ee4bbffde1755cb68871f9819d2110f85848bdc737cba5bf48b7939d0f9c9d2871451ca507902b027342e1e26b686e4868b872c2faab3257c81b6b
|
7
|
+
data.tar.gz: b607f34ef91361c3fa574a013c05f45c201c1168daf0185c07d73bbea573a2793dc87fd0ee6e659db128d36bf6dfccce5e0b981a4357340981c5aaefa1cdc6bd
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Changelog.md
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
asset_packer (0.1.0)
|
5
|
+
hexp (~> 0.3.3)
|
6
|
+
mime-types (~> 2.2)
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
abstract_type (0.0.7)
|
12
|
+
adamantium (0.2.0)
|
13
|
+
ice_nine (~> 0.11.0)
|
14
|
+
memoizable (~> 0.4.0)
|
15
|
+
anima (0.2.0)
|
16
|
+
abstract_type (~> 0.0.7)
|
17
|
+
adamantium (~> 0.1)
|
18
|
+
equalizer (~> 0.0.8)
|
19
|
+
ast (1.1.0)
|
20
|
+
concord (0.1.4)
|
21
|
+
adamantium (~> 0.1)
|
22
|
+
equalizer (~> 0.0.7)
|
23
|
+
diff-lcs (1.2.5)
|
24
|
+
equalizer (0.0.9)
|
25
|
+
hexp (0.3.3)
|
26
|
+
equalizer (~> 0.0)
|
27
|
+
ice_nine (~> 0.9)
|
28
|
+
nokogiri (~> 1.6)
|
29
|
+
sass (~> 3.2.19)
|
30
|
+
ice_nine (0.11.0)
|
31
|
+
inflecto (0.0.2)
|
32
|
+
memoizable (0.4.2)
|
33
|
+
thread_safe (~> 0.3, >= 0.3.1)
|
34
|
+
mime-types (2.2)
|
35
|
+
mini_portile (0.5.3)
|
36
|
+
morpher (0.2.2)
|
37
|
+
abstract_type (~> 0.0.7)
|
38
|
+
adamantium (~> 0.2.0)
|
39
|
+
anima (~> 0.2.0)
|
40
|
+
ast (~> 1.1.0)
|
41
|
+
concord (~> 0.1.4)
|
42
|
+
equalizer (~> 0.0.9)
|
43
|
+
ice_nine (~> 0.11.0)
|
44
|
+
procto (~> 0.0.2)
|
45
|
+
mutant (0.5.10)
|
46
|
+
abstract_type (~> 0.0.7)
|
47
|
+
adamantium (~> 0.2.0)
|
48
|
+
anima (~> 0.2.0)
|
49
|
+
concord (~> 0.1.4)
|
50
|
+
diff-lcs (~> 1.2)
|
51
|
+
equalizer (~> 0.0.9)
|
52
|
+
ice_nine (~> 0.11.0)
|
53
|
+
inflecto (~> 0.0.2)
|
54
|
+
memoizable (~> 0.4.2)
|
55
|
+
morpher (~> 0.2.1)
|
56
|
+
parser (~> 2.1)
|
57
|
+
procto (~> 0.0.2)
|
58
|
+
unparser (~> 0.1.10)
|
59
|
+
mutant-rspec (0.5.10)
|
60
|
+
mutant (~> 0.5.10)
|
61
|
+
rspec-core (>= 2.14.1, <= 3.0.0.beta2)
|
62
|
+
nokogiri (1.6.1)
|
63
|
+
mini_portile (~> 0.5.0)
|
64
|
+
parser (2.1.8)
|
65
|
+
ast (~> 1.1)
|
66
|
+
slop (~> 3.4, >= 3.4.5)
|
67
|
+
procto (0.0.2)
|
68
|
+
rake (10.3.1)
|
69
|
+
rspec (2.14.1)
|
70
|
+
rspec-core (~> 2.14.0)
|
71
|
+
rspec-expectations (~> 2.14.0)
|
72
|
+
rspec-mocks (~> 2.14.0)
|
73
|
+
rspec-core (2.14.8)
|
74
|
+
rspec-expectations (2.14.5)
|
75
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
76
|
+
rspec-mocks (2.14.6)
|
77
|
+
sass (3.2.19)
|
78
|
+
slop (3.5.0)
|
79
|
+
thread_safe (0.3.3)
|
80
|
+
unparser (0.1.12)
|
81
|
+
abstract_type (~> 0.0.7)
|
82
|
+
adamantium (~> 0.2.0)
|
83
|
+
concord (~> 0.1.4)
|
84
|
+
equalizer (~> 0.0.9)
|
85
|
+
parser (~> 2.1)
|
86
|
+
procto (~> 0.0.2)
|
87
|
+
|
88
|
+
PLATFORMS
|
89
|
+
ruby
|
90
|
+
|
91
|
+
DEPENDENCIES
|
92
|
+
asset_packer!
|
93
|
+
mutant-rspec (~> 0.5.10)
|
94
|
+
rake (~> 10.2)
|
95
|
+
rspec (~> 2.14)
|
data/LICENSE
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
Copyright (c) 2014 Arne Brasseur
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
6
|
+
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
8
|
+
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
[![Gem Version](https://badge.fury.io/rb/asset_packer.png)][gem]
|
2
|
+
[![Build Status](https://secure.travis-ci.org/plexus/asset_packer.png?branch=master)][travis]
|
3
|
+
[![Code Climate](https://codeclimate.com/github/plexus/asset_packer.png)][codeclimate]
|
4
|
+
|
5
|
+
[gem]: https://rubygems.org/gems/asset_packer
|
6
|
+
[travis]: https://travis-ci.org/plexus/asset_packer
|
7
|
+
[codeclimate]: https://codeclimate.com/github/plexus/asset_packer
|
8
|
+
|
9
|
+
# Asset Packager
|
10
|
+
|
11
|
+
Given a HTML file (local or remote), asset packager will download all assets (images, stylesheets, scripts) to a local folder, and rewrite the HTML file to point to the local files. The result can be easily copied onto a USB stick and used off-line.
|
12
|
+
|
13
|
+
Asset Packager is part of [Slippery](https://github.com/plexus/slippery), a tool for creating presentations with Markdown, but can also be used stand-alone.
|
14
|
+
|
15
|
+
## Command line usage
|
16
|
+
|
17
|
+
```
|
18
|
+
asset_packer file.html target.html
|
19
|
+
asset_packer http://example.org/file.html target.html
|
20
|
+
```
|
21
|
+
|
22
|
+
This will create `target.html`, and a directory `target_assets` containing all the assets.
|
23
|
+
|
24
|
+
## Hexp usage
|
25
|
+
|
26
|
+
Asset Packager is at its core a Hexp transformation, it transform one HTML DOM tree into another. While doing so it creates some files. To use it directly on a Hexp document you need to pass it a bit of extra information
|
27
|
+
|
28
|
+
* the source URI, needed to resolve relative URI's
|
29
|
+
* the asset directory, where assets will be stored. Will be created if it doesn't exist
|
30
|
+
* the destination HTML file name, used to calculate new relative URI's for the assets
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
doc = Hexp.parse(...)
|
34
|
+
doc = AssetPacker::Processor::Local.new('http://foo/bar', '/tmp/assets', '/tmp/result.html').call(doc)
|
35
|
+
File.write('/tmp/result.html', doc.to_html)
|
36
|
+
```
|
37
|
+
|
38
|
+
## Transformations
|
39
|
+
|
40
|
+
So far, the following assets are recognized
|
41
|
+
|
42
|
+
* img[src]
|
43
|
+
* link[rel="stylesheet"][href]
|
44
|
+
* script[src]
|
45
|
+
|
46
|
+
## Mutation Testing
|
47
|
+
|
48
|
+
Asset Packager has 100% mutation coverage using [Mutant](https://github.com/mbj/mutant). It is a rewrite of functionality originally included in Slippery, used to demonstrate the concept of mutation testing for an article for Sitepoint.
|
49
|
+
|
50
|
+
## Contributing
|
51
|
+
|
52
|
+
Use a feature branch, make sure `rake mutant` tells you all is fine, then send a pull request.
|
53
|
+
|
54
|
+
## License
|
55
|
+
|
56
|
+
Copyright 2014 Arne Brasseur
|
57
|
+
|
58
|
+
Available under the MIT license, see LICENSE file for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
########################################
|
2
|
+
# Testing
|
3
|
+
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
require 'mutant'
|
6
|
+
|
7
|
+
RSpec::Core::RakeTask.new
|
8
|
+
|
9
|
+
task :default => :mutant
|
10
|
+
|
11
|
+
task :mutant do
|
12
|
+
pattern = ENV.fetch('PATTERN', 'AssetPacker*')
|
13
|
+
result = Mutant::CLI.run(%w[-Ilib -rasset_packer --use rspec --score 100] + [pattern])
|
14
|
+
fail unless result == Mutant::CLI::EXIT_SUCCESS
|
15
|
+
end
|
16
|
+
|
17
|
+
########################################
|
18
|
+
# Packaging
|
19
|
+
|
20
|
+
require 'rubygems/package_task'
|
21
|
+
spec = Gem::Specification.load(File.expand_path('../asset_packer.gemspec', __FILE__))
|
22
|
+
gem = Gem::PackageTask.new(spec)
|
23
|
+
gem.define
|
24
|
+
|
25
|
+
|
26
|
+
desc "Push gem to rubygems.org"
|
27
|
+
task :push => :gem do
|
28
|
+
#sh "git tag v#{AssetPacker::VERSION}"
|
29
|
+
sh "git push --tags"
|
30
|
+
sh "gem push pkg/asset_packer-#{AssetPacker::VERSION}.gem"
|
31
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require File.expand_path('../lib/asset_packer/version', __FILE__)
|
4
|
+
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gem.name = 'asset_packer'
|
7
|
+
gem.version = AssetPacker::VERSION
|
8
|
+
gem.authors = [ 'Arne Brasseur' ]
|
9
|
+
gem.email = [ 'arne@arnebrasseur.net' ]
|
10
|
+
gem.description = 'Create an offline version of a HTML file.'
|
11
|
+
gem.summary = gem.description
|
12
|
+
gem.homepage = 'https://github.com/plexus/asset_packer'
|
13
|
+
gem.license = 'MIT'
|
14
|
+
|
15
|
+
gem.require_paths = %w[lib]
|
16
|
+
gem.files = `git ls-files`.split($/)
|
17
|
+
gem.test_files = `git ls-files -- spec`.split($/)
|
18
|
+
gem.extra_rdoc_files = %w[README.md LICENSE]
|
19
|
+
|
20
|
+
gem.bindir = 'bin'
|
21
|
+
gem.executables << 'asset_packer'
|
22
|
+
|
23
|
+
gem.add_runtime_dependency 'hexp' , '~> 0.3.3'
|
24
|
+
gem.add_runtime_dependency 'mime-types' , '~> 2.2'
|
25
|
+
|
26
|
+
gem.add_development_dependency 'rake' , '~> 10.2'
|
27
|
+
gem.add_development_dependency 'rspec' , '~> 2.14'
|
28
|
+
gem.add_development_dependency 'mutant-rspec' , '~> 0.5.10'
|
29
|
+
end
|
data/bin/asset_packer
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
# asset_packer file.html
|
2
|
+
# asset_packer --standalone file.html
|
3
|
+
# asset_packer --help
|
4
|
+
|
5
|
+
module AssetPacker
|
6
|
+
class CLI
|
7
|
+
EXIT_SUCCESS = 0
|
8
|
+
EXIT_FAILURE = 1
|
9
|
+
|
10
|
+
class ExitEarly < StandardError
|
11
|
+
attr_reader :exit_code
|
12
|
+
|
13
|
+
def initialize(message, exit_code = 0)
|
14
|
+
super(message)
|
15
|
+
@exit_code = exit_code
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :infile, :outfile
|
20
|
+
|
21
|
+
def initialize(infile, outfile)
|
22
|
+
@infile = infile
|
23
|
+
@outfile = Pathname(outfile).expand_path
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.run(argv)
|
27
|
+
create_from_args(argv).run
|
28
|
+
EXIT_SUCCESS
|
29
|
+
rescue ExitEarly => exception
|
30
|
+
$stderr.puts(exception)
|
31
|
+
exception.exit_code
|
32
|
+
rescue => exception
|
33
|
+
$stderr.puts(exception)
|
34
|
+
EXIT_FAILURE
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.create_from_args(argv)
|
38
|
+
opts = OptionParser.new do |opts|
|
39
|
+
opts.banner = "Usage: asset_packer [options] input_file.html output_file.html"
|
40
|
+
opts.on('--version', 'Print asset_packers version') do |name|
|
41
|
+
raise ExitEarly, "asset_packer-#{AssetPacker::VERSION}"
|
42
|
+
end.on('--help', 'Display help on command line usage') do
|
43
|
+
raise ExitEarly, opts
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
opts.parse!(argv)
|
48
|
+
|
49
|
+
if argv.length != 2
|
50
|
+
raise ExitEarly.new(opts, EXIT_FAILURE)
|
51
|
+
end
|
52
|
+
|
53
|
+
new(*argv)
|
54
|
+
end
|
55
|
+
|
56
|
+
def run
|
57
|
+
asset_dir = outfile.dirname.join("#{outfile.basename(outfile.extname)}_assets")
|
58
|
+
local = Processor::Local.new(infile, asset_dir, outfile)
|
59
|
+
doc = Hexp.parse(local.retrieve_asset(infile))
|
60
|
+
File.write(outfile, local.(doc).to_html)
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module AssetPacker
|
2
|
+
class Processor
|
3
|
+
class Local < self
|
4
|
+
|
5
|
+
def call(doc)
|
6
|
+
Chain.new([Image, Script, Stylesheet].map {|klz|
|
7
|
+
klz.new(source_uri, asset_dir, destination)
|
8
|
+
}).(doc)
|
9
|
+
end
|
10
|
+
|
11
|
+
def save_asset(uri, extension)
|
12
|
+
asset_dir.mkdir unless asset_dir.directory?
|
13
|
+
content = retrieve_asset(uri)
|
14
|
+
digest = Digest::MD5.hexdigest(content)
|
15
|
+
target = asset_dir.join(digest + '.' + extension)
|
16
|
+
File.write(target, content) unless target.exist?
|
17
|
+
target.relative_path_from(destination.dirname)
|
18
|
+
end
|
19
|
+
|
20
|
+
class Image < self
|
21
|
+
def call(doc)
|
22
|
+
doc.replace('img') do |img|
|
23
|
+
uri = img[:src]
|
24
|
+
extension = File.extname(uri).sub(/^\./, '')
|
25
|
+
img.attr(:src, save_asset(uri, extension))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Script < self
|
31
|
+
def call(doc)
|
32
|
+
doc.replace('script[src]') do |script|
|
33
|
+
script.attr(:src, save_asset(script[:src], 'js'))
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class Stylesheet < self
|
39
|
+
def call(doc)
|
40
|
+
doc.replace('link[rel=stylesheet]') do |link|
|
41
|
+
link.attr(:href, save_asset(link[:href], 'css'))
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module AssetPacker
|
2
|
+
class Processor
|
3
|
+
attr_reader :source_uri, :asset_dir, :destination
|
4
|
+
|
5
|
+
# source_uri: location of the original document, used to retrieve relative URI's
|
6
|
+
# asset_dir: location where assets will be stored
|
7
|
+
# destination: file that will be generated, used to create relative URI's from
|
8
|
+
def initialize(source_uri, asset_dir, destination)
|
9
|
+
@source_uri = URI(source_uri)
|
10
|
+
if @source_uri.relative?
|
11
|
+
@source_uri = URI("file://#{Pathname(source_uri).expand_path}")
|
12
|
+
end
|
13
|
+
@asset_dir = Pathname(asset_dir)
|
14
|
+
@destination = Pathname(destination)
|
15
|
+
end
|
16
|
+
|
17
|
+
def retrieve_asset(uri)
|
18
|
+
uri = URI.join(source_uri, uri)
|
19
|
+
case
|
20
|
+
when %w[http https].include?(uri.scheme)
|
21
|
+
Net::HTTP.get(uri)
|
22
|
+
when uri.scheme.eql?('file')
|
23
|
+
File.read(uri.path)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
data/lib/asset_packer.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'net/http'
|
3
|
+
require 'base64'
|
4
|
+
require 'optparse'
|
5
|
+
|
6
|
+
require 'hexp'
|
7
|
+
require 'mime/types'
|
8
|
+
|
9
|
+
module AssetPacker
|
10
|
+
ROOT = Pathname(__FILE__).dirname.parent
|
11
|
+
end
|
12
|
+
|
13
|
+
require 'asset_packer/version'
|
14
|
+
require 'asset_packer/cli'
|
15
|
+
require 'asset_packer/processor'
|
16
|
+
require 'asset_packer/processor/local'
|
17
|
+
require 'asset_packer/processor/chain'
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe AssetPacker::CLI do
|
4
|
+
describe '.run' do
|
5
|
+
let(:cli) { double(run: nil) }
|
6
|
+
let(:argv) { [] }
|
7
|
+
subject { described_class.run(argv) }
|
8
|
+
|
9
|
+
before do
|
10
|
+
expect(described_class).to \
|
11
|
+
receive(:create_from_args)
|
12
|
+
.with(argv)
|
13
|
+
.and_return(cli)
|
14
|
+
end
|
15
|
+
|
16
|
+
context 'with a succesful run' do
|
17
|
+
it 'should return EXIT_SUCCESS' do
|
18
|
+
expect(subject).to eq 0
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
shared_examples_for 'a non-succesful run' do
|
23
|
+
before do
|
24
|
+
allow(cli).to receive(:run, &run_impl)
|
25
|
+
orig_stderr, $stderr = $stderr, StringIO.new
|
26
|
+
@result = subject
|
27
|
+
$stderr, @captured_io = orig_stderr, $stderr
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'should output the exception message to $stderr' do
|
31
|
+
expect(@captured_io.string).to eq message
|
32
|
+
expect(@result).to eq exit_code
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'when receiving an ExitEarly exception' do
|
38
|
+
let(:run_impl) { ->{ raise described_class::ExitEarly.new('mezage', 123) } }
|
39
|
+
let(:message) { "mezage\n" }
|
40
|
+
let(:exit_code) { 123 }
|
41
|
+
|
42
|
+
it_should_behave_like 'a non-succesful run'
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'when receiving another exception' do
|
46
|
+
let(:run_impl) { ->{ raise ArgumentError, 'foo bar' } }
|
47
|
+
let(:message) { "foo bar\n" }
|
48
|
+
let(:exit_code) { 1 }
|
49
|
+
|
50
|
+
it_should_behave_like 'a non-succesful run'
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
describe '.create_from_args' do
|
56
|
+
subject(:cli) { described_class.create_from_args(argv) }
|
57
|
+
let(:argv) { %w[file1 file2] }
|
58
|
+
|
59
|
+
its(:infile) { should eq 'file1' }
|
60
|
+
its(:outfile) { should eq Pathname('file2').expand_path }
|
61
|
+
|
62
|
+
context 'given --help' do
|
63
|
+
let(:argv) { %w[ --help ] }
|
64
|
+
|
65
|
+
it 'should display the help information' do
|
66
|
+
expect { cli }.to raise_error {|error|
|
67
|
+
expect(error.message).to match /Usage: asset_packer/
|
68
|
+
expect(error.exit_code).to eq 0
|
69
|
+
}
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context 'given --version' do
|
74
|
+
let(:argv) { %w[ --version ] }
|
75
|
+
|
76
|
+
it 'should display the help information' do
|
77
|
+
expect { cli }.to raise_error {|error|
|
78
|
+
expect(error.message).to match /asset_packer-\d+\.\d+\.\d+/
|
79
|
+
expect(error.exit_code).to eq 0
|
80
|
+
}
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
shared_examples_for 'illegal arguments' do |argv|
|
85
|
+
it 'should display the help information' do
|
86
|
+
expect { described_class.create_from_args(argv) }.to raise_error {|error|
|
87
|
+
expect(error.message).to match /Usage: asset_packer/
|
88
|
+
expect(error.exit_code).to eq 1
|
89
|
+
}
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
include_examples 'illegal arguments', %w[]
|
94
|
+
include_examples 'illegal arguments', %w[only_one_filename]
|
95
|
+
include_examples 'illegal arguments', %w[three file names]
|
96
|
+
end
|
97
|
+
|
98
|
+
describe '#run' do
|
99
|
+
with_fixture 'has_all.html',
|
100
|
+
'dummy.png' => 'images/dummy.png',
|
101
|
+
'script.js' => 'scripts/script.js',
|
102
|
+
'style.css' => 'assets/style.css'
|
103
|
+
|
104
|
+
subject(:cli) { described_class.new(source_uri.to_s, destination) }
|
105
|
+
|
106
|
+
it 'should create the outfile' do
|
107
|
+
cli.run
|
108
|
+
expect(destination).to exist
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'should have rewritten all the assets' do
|
112
|
+
cli.run
|
113
|
+
doc = Nokogiri(destination.read)
|
114
|
+
expect(doc.css('script[src="has_all_assets/7d2ac8ea2ea878a64bb26221bc358d76.js"]').count).to eq 1
|
115
|
+
expect(doc.css('img[src="has_all_assets/900150983cd24fb0d6963f7d28e17f72.png"]').count).to eq 1
|
116
|
+
expect(doc.css('link[href="has_all_assets/383296b94213ea174c8bee073ea59733.css"]').count).to eq 1
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe AssetPacker::Processor::Local::Image, '#call' do
|
4
|
+
with_fixture 'has_img.html', 'dummy.png' => 'images/dummy.png'
|
5
|
+
let(:doc) { Hexp.parse(source_uri.read) }
|
6
|
+
|
7
|
+
it_behaves_like 'a local asset', '900150983cd24fb0d6963f7d28e17f72.png', 'abc'
|
8
|
+
|
9
|
+
describe '#call' do
|
10
|
+
it 'should replace the image source' do
|
11
|
+
result = processor.call(doc)
|
12
|
+
expect(result.select('img[src="900150983cd24fb0d6963f7d28e17f72.png"]').count).to eq 1
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe AssetPacker::Processor::Local::Script do
|
4
|
+
with_fixture 'has_script.html', 'script.js' => 'scripts/script.js'
|
5
|
+
let(:doc) { Hexp.parse(source_uri.read) }
|
6
|
+
|
7
|
+
it_behaves_like 'a local asset', '7d2ac8ea2ea878a64bb26221bc358d76.js', '/*javascript*/'
|
8
|
+
|
9
|
+
describe '#call' do
|
10
|
+
it 'should replace the script source' do
|
11
|
+
result = processor.call(doc)
|
12
|
+
expect(result.select('script[src="7d2ac8ea2ea878a64bb26221bc358d76.js"]').count).to eq 1
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe AssetPacker::Processor::Local::Stylesheet, '#call' do
|
4
|
+
with_fixture 'has_stylesheet.html', 'style.css' => 'assets/style.css'
|
5
|
+
let(:doc) { Hexp.parse(source_uri.read) }
|
6
|
+
|
7
|
+
it_behaves_like 'a local asset', '383296b94213ea174c8bee073ea59733.css', '/*style*/'
|
8
|
+
|
9
|
+
describe '#call' do
|
10
|
+
it 'should replace the styleheet href' do
|
11
|
+
result = processor.call(doc)
|
12
|
+
expect(result.select('link[href="383296b94213ea174c8bee073ea59733.css"]').count).to eq 1
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe AssetPacker::Processor::Local do
|
4
|
+
with_fixture 'index.html', ['section.css']
|
5
|
+
|
6
|
+
describe '#save_asset' do
|
7
|
+
let(:target_path) { processor.asset_dir.join('4056951747637c9c22a635e09da36fea.css') }
|
8
|
+
|
9
|
+
it 'should compute the path based on md5 digest and extension' do
|
10
|
+
expect(processor.save_asset('section.css', 'css')).to eq Pathname('4056951747637c9c22a635e09da36fea.css')
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should retrieve the asset's content" do
|
14
|
+
processor.save_asset('section.css', 'css')
|
15
|
+
expect(target_path.read).to eq 'section { color: blue; }'
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should reuse existing assets with identical digest" do
|
19
|
+
File.write(target_path, 'foobar')
|
20
|
+
processor.save_asset('section.css', 'css')
|
21
|
+
expect(target_path.read).to eq 'foobar'
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "with an asset directory that doesn't exist" do
|
25
|
+
let(:dst_dir) { Pathname(Dir.mktmpdir()).join('assets') }
|
26
|
+
let(:destination) { dst_dir.join(html_file).join('..', html_file) }
|
27
|
+
|
28
|
+
it 'should create the asset directory' do
|
29
|
+
processor.save_asset('section.css', 'css')
|
30
|
+
expect(dst_dir).to exist
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe '#call' do
|
36
|
+
with_fixture 'has_all.html',
|
37
|
+
'dummy.png' => 'images/dummy.png',
|
38
|
+
'script.js' => 'scripts/script.js',
|
39
|
+
'style.css' => 'assets/style.css'
|
40
|
+
|
41
|
+
let(:result) { processor.call(Hexp.parse(source_uri.read)) }
|
42
|
+
|
43
|
+
it 'should delegate to its subclasses' do
|
44
|
+
expect(result.select('script[src="7d2ac8ea2ea878a64bb26221bc358d76.js"]').count).to eq 1
|
45
|
+
expect(result.select('img[src="900150983cd24fb0d6963f7d28e17f72.png"]').count).to eq 1
|
46
|
+
expect(result.select('link[href="383296b94213ea174c8bee073ea59733.css"]').count).to eq 1
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe AssetPacker::Processor do
|
4
|
+
with_fixture 'index.html', ['section.css']
|
5
|
+
|
6
|
+
let(:fixture_pathname) { src_dir.join 'section.css' }
|
7
|
+
|
8
|
+
its(:source_uri) { should eq URI("file://#{source_uri}") }
|
9
|
+
|
10
|
+
describe '#initialize' do
|
11
|
+
let(:subject) { described_class.new('/source/file.html', '/asset_dir', '/dest.html') }
|
12
|
+
its(:source_uri) { should eq URI('file:///source/file.html') }
|
13
|
+
its(:asset_dir) { should eq Pathname('/asset_dir') }
|
14
|
+
its(:destination) { should eq Pathname('/dest.html') }
|
15
|
+
|
16
|
+
context 'with absolute URI' do
|
17
|
+
let(:subject) { described_class.new('http://source/file.html', '/asset_dir', '/dest.html') }
|
18
|
+
its(:source_uri) { should eq URI('http://source/file.html') }
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'with a relative file name' do
|
22
|
+
let(:subject) { described_class.new('foo/bar.html', '/asset_dir', 'dest.html') }
|
23
|
+
its(:source_uri) { should eq URI("file://#{AssetPacker::ROOT.join('foo/bar.html')}") }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe '#retrieve_asset' do
|
28
|
+
subject(:asset) { processor.retrieve_asset(uri) }
|
29
|
+
|
30
|
+
shared_examples 'local files' do
|
31
|
+
it 'should load the file from the local file system' do
|
32
|
+
expect(asset).to eq 'section { color: blue; }'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe 'with a file URI' do
|
37
|
+
let(:uri) { "file://#{fixture_pathname}" }
|
38
|
+
include_examples 'local files'
|
39
|
+
end
|
40
|
+
|
41
|
+
describe 'with an absolute path' do
|
42
|
+
let(:uri) { fixture_pathname.to_s }
|
43
|
+
include_examples 'local files'
|
44
|
+
end
|
45
|
+
|
46
|
+
describe 'with a relative path' do
|
47
|
+
let(:uri) { 'section.css' }
|
48
|
+
include_examples 'local files'
|
49
|
+
end
|
50
|
+
|
51
|
+
shared_examples 'remote URIs' do |uri|
|
52
|
+
it 'should retrieve the file through Net::HTTP' do
|
53
|
+
expect(Net::HTTP).to receive(:get).with(URI(uri)).and_return('abc')
|
54
|
+
expect(processor.retrieve_asset(uri)).to eq 'abc'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
include_examples 'remote URIs', 'http://foo.bar/baz'
|
59
|
+
include_examples 'remote URIs', 'https://foo.bar/baz'
|
60
|
+
|
61
|
+
describe 'with an unrecognized protocol' do
|
62
|
+
let(:uri) { 'ftp://example.com' }
|
63
|
+
|
64
|
+
it 'should return nil' do
|
65
|
+
expect(Net::HTTP).to_not receive(:get)
|
66
|
+
expect(asset).to be_nil
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
abc
|
@@ -0,0 +1 @@
|
|
1
|
+
<foo>
|
@@ -0,0 +1 @@
|
|
1
|
+
/*javascript*/
|
@@ -0,0 +1 @@
|
|
1
|
+
section { color: blue; }
|
@@ -0,0 +1 @@
|
|
1
|
+
/*style*/
|
data/spec/shared.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
shared_examples 'a local asset' do |filename, contents|
|
2
|
+
let(:destination) { dst_dir.join(filename) }
|
3
|
+
|
4
|
+
it 'should save the file to an MD5 digest based file name' do
|
5
|
+
processor.call(doc)
|
6
|
+
expect(destination).to exist
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'should save the asset with the correct contents' do
|
10
|
+
processor.call(doc)
|
11
|
+
expect(destination.read).to eq contents
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
shared_context 'copy fixtures to tmpdir' do
|
16
|
+
before do
|
17
|
+
FileUtils.cp fixture_dir.join(html_file), src_dir.join(html_file)
|
18
|
+
|
19
|
+
assets.each do |src, dst|
|
20
|
+
dst ||= src
|
21
|
+
dst = src_dir.join(dst)
|
22
|
+
Dir.mkdir(dst.dirname) unless dst.dirname.exist?
|
23
|
+
FileUtils.cp fixture_dir.join(src), dst
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
after do
|
28
|
+
FileUtils.rm_rf src_dir if src_dir.exist?
|
29
|
+
FileUtils.rm_rf dst_dir if dst_dir.exist?
|
30
|
+
end
|
31
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
2
|
+
|
3
|
+
require 'tmpdir'
|
4
|
+
require 'fileutils'
|
5
|
+
|
6
|
+
require 'asset_packer'
|
7
|
+
require_relative 'shared'
|
8
|
+
|
9
|
+
module FixtureHelpers
|
10
|
+
def with_fixture(html_file, assets = [])
|
11
|
+
subject(:processor) { described_class.new(source_uri.to_s, dst_dir.to_s, destination.to_s) }
|
12
|
+
|
13
|
+
let(:html_file) { html_file }
|
14
|
+
let(:assets) { assets }
|
15
|
+
let(:fixture_dir) { AssetPacker::ROOT.join('spec/fixtures') }
|
16
|
+
let(:src_dir) { Pathname(Dir.mktmpdir()) }
|
17
|
+
let(:dst_dir) { Pathname(Dir.mktmpdir()) }
|
18
|
+
let(:source_uri) { src_dir.join(html_file) }
|
19
|
+
let(:destination) { dst_dir.join(html_file) }
|
20
|
+
|
21
|
+
include_context 'copy fixtures to tmpdir'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
RSpec.configure do |rspec|
|
26
|
+
rspec.extend FixtureHelpers
|
27
|
+
|
28
|
+
rspec.backtrace_exclusion_patterns = [] if ENV['FULLSTACK']
|
29
|
+
end
|
metadata
ADDED
@@ -0,0 +1,169 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: asset_packer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Arne Brasseur
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-04-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: hexp
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.3.3
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.3.3
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: mime-types
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.2'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.2'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.2'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.2'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.14'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '2.14'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: mutant-rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ~>
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.5.10
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ~>
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.5.10
|
83
|
+
description: Create an offline version of a HTML file.
|
84
|
+
email:
|
85
|
+
- arne@arnebrasseur.net
|
86
|
+
executables:
|
87
|
+
- asset_packer
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files:
|
90
|
+
- README.md
|
91
|
+
- LICENSE
|
92
|
+
files:
|
93
|
+
- .gitignore
|
94
|
+
- .travis.yml
|
95
|
+
- Changelog.md
|
96
|
+
- Gemfile
|
97
|
+
- Gemfile.lock
|
98
|
+
- LICENSE
|
99
|
+
- README.md
|
100
|
+
- Rakefile
|
101
|
+
- asset_packer.gemspec
|
102
|
+
- bin/asset_packer
|
103
|
+
- lib/asset_packer.rb
|
104
|
+
- lib/asset_packer/cli.rb
|
105
|
+
- lib/asset_packer/processor.rb
|
106
|
+
- lib/asset_packer/processor/chain.rb
|
107
|
+
- lib/asset_packer/processor/local.rb
|
108
|
+
- lib/asset_packer/version.rb
|
109
|
+
- spec/asset_packer/cli_spec.rb
|
110
|
+
- spec/asset_packer/processor/chain_spec.rb
|
111
|
+
- spec/asset_packer/processor/local/image_spec.rb
|
112
|
+
- spec/asset_packer/processor/local/script_spec.rb
|
113
|
+
- spec/asset_packer/processor/local/stylesheet_spec.rb
|
114
|
+
- spec/asset_packer/processor/local_spec.rb
|
115
|
+
- spec/asset_packer/processor_spec.rb
|
116
|
+
- spec/fixtures/dummy.png
|
117
|
+
- spec/fixtures/has_all.html
|
118
|
+
- spec/fixtures/has_img.html
|
119
|
+
- spec/fixtures/has_script.html
|
120
|
+
- spec/fixtures/has_stylesheet.html
|
121
|
+
- spec/fixtures/index.html
|
122
|
+
- spec/fixtures/script.js
|
123
|
+
- spec/fixtures/section.css
|
124
|
+
- spec/fixtures/style.css
|
125
|
+
- spec/shared.rb
|
126
|
+
- spec/spec_helper.rb
|
127
|
+
homepage: https://github.com/plexus/asset_packer
|
128
|
+
licenses:
|
129
|
+
- MIT
|
130
|
+
metadata: {}
|
131
|
+
post_install_message:
|
132
|
+
rdoc_options: []
|
133
|
+
require_paths:
|
134
|
+
- lib
|
135
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
136
|
+
requirements:
|
137
|
+
- - '>='
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: '0'
|
140
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
141
|
+
requirements:
|
142
|
+
- - '>='
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '0'
|
145
|
+
requirements: []
|
146
|
+
rubyforge_project:
|
147
|
+
rubygems_version: 2.0.14
|
148
|
+
signing_key:
|
149
|
+
specification_version: 4
|
150
|
+
summary: Create an offline version of a HTML file.
|
151
|
+
test_files:
|
152
|
+
- spec/asset_packer/cli_spec.rb
|
153
|
+
- spec/asset_packer/processor/chain_spec.rb
|
154
|
+
- spec/asset_packer/processor/local/image_spec.rb
|
155
|
+
- spec/asset_packer/processor/local/script_spec.rb
|
156
|
+
- spec/asset_packer/processor/local/stylesheet_spec.rb
|
157
|
+
- spec/asset_packer/processor/local_spec.rb
|
158
|
+
- spec/asset_packer/processor_spec.rb
|
159
|
+
- spec/fixtures/dummy.png
|
160
|
+
- spec/fixtures/has_all.html
|
161
|
+
- spec/fixtures/has_img.html
|
162
|
+
- spec/fixtures/has_script.html
|
163
|
+
- spec/fixtures/has_stylesheet.html
|
164
|
+
- spec/fixtures/index.html
|
165
|
+
- spec/fixtures/script.js
|
166
|
+
- spec/fixtures/section.css
|
167
|
+
- spec/fixtures/style.css
|
168
|
+
- spec/shared.rb
|
169
|
+
- spec/spec_helper.rb
|