pannier 0.1.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/LICENSE.txt +20 -0
- data/README.md +81 -0
- data/bin/pannier +6 -0
- data/lib/pannier.rb +44 -0
- data/lib/pannier/app.rb +89 -0
- data/lib/pannier/asset.rb +55 -0
- data/lib/pannier/cli.rb +85 -0
- data/lib/pannier/concatenator.rb +9 -0
- data/lib/pannier/dsl.rb +26 -0
- data/lib/pannier/environment.rb +16 -0
- data/lib/pannier/errors.rb +11 -0
- data/lib/pannier/file_handler.rb +20 -0
- data/lib/pannier/manifest_writer.rb +32 -0
- data/lib/pannier/mounted.rb +7 -0
- data/lib/pannier/mounted/app.rb +32 -0
- data/lib/pannier/mounted/asset.rb +13 -0
- data/lib/pannier/mounted/package.rb +40 -0
- data/lib/pannier/mounted/tags.rb +75 -0
- data/lib/pannier/package.rb +144 -0
- data/lib/pannier/report.rb +26 -0
- data/lib/pannier/version.rb +3 -0
- metadata +250 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b499e4d8880444acab12e7c82236e70102645d60
|
4
|
+
data.tar.gz: 9fc7791e4d6f6d2de4a7aad64bae4b2249932241
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 46f440d5c44c3dfb30489c42532840afd4b397e0a6bdad58f35f8614a3dd3e39204b34b5b2007ce899465fe268b9e20e08db404116aeac197f89a44787f8a585
|
7
|
+
data.tar.gz: 9e208521ae8caa253f9a52899a7c4b0ead6d9618b7e166848a42e8d1bb87b965af0ad26483dbaf38fa583859b9cc2cb0a10c436e6d613d072d3f582d1f4b8ef9
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2013 Joe Corcoran
|
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,81 @@
|
|
1
|
+
# Pannier
|
2
|
+
|
3
|
+
[](https://travis-ci.org/joecorcoran/pannier) [](https://codeclimate.com/github/joecorcoran/pannier)
|
4
|
+
|
5
|
+
Pannier is a general-purpose Ruby asset processing tool. Its goal is to
|
6
|
+
work the same way in any Rack environment. No Rails glue, no mandatory
|
7
|
+
JavaScript or CSS libraries, preprocessors or gems.
|
8
|
+
|
9
|
+
## Why?
|
10
|
+
|
11
|
+
The Rails asset pipeline essentially consists of [Sprockets][sprockets]
|
12
|
+
and a bunch of inscrutable Rails coupling. You generate a new Rails app
|
13
|
+
and everything is setup for you. We call this "convention over configuration".
|
14
|
+
It's a fine idea, but as soon as you need to ditch one of more of those
|
15
|
+
conventions you'll be frustrated.
|
16
|
+
|
17
|
+
I have found the
|
18
|
+
[principle of least astonishment][pola] to be far more valuable than
|
19
|
+
an *automagic* beginners' experience in the long run. This is especially
|
20
|
+
true where asset processing in Rails is concerned. I want explicit control
|
21
|
+
over my assets and I don't mind spending a small amount of time on
|
22
|
+
configuration.
|
23
|
+
|
24
|
+
## What does it do?
|
25
|
+
|
26
|
+
The configuration DSL was inspired by — but is ultimately quite
|
27
|
+
different from — [rake-pipeline][rp]. The config describes a Rack
|
28
|
+
application that handles asset processing (modification of file contents
|
29
|
+
and file names, file concatenation). No decisions about
|
30
|
+
uglifiers/optimisers/preprocessors have been made; that part is up to you.
|
31
|
+
The interface to plug any of these libraries into Pannier is very simple
|
32
|
+
and inspired by Rack. The lack of a plugin ecosystem that binds you to any
|
33
|
+
particular preprocessor is considered a feature.
|
34
|
+
|
35
|
+
In some cases the above is all you'll need, but the application
|
36
|
+
can also be mounted (mapped to a path) within another Rack application, so
|
37
|
+
it can serve your assets too. In that case, there are helper methods for
|
38
|
+
including your assets in the view layer. Adding your own view helpers is
|
39
|
+
easy too.
|
40
|
+
|
41
|
+
## Getting started
|
42
|
+
|
43
|
+
Create a config file in the root of your project named `.assets.rb`. The
|
44
|
+
example below will simply take all of your stylesheets from one directory
|
45
|
+
and concatenate them in another.
|
46
|
+
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
input 'assets' # Where your unprocessed assets live.
|
50
|
+
output 'public' # Where your processed assets will live.
|
51
|
+
|
52
|
+
package :styles do
|
53
|
+
input 'stylesheets' # Relative to `assets`.
|
54
|
+
assets '**/*.css' # Glob, relative to `assets/stylesheets`.
|
55
|
+
|
56
|
+
modify do |content, basename| # Do something to the file content here.
|
57
|
+
[quuxify(content), basename] # Return an array of content and basename.
|
58
|
+
end # This block is called once for each file.
|
59
|
+
|
60
|
+
concat 'main.min.css' # Concat into `public`.
|
61
|
+
end
|
62
|
+
```
|
63
|
+
|
64
|
+
To understand further, you can [browse the current features on relish][relish].
|
65
|
+
|
66
|
+
## Contributing
|
67
|
+
|
68
|
+
Yes, please contribute! Fork this repo, then send a pull request with a
|
69
|
+
note that clearly explains your changes. If you're unsure or you're
|
70
|
+
asking for a big change, just open an issue and we can chat about it first.
|
71
|
+
|
72
|
+
## License
|
73
|
+
|
74
|
+
[MIT][license].
|
75
|
+
|
76
|
+
[sprockets]: https://github.com/sstephenson/sprockets
|
77
|
+
[pola]: http://en.wikipedia.org/wiki/Principle_of_least_astonishment
|
78
|
+
[rp]: https://github.com/livingsocial/rake-pipeline
|
79
|
+
[relish]: https://www.relishapp.com/joecorcoran/pannier/docs
|
80
|
+
[todo]: https://github.com/joecorcoran/pannier/wiki/Todo
|
81
|
+
[license]: https://github.com/joecorcoran/pannier/blob/master/LICENSE.txt
|
data/bin/pannier
ADDED
data/lib/pannier.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'multi_json'
|
2
|
+
|
3
|
+
require 'pannier/app'
|
4
|
+
require 'pannier/version'
|
5
|
+
|
6
|
+
module Pannier
|
7
|
+
def self.build(env_name = 'development', &block)
|
8
|
+
App.build(env_name, &block)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.load(path, env_name)
|
12
|
+
config = File.read(path)
|
13
|
+
block = eval("proc { #{config} }", TOPLEVEL_BINDING, path, 0)
|
14
|
+
App.build(env_name, &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.prime(path, env_name = 'development')
|
18
|
+
app = load(path, env_name)
|
19
|
+
if manifest_exists?(app, env_name)
|
20
|
+
manifest = load_manifest(app, env_name)
|
21
|
+
app.prime!(manifest)
|
22
|
+
end
|
23
|
+
app
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.rackup!(ru, path = './.assets.rb')
|
27
|
+
app = prime(path, ENV['RACK_ENV'])
|
28
|
+
ru.map(app.root) { run(app) }
|
29
|
+
app
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def self.load_manifest(app, env_name)
|
35
|
+
path = File.join(app.input_path, ".assets.#{env_name}.json")
|
36
|
+
json = File.read(path)
|
37
|
+
MultiJson.load(json, :symbolize_keys => true)
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.manifest_exists?(app, env_name)
|
41
|
+
path = File.join(app.input_path, ".assets.#{env_name}.json")
|
42
|
+
File.exists?(path)
|
43
|
+
end
|
44
|
+
end
|
data/lib/pannier/app.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
require 'pannier/dsl'
|
4
|
+
require 'pannier/environment'
|
5
|
+
require 'pannier/manifest_writer'
|
6
|
+
require 'pannier/package'
|
7
|
+
|
8
|
+
module Pannier
|
9
|
+
class App
|
10
|
+
extend DSL
|
11
|
+
|
12
|
+
attr_reader :env, :root, :input_path, :output_path,
|
13
|
+
:behaviors, :packages
|
14
|
+
|
15
|
+
def initialize(env_name = 'development')
|
16
|
+
@env = Environment.new(env_name)
|
17
|
+
@behaviors, @packages, @root = {}, [], '/'
|
18
|
+
end
|
19
|
+
|
20
|
+
def set_root(path)
|
21
|
+
@root = path
|
22
|
+
end
|
23
|
+
|
24
|
+
def set_input(path)
|
25
|
+
@input_path = File.expand_path(path)
|
26
|
+
end
|
27
|
+
|
28
|
+
def set_output(path)
|
29
|
+
@output_path = File.expand_path(path)
|
30
|
+
end
|
31
|
+
|
32
|
+
def add_package(package)
|
33
|
+
@packages << package
|
34
|
+
end
|
35
|
+
|
36
|
+
def [](package_name)
|
37
|
+
@packages.find { |pkg| pkg.name == package_name }
|
38
|
+
end
|
39
|
+
|
40
|
+
def manifest_writer
|
41
|
+
@manifest_writer ||= ManifestWriter.new(self, @env)
|
42
|
+
end
|
43
|
+
|
44
|
+
def process!
|
45
|
+
@packages.each(&:process!)
|
46
|
+
manifest_writer.write!(@input_path)
|
47
|
+
end
|
48
|
+
|
49
|
+
def process_owners!(*paths)
|
50
|
+
pkgs = @packages.select { |pkg| pkg.owns_any?(*paths) }
|
51
|
+
pkgs.each(&:process!)
|
52
|
+
end
|
53
|
+
|
54
|
+
dsl do
|
55
|
+
|
56
|
+
def _
|
57
|
+
@locals ||= OpenStruct.new(:env => env.name)
|
58
|
+
end
|
59
|
+
|
60
|
+
def root(path)
|
61
|
+
set_root(path)
|
62
|
+
end
|
63
|
+
|
64
|
+
def input(path)
|
65
|
+
set_input(path)
|
66
|
+
end
|
67
|
+
|
68
|
+
def output(path)
|
69
|
+
set_output(path)
|
70
|
+
end
|
71
|
+
|
72
|
+
def behavior(name, &block)
|
73
|
+
add_behavior(name, &block)
|
74
|
+
end
|
75
|
+
|
76
|
+
def package(name, &block)
|
77
|
+
add_package(Package.build(name, __getobj__, &block))
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def add_behavior(name, &block)
|
83
|
+
self.behaviors[name] = block
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'pathname'
|
3
|
+
|
4
|
+
module Pannier
|
5
|
+
class Asset
|
6
|
+
include Comparable
|
7
|
+
|
8
|
+
attr_accessor :basename, :dirname, :content
|
9
|
+
|
10
|
+
def initialize(basename, dirname, package)
|
11
|
+
@basename, @dirname, @package = basename, dirname, package
|
12
|
+
@content = original_content
|
13
|
+
end
|
14
|
+
|
15
|
+
def path
|
16
|
+
File.join(@dirname, @basename)
|
17
|
+
end
|
18
|
+
|
19
|
+
def original_content
|
20
|
+
return unless File.exists?(path)
|
21
|
+
@original_content ||= File.read(path)
|
22
|
+
end
|
23
|
+
|
24
|
+
def eql?(other)
|
25
|
+
path == other.path
|
26
|
+
end
|
27
|
+
|
28
|
+
def hash
|
29
|
+
path.hash
|
30
|
+
end
|
31
|
+
|
32
|
+
def <=>(other)
|
33
|
+
path <=> other.path
|
34
|
+
end
|
35
|
+
|
36
|
+
def copy_to(to_dirname)
|
37
|
+
copy = self.dup
|
38
|
+
copy.content = content.dup if content
|
39
|
+
copy.dirname = to_dirname
|
40
|
+
copy
|
41
|
+
end
|
42
|
+
|
43
|
+
def modify!(modifier)
|
44
|
+
modified = modifier.call(content, basename)
|
45
|
+
self.content, self.basename = modified
|
46
|
+
end
|
47
|
+
|
48
|
+
def write_file!
|
49
|
+
FileUtils.mkdir_p(@dirname)
|
50
|
+
File.open(path, 'w+') do |file|
|
51
|
+
file << content
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/lib/pannier/cli.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'slop'
|
2
|
+
|
3
|
+
require 'pannier'
|
4
|
+
|
5
|
+
module Pannier
|
6
|
+
class CLI
|
7
|
+
def initialize(args, stdin = $stdin, stdout = $stdout, stderr = $stderr)
|
8
|
+
@args, @stdin, @stdout, @stderr = args, stdin, stdout, stderr
|
9
|
+
end
|
10
|
+
|
11
|
+
def run!
|
12
|
+
command, opts = (@args.shift || 'usage').to_sym, @args
|
13
|
+
public_send(command, *opts)
|
14
|
+
end
|
15
|
+
|
16
|
+
def process(*opts)
|
17
|
+
opts = Slop.parse(opts, :help => true, :ignore_case => true) do
|
18
|
+
banner 'Usage: pannier process [options]'
|
19
|
+
on :c, :config, 'Config file', :argument => :optional, :default => '.assets.rb'
|
20
|
+
on :e, :env, 'Host environment', :argument => :optional, :default => 'development'
|
21
|
+
on :a, :assets, 'Asset paths', :argument => :optional, :as => Array
|
22
|
+
end
|
23
|
+
|
24
|
+
config_path = File.expand_path(opts[:config])
|
25
|
+
err(no_config_msg(config_path)) && abort unless File.exists?(config_path)
|
26
|
+
|
27
|
+
app = Pannier.load(config_path, opts[:env])
|
28
|
+
|
29
|
+
if opts.assets?
|
30
|
+
paths = opts[:assets].map { |path| File.expand_path(path) }
|
31
|
+
app.process_owners!(*paths)
|
32
|
+
else
|
33
|
+
app.process!
|
34
|
+
end
|
35
|
+
exit
|
36
|
+
end
|
37
|
+
|
38
|
+
def usage
|
39
|
+
out(usage_msg)
|
40
|
+
exit
|
41
|
+
end
|
42
|
+
|
43
|
+
def method_missing(command, *args)
|
44
|
+
err(<<-txt)
|
45
|
+
You ran `pannier #{command}#{(' ' + args.join(' ')) unless args.empty?}`.
|
46
|
+
Pannier has no command named "#{command}".
|
47
|
+
txt
|
48
|
+
exit(127)
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def no_config_msg(path)
|
54
|
+
<<-txt
|
55
|
+
Pannier config file not found at #{path}.
|
56
|
+
txt
|
57
|
+
end
|
58
|
+
|
59
|
+
def usage_msg
|
60
|
+
<<-txt
|
61
|
+
Available commands (run any command with --help for details):
|
62
|
+
pannier process Process assets
|
63
|
+
pannier usage Show this list of commands
|
64
|
+
txt
|
65
|
+
end
|
66
|
+
|
67
|
+
def out(*msgs)
|
68
|
+
msg = msgs.map { |m| format_output(m) }.join
|
69
|
+
@stdout.puts(msg)
|
70
|
+
end
|
71
|
+
|
72
|
+
def err(*msgs)
|
73
|
+
msg = msgs.map { |m| format_output(m) }.join
|
74
|
+
msg += format_output(usage_msg)
|
75
|
+
@stderr.puts(msg)
|
76
|
+
end
|
77
|
+
|
78
|
+
def format_output(txt)
|
79
|
+
spaces = '^[ \t]'
|
80
|
+
indent = txt.scan(/#{spaces}*(?=\S)/).min
|
81
|
+
indent_size = indent ? indent.size : 0
|
82
|
+
txt.gsub(/#{spaces}{#{indent_size}}/, '')
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/lib/pannier/dsl.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
require 'pannier/errors'
|
3
|
+
|
4
|
+
module Pannier
|
5
|
+
module DSL
|
6
|
+
|
7
|
+
def build(*args, &block)
|
8
|
+
base = self.new(*args)
|
9
|
+
delegator_klass = self.const_get('DSLDelegator')
|
10
|
+
delegator = delegator_klass.new(base)
|
11
|
+
delegator.instance_eval(&block)
|
12
|
+
base
|
13
|
+
end
|
14
|
+
|
15
|
+
def dsl(&block)
|
16
|
+
begin
|
17
|
+
delegator_klass = self.const_get('DSLDelegator')
|
18
|
+
delegator_klass.class_eval(&block)
|
19
|
+
rescue NameError
|
20
|
+
delegator_klass = Class.new(SimpleDelegator, &block)
|
21
|
+
self.const_set('DSLDelegator', delegator_klass)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Pannier
|
2
|
+
class FileHandler < Rack::File
|
3
|
+
|
4
|
+
def initialize(allowed, *args)
|
5
|
+
@allowed = allowed
|
6
|
+
super(*args)
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(env)
|
10
|
+
return fail(404, 'File not a member of this package') unless handle?(env)
|
11
|
+
super(env)
|
12
|
+
end
|
13
|
+
|
14
|
+
def handle?(env)
|
15
|
+
path = Rack::Utils.unescape(env['PATH_INFO'])
|
16
|
+
@allowed.include?(File.join(@root, path))
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'multi_json'
|
3
|
+
|
4
|
+
require 'pannier/report'
|
5
|
+
|
6
|
+
module Pannier
|
7
|
+
class ManifestWriter
|
8
|
+
|
9
|
+
def initialize(app, env)
|
10
|
+
@app, @env = app, env
|
11
|
+
@report = Report.new(@app)
|
12
|
+
end
|
13
|
+
|
14
|
+
def basename
|
15
|
+
".assets.#{@env.name}.json"
|
16
|
+
end
|
17
|
+
|
18
|
+
def content
|
19
|
+
@report.build!
|
20
|
+
MultiJson.dump(@report.tree)
|
21
|
+
end
|
22
|
+
|
23
|
+
def write!(dir_path)
|
24
|
+
@report.build!
|
25
|
+
FileUtils.mkdir_p(dir_path)
|
26
|
+
File.open(File.join(dir_path, basename), 'w+') do |f|
|
27
|
+
f << content
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'pannier/app'
|
2
|
+
|
3
|
+
module Pannier
|
4
|
+
class App
|
5
|
+
|
6
|
+
def prime!(manifest)
|
7
|
+
manifest.each do |name, paths|
|
8
|
+
if (pkg = self[name])
|
9
|
+
assets = pkg.build_assets_from_paths(paths)
|
10
|
+
pkg.add_output_assets(assets)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def handler_map
|
16
|
+
@packages.reduce({}) do |hash, pkg|
|
17
|
+
hash[pkg.handler_path] ||= Rack::Cascade.new([])
|
18
|
+
hash[pkg.handler_path].add(pkg.handler)
|
19
|
+
hash
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def handler
|
24
|
+
Rack::URLMap.new(handler_map)
|
25
|
+
end
|
26
|
+
|
27
|
+
def call(env)
|
28
|
+
handler.call(env)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'pannier/asset'
|
2
|
+
|
3
|
+
module Pannier
|
4
|
+
class Asset
|
5
|
+
|
6
|
+
def serve_from(app)
|
7
|
+
asset_path, app_output_path = Pathname.new(path), Pathname.new(app.output_path)
|
8
|
+
relative_path = asset_path.relative_path_from(app_output_path)
|
9
|
+
File.join(app.root, relative_path.to_s)
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'pannier/file_handler'
|
2
|
+
require 'pannier/package'
|
3
|
+
|
4
|
+
module Pannier
|
5
|
+
class Package
|
6
|
+
|
7
|
+
def add_middleware(middleware, *args, &block)
|
8
|
+
@middlewares << proc { |app| middleware.new(app, *args, &block) }
|
9
|
+
end
|
10
|
+
|
11
|
+
def handler
|
12
|
+
handler_with_middlewares(output_assets.map(&:path), full_output_path)
|
13
|
+
end
|
14
|
+
|
15
|
+
def handler_path
|
16
|
+
build_handler_path(output_path)
|
17
|
+
end
|
18
|
+
|
19
|
+
def build_handler_path(handler_path)
|
20
|
+
hp = handler_path || '/'
|
21
|
+
hp.insert(0, '/') unless hp[0] == '/'
|
22
|
+
hp
|
23
|
+
end
|
24
|
+
|
25
|
+
def handler_with_middlewares(paths, full_path)
|
26
|
+
handler = FileHandler.new(paths, full_path)
|
27
|
+
return handler if @middlewares.empty?
|
28
|
+
@middlewares.reverse.reduce(handler) { |app, proc| proc.call(app) }
|
29
|
+
end
|
30
|
+
|
31
|
+
dsl do
|
32
|
+
|
33
|
+
def use(*args, &block)
|
34
|
+
add_middleware(*args, &block)
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
module Pannier
|
4
|
+
class Tags
|
5
|
+
|
6
|
+
attr_reader :app
|
7
|
+
|
8
|
+
def initialize(app)
|
9
|
+
@app = app
|
10
|
+
end
|
11
|
+
|
12
|
+
def write(package_name, attrs = {})
|
13
|
+
template_klass = attrs.delete(:as)
|
14
|
+
template = template_klass.new
|
15
|
+
to_write = @app[package_name].output_assets.map do |asset|
|
16
|
+
template.call(asset.serve_from(@app), attrs)
|
17
|
+
end
|
18
|
+
to_write.join("\n")
|
19
|
+
end
|
20
|
+
|
21
|
+
module Helpers
|
22
|
+
def attrs_to_s(hash)
|
23
|
+
pairs = hash.reduce([]) do |arr, pair|
|
24
|
+
key, value = escape(pair[0].to_s), escape(pair[1].to_s)
|
25
|
+
arr << "#{key}=\"#{value}\""
|
26
|
+
end
|
27
|
+
pairs.sort.join(' ')
|
28
|
+
end
|
29
|
+
|
30
|
+
def escape(string)
|
31
|
+
ERB::Util.html_escape(string)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class JavaScript
|
36
|
+
include Helpers
|
37
|
+
|
38
|
+
def call(path, attrs)
|
39
|
+
attrs = attrs_to_s({
|
40
|
+
:type => 'text/javascript',
|
41
|
+
:src => path
|
42
|
+
}.merge(attrs))
|
43
|
+
|
44
|
+
template.result(binding)
|
45
|
+
end
|
46
|
+
|
47
|
+
def template
|
48
|
+
ERB.new(<<-erb.strip)
|
49
|
+
<script <%= attrs %>></script>
|
50
|
+
erb
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class CSS
|
55
|
+
include Helpers
|
56
|
+
|
57
|
+
def call(path, attrs)
|
58
|
+
attrs = attrs_to_s({
|
59
|
+
:rel => 'stylesheet',
|
60
|
+
:type => 'text/css',
|
61
|
+
:href => path
|
62
|
+
}.merge(attrs))
|
63
|
+
|
64
|
+
template.result(binding)
|
65
|
+
end
|
66
|
+
|
67
|
+
def template
|
68
|
+
ERB.new(<<-erb.strip)
|
69
|
+
<link <%= attrs %> />
|
70
|
+
erb
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
require 'ostruct'
|
3
|
+
require 'pathname'
|
4
|
+
require 'set'
|
5
|
+
|
6
|
+
require 'pannier/asset'
|
7
|
+
require 'pannier/concatenator'
|
8
|
+
require 'pannier/dsl'
|
9
|
+
require 'pannier/errors'
|
10
|
+
|
11
|
+
module Pannier
|
12
|
+
class Package
|
13
|
+
extend DSL
|
14
|
+
|
15
|
+
attr_reader :name, :app, :input_assets, :output_assets, :input_path,
|
16
|
+
:output_path, :middlewares, :processors
|
17
|
+
|
18
|
+
def initialize(name, app)
|
19
|
+
@name, @app = name, app
|
20
|
+
@input_assets, @output_assets = SortedSet.new, SortedSet.new
|
21
|
+
@middlewares, @processors = [], []
|
22
|
+
end
|
23
|
+
|
24
|
+
def set_input(path)
|
25
|
+
@input_path = path
|
26
|
+
end
|
27
|
+
|
28
|
+
def set_output(path)
|
29
|
+
@output_path = path
|
30
|
+
end
|
31
|
+
|
32
|
+
def full_input_path
|
33
|
+
File.expand_path(File.join(*[@app.input_path, @input_path].compact))
|
34
|
+
end
|
35
|
+
|
36
|
+
def full_output_path
|
37
|
+
File.expand_path(File.join(*[@app.output_path, @output_path].compact))
|
38
|
+
end
|
39
|
+
|
40
|
+
def build_assets_from_paths(paths)
|
41
|
+
paths.map do |path|
|
42
|
+
pathname = Pathname.new(path)
|
43
|
+
Asset.new(pathname.basename.to_s, pathname.dirname.to_s, self)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def add_input_assets(assets)
|
48
|
+
@input_assets.merge(assets)
|
49
|
+
end
|
50
|
+
|
51
|
+
def add_output_assets(assets)
|
52
|
+
@output_assets.merge(assets)
|
53
|
+
end
|
54
|
+
|
55
|
+
def add_modifiers(modifiers)
|
56
|
+
@processors += modifiers.map { |m| [:modify!, m] }
|
57
|
+
end
|
58
|
+
|
59
|
+
def add_concatenator(concat_name, concatenator = Concatenator.new)
|
60
|
+
@processors << [:concat!, concat_name, concatenator]
|
61
|
+
end
|
62
|
+
|
63
|
+
def owns_any?(*paths)
|
64
|
+
@input_assets.any? { |a| paths.include?(a.path) }
|
65
|
+
end
|
66
|
+
|
67
|
+
def process!
|
68
|
+
copy!
|
69
|
+
!@processors.empty? && @processors.each do |instructions|
|
70
|
+
send(*instructions)
|
71
|
+
end
|
72
|
+
write_files!
|
73
|
+
end
|
74
|
+
|
75
|
+
def modify!(modifier)
|
76
|
+
@output_assets.each do |asset|
|
77
|
+
asset.modify!(modifier)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def concat!(concat_name, concatenator)
|
82
|
+
asset = Asset.new(concat_name, full_output_path, self)
|
83
|
+
asset.content = concatenator.call(@output_assets.map(&:content))
|
84
|
+
@output_assets.replace([asset])
|
85
|
+
end
|
86
|
+
|
87
|
+
def copy!
|
88
|
+
assets = @input_assets.map do |asset|
|
89
|
+
asset.copy_to(full_output_path)
|
90
|
+
end
|
91
|
+
@output_assets.replace(assets)
|
92
|
+
end
|
93
|
+
|
94
|
+
def write_files!
|
95
|
+
@output_assets.each(&:write_file!)
|
96
|
+
end
|
97
|
+
|
98
|
+
dsl do
|
99
|
+
|
100
|
+
def _
|
101
|
+
@locals ||= OpenStruct.new(:env => app.env.name)
|
102
|
+
end
|
103
|
+
|
104
|
+
def input(path)
|
105
|
+
set_input(path)
|
106
|
+
end
|
107
|
+
|
108
|
+
def output(path)
|
109
|
+
set_output(path)
|
110
|
+
end
|
111
|
+
|
112
|
+
def behave(*names)
|
113
|
+
names.each do |name|
|
114
|
+
behavior = self.app.behaviors[name]
|
115
|
+
raise MissingBehavior.new(name) if behavior.nil?
|
116
|
+
self.instance_eval(&behavior)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def assets(*patterns)
|
121
|
+
patterns.each do |pattern|
|
122
|
+
paths = Dir[File.join(full_input_path, pattern)]
|
123
|
+
assets = build_assets_from_paths(paths)
|
124
|
+
add_input_assets(assets)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def modify(*modifiers, &block)
|
129
|
+
modifiers << block if block_given?
|
130
|
+
add_modifiers(modifiers)
|
131
|
+
end
|
132
|
+
|
133
|
+
def concat(*args)
|
134
|
+
add_concatenator(*args)
|
135
|
+
end
|
136
|
+
|
137
|
+
def env(expression, &block)
|
138
|
+
self.instance_eval(&block) if self.app.env.is?(expression)
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Pannier
|
2
|
+
class Report
|
3
|
+
|
4
|
+
attr_reader :tree
|
5
|
+
|
6
|
+
def initialize(app, base_url = '')
|
7
|
+
@app, @base_url, @tree = app, base_url, {}
|
8
|
+
build!
|
9
|
+
end
|
10
|
+
|
11
|
+
def build!
|
12
|
+
@app.packages.each do |package|
|
13
|
+
@tree[package.name] ||= []
|
14
|
+
next if package.output_assets.empty?
|
15
|
+
@tree[package.name] = package.output_assets.map(&:path)
|
16
|
+
end
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
def lookup(package_name)
|
21
|
+
return @tree if package_name.nil?
|
22
|
+
@tree[package_name.to_sym]
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,250 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pannier
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Joe Corcoran
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-03-02 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rack
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.5'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 1.5.2
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.5'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 1.5.2
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: multi_json
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '1.7'
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 1.7.9
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - "~>"
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '1.7'
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 1.7.9
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: slop
|
55
|
+
requirement: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - "~>"
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '3.4'
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: 3.4.6
|
63
|
+
type: :runtime
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '3.4'
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: 3.4.6
|
73
|
+
- !ruby/object:Gem::Dependency
|
74
|
+
name: rake
|
75
|
+
requirement: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - "~>"
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '10.1'
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 10.1.0
|
83
|
+
type: :development
|
84
|
+
prerelease: false
|
85
|
+
version_requirements: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '10.1'
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: 10.1.0
|
93
|
+
- !ruby/object:Gem::Dependency
|
94
|
+
name: rspec
|
95
|
+
requirement: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - "~>"
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '2.14'
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: 2.14.1
|
103
|
+
type: :development
|
104
|
+
prerelease: false
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - "~>"
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '2.14'
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: 2.14.1
|
113
|
+
- !ruby/object:Gem::Dependency
|
114
|
+
name: mocha
|
115
|
+
requirement: !ruby/object:Gem::Requirement
|
116
|
+
requirements:
|
117
|
+
- - "~>"
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: '0.14'
|
120
|
+
- - ">="
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: 0.14.0
|
123
|
+
type: :development
|
124
|
+
prerelease: false
|
125
|
+
version_requirements: !ruby/object:Gem::Requirement
|
126
|
+
requirements:
|
127
|
+
- - "~>"
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: '0.14'
|
130
|
+
- - ">="
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: 0.14.0
|
133
|
+
- !ruby/object:Gem::Dependency
|
134
|
+
name: cucumber
|
135
|
+
requirement: !ruby/object:Gem::Requirement
|
136
|
+
requirements:
|
137
|
+
- - "~>"
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: '1.3'
|
140
|
+
- - ">="
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
version: 1.3.6
|
143
|
+
type: :development
|
144
|
+
prerelease: false
|
145
|
+
version_requirements: !ruby/object:Gem::Requirement
|
146
|
+
requirements:
|
147
|
+
- - "~>"
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '1.3'
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: 1.3.6
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: aruba
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - "~>"
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0.5'
|
160
|
+
- - ">="
|
161
|
+
- !ruby/object:Gem::Version
|
162
|
+
version: 0.5.3
|
163
|
+
type: :development
|
164
|
+
prerelease: false
|
165
|
+
version_requirements: !ruby/object:Gem::Requirement
|
166
|
+
requirements:
|
167
|
+
- - "~>"
|
168
|
+
- !ruby/object:Gem::Version
|
169
|
+
version: '0.5'
|
170
|
+
- - ">="
|
171
|
+
- !ruby/object:Gem::Version
|
172
|
+
version: 0.5.3
|
173
|
+
- !ruby/object:Gem::Dependency
|
174
|
+
name: rack-test
|
175
|
+
requirement: !ruby/object:Gem::Requirement
|
176
|
+
requirements:
|
177
|
+
- - "~>"
|
178
|
+
- !ruby/object:Gem::Version
|
179
|
+
version: '0.6'
|
180
|
+
- - ">="
|
181
|
+
- !ruby/object:Gem::Version
|
182
|
+
version: 0.6.2
|
183
|
+
type: :development
|
184
|
+
prerelease: false
|
185
|
+
version_requirements: !ruby/object:Gem::Requirement
|
186
|
+
requirements:
|
187
|
+
- - "~>"
|
188
|
+
- !ruby/object:Gem::Version
|
189
|
+
version: '0.6'
|
190
|
+
- - ">="
|
191
|
+
- !ruby/object:Gem::Version
|
192
|
+
version: 0.6.2
|
193
|
+
description: |
|
194
|
+
Pannier is a Ruby tool for the processing of web assets like CSS
|
195
|
+
and JavaScript files, both programatically and from the command line. It can
|
196
|
+
be used as a standalone asset organizer or mounted within any Rack-compatible
|
197
|
+
application.
|
198
|
+
email:
|
199
|
+
- joecorcoran@gmail.com
|
200
|
+
executables:
|
201
|
+
- pannier
|
202
|
+
extensions: []
|
203
|
+
extra_rdoc_files: []
|
204
|
+
files:
|
205
|
+
- LICENSE.txt
|
206
|
+
- README.md
|
207
|
+
- bin/pannier
|
208
|
+
- lib/pannier.rb
|
209
|
+
- lib/pannier/app.rb
|
210
|
+
- lib/pannier/asset.rb
|
211
|
+
- lib/pannier/cli.rb
|
212
|
+
- lib/pannier/concatenator.rb
|
213
|
+
- lib/pannier/dsl.rb
|
214
|
+
- lib/pannier/environment.rb
|
215
|
+
- lib/pannier/errors.rb
|
216
|
+
- lib/pannier/file_handler.rb
|
217
|
+
- lib/pannier/manifest_writer.rb
|
218
|
+
- lib/pannier/mounted.rb
|
219
|
+
- lib/pannier/mounted/app.rb
|
220
|
+
- lib/pannier/mounted/asset.rb
|
221
|
+
- lib/pannier/mounted/package.rb
|
222
|
+
- lib/pannier/mounted/tags.rb
|
223
|
+
- lib/pannier/package.rb
|
224
|
+
- lib/pannier/report.rb
|
225
|
+
- lib/pannier/version.rb
|
226
|
+
homepage: http://github.com/joecorcoran/pannier
|
227
|
+
licenses:
|
228
|
+
- MIT
|
229
|
+
metadata: {}
|
230
|
+
post_install_message:
|
231
|
+
rdoc_options: []
|
232
|
+
require_paths:
|
233
|
+
- lib
|
234
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
235
|
+
requirements:
|
236
|
+
- - ">="
|
237
|
+
- !ruby/object:Gem::Version
|
238
|
+
version: '0'
|
239
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
240
|
+
requirements:
|
241
|
+
- - ">="
|
242
|
+
- !ruby/object:Gem::Version
|
243
|
+
version: '0'
|
244
|
+
requirements: []
|
245
|
+
rubyforge_project:
|
246
|
+
rubygems_version: 2.2.1
|
247
|
+
signing_key:
|
248
|
+
specification_version: 4
|
249
|
+
summary: A simple, portable asset processing tool for Ruby web apps
|
250
|
+
test_files: []
|