asset_packer 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,2 @@
1
+ pkg
2
+ scratch
data/.travis.yml ADDED
@@ -0,0 +1,13 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ - 2.1.0
6
+ - rbx
7
+ - ruby-head
8
+ matrix:
9
+ allow_failures:
10
+ - rvm: ruby-head
11
+ - rvm: 2.1.0
12
+ - rvm: rbx
13
+ script: bundle exec rake mutant
data/Changelog.md ADDED
@@ -0,0 +1,7 @@
1
+ # v0.1.0
2
+
3
+ First major release after extraction from Slippery
4
+
5
+ * command line and Hexp usage
6
+ * support for img, stylesheet, script
7
+ * taken out StandAlone to focus on Local
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ # A sample Gemfile
2
+ source "https://rubygems.org"
3
+
4
+ gemspec
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,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'asset_packer'
4
+ Kernel.exit AssetPacker::CLI.run(ARGV)
@@ -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,13 @@
1
+ module AssetPacker
2
+ class Processor
3
+ class Chain
4
+ def initialize(processors)
5
+ @processors = processors
6
+ end
7
+
8
+ def call(doc)
9
+ @processors.inject(doc) {|doc, proc| proc.(doc) }
10
+ end
11
+ end
12
+ end
13
+ 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
@@ -0,0 +1,3 @@
1
+ module AssetPacker
2
+ VERSION = '0.1.0'
3
+ end
@@ -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,11 @@
1
+ require 'spec_helper'
2
+
3
+ describe AssetPacker::Processor::Chain do
4
+ it 'should functionally compose' do
5
+ chain = described_class.new([
6
+ ->(x) { x + 1 },
7
+ ->(x) { x * 5 }
8
+ ])
9
+ expect(chain.call(2)).to eq 15
10
+ end
11
+ 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,9 @@
1
+ <html>
2
+ <head>
3
+ <script src="scripts/script.js" />
4
+ <link rel="stylesheet" href="assets/style.css" type="text/css" media="screen" />
5
+ </head>
6
+ <body>
7
+ <img src="images/dummy.png" />
8
+ </body>
9
+ </html>
@@ -0,0 +1,5 @@
1
+ <html>
2
+ <body>
3
+ <img src="images/dummy.png" />
4
+ </body>
5
+ </html>
@@ -0,0 +1,5 @@
1
+ <html>
2
+ <head>
3
+ <script src="scripts/script.js" />
4
+ </head>
5
+ </html>
@@ -0,0 +1,5 @@
1
+ <html>
2
+ <head>
3
+ <link rel="stylesheet" href="assets/style.css" type="text/css" media="screen" />
4
+ </head>
5
+ </html>
@@ -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
@@ -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