pannier 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/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
|
+
[![Build Status](https://travis-ci.org/joecorcoran/pannier.png?branch=master)](https://travis-ci.org/joecorcoran/pannier) [![Code Climate](https://codeclimate.com/github/joecorcoran/pannier.png)](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: []
|