spar 0.2.7
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.
- data/README.md +1 -0
- data/bin/spar +3 -0
- data/lib/spar.rb +16 -0
- data/lib/spar/assets.rb +78 -0
- data/lib/spar/base.rb +54 -0
- data/lib/spar/cli.rb +32 -0
- data/lib/spar/css_compressor.rb +17 -0
- data/lib/spar/deployer.rb +198 -0
- data/lib/spar/generators/application.rb +42 -0
- data/lib/spar/generators/templates/Gemfile +3 -0
- data/lib/spar/generators/templates/README +1 -0
- data/lib/spar/generators/templates/Rakefile +8 -0
- data/lib/spar/generators/templates/app/assets/javascripts/javascript.js.coffee +10 -0
- data/lib/spar/generators/templates/app/assets/stylesheets/application.css.sass +11 -0
- data/lib/spar/generators/templates/app/views/index.haml +7 -0
- data/lib/spar/generators/templates/config.ru +9 -0
- data/lib/spar/generators/templates/config/application.rb +12 -0
- data/lib/spar/generators/templates/script/server +1 -0
- data/lib/spar/helpers.rb +176 -0
- data/lib/spar/static_compiler.rb +91 -0
- data/lib/spar/tasks.rb +21 -0
- data/lib/spar/version.rb +3 -0
- metadata +291 -0
data/README.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Docs Coming Soon!
|
data/bin/spar
ADDED
data/lib/spar.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
module Spar
|
2
|
+
autoload :Version, 'spar/version'
|
3
|
+
autoload :Base, 'spar/base'
|
4
|
+
autoload :Assets, 'spar/assets'
|
5
|
+
autoload :StaticCompiler, 'spar/static_compiler'
|
6
|
+
autoload :Helpers, 'spar/helpers'
|
7
|
+
autoload :CssCompressor, 'spar/css_compressor'
|
8
|
+
autoload :Deployer, 'spar/deployer'
|
9
|
+
|
10
|
+
class << self
|
11
|
+
|
12
|
+
attr_accessor :root, :assets
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
data/lib/spar/assets.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'sprockets'
|
2
|
+
require 'sprockets-sass'
|
3
|
+
require 'coffee_script'
|
4
|
+
require 'uglifier'
|
5
|
+
|
6
|
+
module Spar
|
7
|
+
module Assets
|
8
|
+
|
9
|
+
DEFAULT_DIRS = ['stylesheets', 'javascripts', 'images', 'fonts']
|
10
|
+
DEFAULT_PRECOMPILE = [ /\w+\.(?!js|css).+/, /application.(css|js)$/ ]
|
11
|
+
DEFAULT_PREFIX = 'assets'
|
12
|
+
DEFAULT_DIGEST_FREE_ASSETS = []
|
13
|
+
|
14
|
+
def self.registered(app)
|
15
|
+
app.set :asset_env, Sprockets::Environment.new(app.root)
|
16
|
+
app.set :asset_precompile, app.respond_to?(:asset_precompile) ? app.asset_precompile : DEFAULT_PRECOMPILE
|
17
|
+
app.set :asset_dirs, app.respond_to?(:asset_dirs) ? app.asset_dirs : DEFAULT_DIRS
|
18
|
+
app.set :asset_prefix, app.respond_to?(:asset_prefix) ? app.asset_prefix : DEFAULT_PREFIX
|
19
|
+
app.set :digest_free_assets, app.respond_to?(:digest_free_assets) ? app.digest_free_assets : DEFAULT_DIGEST_FREE_ASSETS
|
20
|
+
app.set :asset_path, File.join(app.root, app.asset_prefix)
|
21
|
+
app.set :manifest_path, File.join(app.public_path, app.asset_prefix, "manifest.yml")
|
22
|
+
|
23
|
+
app.configure :production, :staging do
|
24
|
+
app.set :request_gzip, true unless app.respond_to?(:request_gzip)
|
25
|
+
app.set :assets_debug, false unless app.respond_to?(:assets_debug)
|
26
|
+
app.set :asset_digest, true unless app.respond_to?(:asset_digest)
|
27
|
+
app.set :asset_host, nil unless app.respond_to?(:asset_host)
|
28
|
+
app.set :asset_compile, false unless app.respond_to?(:asset_compile)
|
29
|
+
app.asset_env.js_compressor = Uglifier.new(:mangle => false)
|
30
|
+
app.asset_env.css_compressor = CssCompressor.new
|
31
|
+
end
|
32
|
+
|
33
|
+
app.configure :development, :test do
|
34
|
+
app.set :request_gzip, false unless app.respond_to?(:request_gzip)
|
35
|
+
app.set :assets_debug, true unless app.respond_to?(:assets_debug)
|
36
|
+
app.set :asset_digest, false unless app.respond_to?(:asset_digest)
|
37
|
+
app.set :asset_host, nil unless app.respond_to?(:asset_host)
|
38
|
+
app.set :asset_compile, true unless app.respond_to?(:asset_compile)
|
39
|
+
end
|
40
|
+
|
41
|
+
app.asset_dirs.each do |asset_type|
|
42
|
+
app.asset_env.append_path(File.join(app.asset_path, asset_type))
|
43
|
+
app.asset_env.append_path(File.join(Spar.root, 'lib', DEFAULT_PREFIX, asset_type))
|
44
|
+
app.asset_env.append_path(File.join(Spar.root, 'vendor', DEFAULT_PREFIX, asset_type))
|
45
|
+
Gem.loaded_specs.each do |name, gem|
|
46
|
+
app.asset_env.append_path(File.join(gem.full_gem_path, 'vendor', DEFAULT_PREFIX, asset_type))
|
47
|
+
app.asset_env.append_path(File.join(gem.full_gem_path, 'app', 'assets', DEFAULT_PREFIX, asset_type))
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
if File.exist?(app.manifest_path)
|
52
|
+
app.set :asset_digests, YAML.load_file(app.manifest_path)
|
53
|
+
else
|
54
|
+
app.set :asset_digests, {}
|
55
|
+
end
|
56
|
+
|
57
|
+
Spar::Helpers.configure do |config|
|
58
|
+
config.asset_environment = app.asset_env
|
59
|
+
config.asset_prefix = app.asset_prefix
|
60
|
+
config.compile_assets = app.asset_compile
|
61
|
+
config.debug_assets = app.assets_debug
|
62
|
+
config.digest_assets = app.asset_digest
|
63
|
+
config.asset_digests = app.asset_digests
|
64
|
+
config.asset_host = app.asset_host
|
65
|
+
end
|
66
|
+
|
67
|
+
app.asset_env.context_class.instance_eval do
|
68
|
+
include ::Spar::Helpers
|
69
|
+
end
|
70
|
+
|
71
|
+
app.helpers do
|
72
|
+
include Spar::Helpers
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
data/lib/spar/base.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'rbconfig'
|
2
|
+
require 'sinatra/base'
|
3
|
+
require 'haml'
|
4
|
+
require 'sass'
|
5
|
+
|
6
|
+
module Spar
|
7
|
+
class Base < Sinatra::Base
|
8
|
+
|
9
|
+
class << self
|
10
|
+
|
11
|
+
attr_accessor :setup_block
|
12
|
+
|
13
|
+
def inherited(base)
|
14
|
+
super
|
15
|
+
|
16
|
+
find_root
|
17
|
+
|
18
|
+
set :environment, (ENV['RACK_ENV'] || :development).to_sym
|
19
|
+
set :public_path, File.join(Spar.root, 'public')
|
20
|
+
set :library_path, File.join(Spar.root, 'lib')
|
21
|
+
set :root, File.join(Spar.root, 'app')
|
22
|
+
|
23
|
+
config_path = "#{Spar.root}/config/environments/#{environment}.rb"
|
24
|
+
eval File.read(config_path), binding, config_path
|
25
|
+
|
26
|
+
Dir[File.join(library_path, '*.rb')].each {|file| autoload file }
|
27
|
+
|
28
|
+
puts "Started Spar Server [#{environment.to_s}]"
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
|
33
|
+
def find_root
|
34
|
+
Spar.root = begin
|
35
|
+
# Remove the line number from backtraces making sure we don't leave anything behind
|
36
|
+
call_stack = caller.map { |p| p.sub(/:\d+.*/, '') }
|
37
|
+
root_path = File.dirname(call_stack.detect { |p| p !~ %r[[\w.-]*/lib/spar|rack[\w.-]*/lib/rack] })
|
38
|
+
|
39
|
+
while root_path && File.directory?(root_path) && !File.exist?("#{root_path}/config.ru")
|
40
|
+
parent = File.dirname(root_path)
|
41
|
+
root_path = parent != root_path && parent
|
42
|
+
end
|
43
|
+
|
44
|
+
root = File.exist?("#{root_path}/config.ru") ? root_path : Dir.pwd
|
45
|
+
raise "Could not find root path for #{self}" unless root
|
46
|
+
|
47
|
+
RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ ? Pathname.new(root).expand_path : Pathname.new(root).realpath
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
data/lib/spar/cli.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'rbconfig'
|
2
|
+
|
3
|
+
if RUBY_VERSION < '1.9.2'
|
4
|
+
desc = defined?(RUBY_DESCRIPTION) ? RUBY_DESCRIPTION : "ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE})"
|
5
|
+
abort <<-end_message
|
6
|
+
|
7
|
+
Spar requires Ruby 1.9.2+.
|
8
|
+
|
9
|
+
You're running
|
10
|
+
#{desc}
|
11
|
+
|
12
|
+
Please upgrade to continue.
|
13
|
+
|
14
|
+
end_message
|
15
|
+
end
|
16
|
+
Signal.trap("INT") { puts; exit(1) }
|
17
|
+
|
18
|
+
require 'spar/version'
|
19
|
+
|
20
|
+
if ['--version', '-v'].include?(ARGV.first)
|
21
|
+
puts "Spar #{Spar::VERSION}"
|
22
|
+
exit(0)
|
23
|
+
end
|
24
|
+
|
25
|
+
if ARGV.first.nil?
|
26
|
+
puts "Please specify a name for your new app like so: 'spar APP_NAME'"
|
27
|
+
exit(0)
|
28
|
+
end
|
29
|
+
|
30
|
+
require 'spar/generators/application'
|
31
|
+
|
32
|
+
Spar::Generators::Application.new.generate(ARGV.first)
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Spar
|
2
|
+
module Assets
|
3
|
+
class CssCompressor
|
4
|
+
def compress(css)
|
5
|
+
if css.count("\n") > 2
|
6
|
+
Sass::Engine.new(css,
|
7
|
+
:syntax => :scss,
|
8
|
+
:cache => false,
|
9
|
+
:read_cache => false,
|
10
|
+
:style => :compressed).render
|
11
|
+
else
|
12
|
+
css
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,198 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'find'
|
3
|
+
require 'mime/types'
|
4
|
+
require 'aws-sdk'
|
5
|
+
require 'cloudfront-invalidator'
|
6
|
+
|
7
|
+
module Spar
|
8
|
+
class Deployer
|
9
|
+
|
10
|
+
def initialize(app, options = {})
|
11
|
+
@app = app
|
12
|
+
unless @app.respond_to? :aws_access_key_id and @app.respond_to? :aws_secret_access_key
|
13
|
+
raise ArgumentError.new(":aws_acces_key_id and :aws_secret_access_key are required")
|
14
|
+
end
|
15
|
+
AWS.config(
|
16
|
+
:access_key_id => @app.aws_access_key_id,
|
17
|
+
:secret_access_key => @app.aws_secret_access_key,
|
18
|
+
:logger => Logger.new($stderr),
|
19
|
+
:log_formatter => AWS::Core::LogFormatter.colored
|
20
|
+
)
|
21
|
+
AWS.config.logger.level = Logger::WARN
|
22
|
+
@s3 = AWS::S3.new
|
23
|
+
@bucket = @s3.buckets[App.s3_bucket]
|
24
|
+
@age_out = options.delete(:age_out) || 60 * 60 * 24 * 3 # 3 days
|
25
|
+
@cache_control = options.delete(:cache_control) || "public, max-age=#{60 * 60 * 24 * 7}"
|
26
|
+
@zip_files = options.delete(:zip_files) || /\.(?:css|html|js|svg|txt|xml)$/
|
27
|
+
@view_paths = app.precompile_view_paths || []
|
28
|
+
@to_invalidate = []
|
29
|
+
end
|
30
|
+
|
31
|
+
def upload_assets
|
32
|
+
Dir.chdir(@app.public_path) do
|
33
|
+
local = Find.find( 'assets' ).reject{|f| %w[assets/index.html assets/manifest.yml].index f}.reject! { |f| File.directory? f }
|
34
|
+
remote = @bucket.objects.with_prefix( 'assets/' ).map{|o|o.key}.reject{|o| o =~ /\/$/ }
|
35
|
+
to_delete = remote - local
|
36
|
+
to_upload = local - remote
|
37
|
+
to_check = remote & local
|
38
|
+
|
39
|
+
to_check.each do |file|
|
40
|
+
if @bucket.objects[file].etag.gsub(/\"/,'') != Digest::MD5.hexdigest(File.read(file))
|
41
|
+
logger "Etag mismatch for: #{file}"
|
42
|
+
headers = {
|
43
|
+
:content_type => MIME::Types.of(file.gsub(/\.?gz$/, '')).first,
|
44
|
+
:cache_control => 'public, max-age=0',
|
45
|
+
:acl => :public_read
|
46
|
+
}
|
47
|
+
headers[:content_encoding] = :gzip if %w[svgz gz].index file.split('.').last
|
48
|
+
|
49
|
+
logger "Uploading #{file}", headers
|
50
|
+
@bucket.objects[file].write(headers.merge :data => File.read(file) )
|
51
|
+
@to_invalidate << file
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
to_upload.each do |file|
|
56
|
+
|
57
|
+
headers = {
|
58
|
+
:content_type => MIME::Types.of(file.gsub(/\.?gz$/, '')).first,
|
59
|
+
:cache_control => @cache_control,
|
60
|
+
:acl => :public_read,
|
61
|
+
:expires => (Time.now+60*60*24*365).httpdate
|
62
|
+
}
|
63
|
+
headers[:content_encoding] = :gzip if %w[svgz gz].index file.split('.').last
|
64
|
+
|
65
|
+
logger "Uploading #{file}", headers
|
66
|
+
@bucket.objects[file].write(headers.merge :data => File.read(file) )
|
67
|
+
end
|
68
|
+
|
69
|
+
age_out to_delete
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# TODO I really want this to call StaticCompiler#write_manifest directly. Another day.
|
74
|
+
def upload_views
|
75
|
+
Dir.chdir(@app.public_path) do
|
76
|
+
views = Find.find( '.' ).to_a
|
77
|
+
.select { |f| File.basename(f) == 'index.html' }
|
78
|
+
.sort_by { |f| f.length }
|
79
|
+
.map { |f| f.gsub('./','') }
|
80
|
+
.flatten
|
81
|
+
views.each do |file|
|
82
|
+
headers = {
|
83
|
+
:content_type => 'text/html; charset=utf-8',
|
84
|
+
:cache_control => 'public, max-age=0',
|
85
|
+
:acl => :public_read
|
86
|
+
}
|
87
|
+
logger "Uploading #{file}", headers
|
88
|
+
@bucket.objects[file].write(headers.merge :data => File.read(file) )
|
89
|
+
end
|
90
|
+
@to_invalidate << views.map { |f| [ f, f.gsub('/index.html', ''), f.gsub('index.html', '') ] }.flatten
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def upload_downloads
|
95
|
+
# TODO This should be doing checksum comparisons so that downloads can be replaced.
|
96
|
+
Dir.chdir(File.join(@app.root,'assets')) do
|
97
|
+
return unless Dir.exists? 'downloads'
|
98
|
+
local = Find.find( 'downloads' ).to_a.reject! { |f| File.directory? f }
|
99
|
+
remote = @bucket.objects.with_prefix( 'downloads/' ).map{|o|o.key}.reject{|o| o =~ /\/$/ }
|
100
|
+
to_delete = remote - local
|
101
|
+
to_upload = local - remote
|
102
|
+
@to_invalidate << to_upload
|
103
|
+
|
104
|
+
to_upload.each do |file|
|
105
|
+
|
106
|
+
headers = {
|
107
|
+
:content_type => MIME::Types.of(file).first,
|
108
|
+
:content_disposition => "attachment; filename=#{File.basename(file)}",
|
109
|
+
:cache_control => 'public, max-age=86400',
|
110
|
+
:acl => :public_read,
|
111
|
+
:expires => (Time.now+60*60*24*365).httpdate
|
112
|
+
}
|
113
|
+
|
114
|
+
logger "Uploading #{file}", headers
|
115
|
+
@bucket.objects[file].write(headers.merge :data => File.read(file) )
|
116
|
+
end
|
117
|
+
age_out to_delete
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# Copy `assets/favicon-hash.ico` (if changed in this deployment) to /favicon.ico.
|
122
|
+
def upload_favicon
|
123
|
+
Dir.chdir(@app.public_path) do
|
124
|
+
to_upload.select{ |path| path =~ /favicon/}.each do |hashed| # Should only be one.
|
125
|
+
logger "Copying favicon.ico into place from #{hashed}"
|
126
|
+
@bucket.objects[hashed].copy_to('favicon.ico', { :acl => :public_read })
|
127
|
+
@to_invalidate << 'favicon.ico'
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Remove obsolete objects once they are sufficiently old
|
133
|
+
def age_out(list)
|
134
|
+
list.flatten.each do |file|
|
135
|
+
if Time.now - @bucket.objects[file].last_modified > @age_out
|
136
|
+
logger "Deleting #{file}"
|
137
|
+
@bucket.objects[file].delete
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Add a file indicating time of most recent deploy.
|
143
|
+
def timestamp!
|
144
|
+
@bucket.objects['TIMESTAMP.txt'].write(
|
145
|
+
:data => Time.now.to_s+"\n",
|
146
|
+
:content_type => "text/plain",
|
147
|
+
:acl => :public_read
|
148
|
+
)
|
149
|
+
@to_invalidate << 'TIMESTAMP.txt'
|
150
|
+
end
|
151
|
+
|
152
|
+
def deploy
|
153
|
+
upload_assets
|
154
|
+
upload_views
|
155
|
+
upload_downloads
|
156
|
+
timestamp!
|
157
|
+
invalidate_cloudfront
|
158
|
+
end
|
159
|
+
|
160
|
+
def invalidate_cloudfront
|
161
|
+
return unless @app.respond_to? :cloudfront_distribution
|
162
|
+
@to_invalidate.flatten!
|
163
|
+
@to_invalidate.uniq!
|
164
|
+
logger "Issuing CloudFront invalidation request for #{@to_invalidate.count} objects."
|
165
|
+
CloudfrontInvalidator.new(
|
166
|
+
@app.aws_access_key_id,
|
167
|
+
@app.aws_secret_access_key,
|
168
|
+
@app.cloudfront_distribution,
|
169
|
+
).invalidate(@to_invalidate) do |status,time|
|
170
|
+
puts "Invalidation #{status} after %.2f seconds" % time.to_f
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def logger(*args)
|
175
|
+
STDERR.puts args.map{|x|x.to_s}.join(' ')
|
176
|
+
end
|
177
|
+
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
# MIME::Types has incomplete knowledge of how web fonts get served, and
|
182
|
+
# complains when we try to fix it.
|
183
|
+
module Kernel
|
184
|
+
def suppress_warnings
|
185
|
+
original_verbosity = $VERBOSE
|
186
|
+
$VERBOSE = nil
|
187
|
+
result = yield
|
188
|
+
$VERBOSE = original_verbosity
|
189
|
+
return result
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
Kernel.suppress_warnings do
|
194
|
+
MIME::Types.add(
|
195
|
+
MIME::Type.new('image/svg+xml'){|t| t.extensions = %w[svgz]},
|
196
|
+
MIME::Type.new('application/vnd.ms-fontobject'){|t| t.extensions = %w[eot]}
|
197
|
+
)
|
198
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'thor'
|
2
|
+
|
3
|
+
module Spar
|
4
|
+
module Generators
|
5
|
+
class Error < Thor::Error
|
6
|
+
end
|
7
|
+
|
8
|
+
class Application < Thor
|
9
|
+
include Thor::Actions
|
10
|
+
|
11
|
+
source_root File.expand_path("../", __FILE__)
|
12
|
+
|
13
|
+
desc "new APP_NAME", "create a new Spar app"
|
14
|
+
def generate(name)
|
15
|
+
directory 'templates', name
|
16
|
+
inside name do
|
17
|
+
run('chmod +x script/*')
|
18
|
+
empty_directory_with_gitkeep "public"
|
19
|
+
empty_directory "lib"
|
20
|
+
empty_directory_with_gitkeep "lib/tasks"
|
21
|
+
empty_directory_with_gitkeep "lib/assets"
|
22
|
+
empty_directory_with_gitkeep "app/assets/images"
|
23
|
+
empty_directory_with_gitkeep "vendor/assets/javascripts"
|
24
|
+
empty_directory_with_gitkeep "vendor/assets/stylesheets"
|
25
|
+
end
|
26
|
+
puts "A new Spar app has been created in #{name} - Have Fun!"
|
27
|
+
end
|
28
|
+
|
29
|
+
protected
|
30
|
+
|
31
|
+
def empty_directory_with_gitkeep(destination)
|
32
|
+
empty_directory(destination)
|
33
|
+
git_keep(destination)
|
34
|
+
end
|
35
|
+
|
36
|
+
def git_keep(destination)
|
37
|
+
create_file("#{destination}/.gitkeep")
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
Hi! I'm a new Spar application!
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# This is a manifest file that'll be compiled into application.js, which will include all the files
|
2
|
+
# listed below.
|
3
|
+
|
4
|
+
# Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
5
|
+
# or vendor/assets/javascripts of gems, if any, can be referenced here using a relative path.
|
6
|
+
|
7
|
+
# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
8
|
+
# the compiled file.
|
9
|
+
|
10
|
+
# = require_tree .
|
@@ -0,0 +1,11 @@
|
|
1
|
+
// This is a manifest file that'll be compiled into application.css, which will include all the files
|
2
|
+
// listed below.
|
3
|
+
|
4
|
+
// Any CSS and SASS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
5
|
+
// or vendor/assets/stylesheets of gems, if any, can be referenced here using a relative path.
|
6
|
+
|
7
|
+
// You're free to add application-wide styles to this file and they'll appear at the top of the
|
8
|
+
// compiled file, but it's generally better to create a new file per style scope.
|
9
|
+
|
10
|
+
// = require_self
|
11
|
+
// = require_tree .
|
@@ -0,0 +1 @@
|
|
1
|
+
rackup
|
data/lib/spar/helpers.rb
ADDED
@@ -0,0 +1,176 @@
|
|
1
|
+
require 'sprockets'
|
2
|
+
|
3
|
+
module Spar
|
4
|
+
module Helpers
|
5
|
+
|
6
|
+
class << self
|
7
|
+
# When true, the asset paths will return digest paths.
|
8
|
+
attr_accessor :asset_environment, :asset_prefix, :digest_assets, :asset_digests, :compile_assets, :debug_assets, :asset_host
|
9
|
+
|
10
|
+
# Convience method for configuring Sprockets::Helpers.
|
11
|
+
def configure
|
12
|
+
yield self
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def asset_paths
|
17
|
+
@asset_paths ||= begin
|
18
|
+
paths = Spar::Helpers::AssetPaths.new()
|
19
|
+
paths.asset_environment = Helpers.asset_environment
|
20
|
+
paths.asset_digests = Helpers.asset_digests
|
21
|
+
paths.compile_assets = compile_assets?
|
22
|
+
paths.digest_assets = digest_assets?
|
23
|
+
paths.asset_host = Helpers.asset_host
|
24
|
+
paths
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def javascript_include_tag(*sources)
|
29
|
+
sources.collect do |source|
|
30
|
+
if debug_assets? && asset = asset_paths.asset_for(source, 'js')
|
31
|
+
asset.to_a.map { |dep|
|
32
|
+
javascript_tag(asset_path(dep, :ext => 'js', :body => true, :digest => digest_assets?))
|
33
|
+
}
|
34
|
+
else
|
35
|
+
javascript_tag(asset_path(source, :ext => 'js', :body => false, :digest => digest_assets?))
|
36
|
+
end
|
37
|
+
end.join("\n")
|
38
|
+
end
|
39
|
+
|
40
|
+
def javascript_tag(src)
|
41
|
+
"<script src='#{src}' charset='utf-8'></script>"
|
42
|
+
end
|
43
|
+
|
44
|
+
def stylesheet_link_tag(*sources)
|
45
|
+
sources.collect do |source|
|
46
|
+
if debug_assets? && asset = asset_paths.asset_for(source, 'css')
|
47
|
+
asset.to_a.map { |dep|
|
48
|
+
stylesheet_tag(asset_path(dep, :ext => 'css', :body => true, :protocol => :request, :digest => digest_assets?))
|
49
|
+
}
|
50
|
+
else
|
51
|
+
stylesheet_tag(asset_path(source, :ext => 'css', :body => false, :protocol => :request, :digest => digest_assets?))
|
52
|
+
end
|
53
|
+
end.join("\n")
|
54
|
+
end
|
55
|
+
|
56
|
+
def stylesheet_tag(src)
|
57
|
+
"<link href='#{src}' rel='stylesheet'>"
|
58
|
+
end
|
59
|
+
|
60
|
+
def asset_path(source, options = {})
|
61
|
+
source = source.logical_path if source.respond_to?(:logical_path)
|
62
|
+
path = asset_paths.compute_public_path(source, Helpers.asset_prefix, options.merge(:body => true))
|
63
|
+
options[:body] ? "#{path}?body=1" : path
|
64
|
+
end
|
65
|
+
|
66
|
+
def image_path(source, options = {})
|
67
|
+
asset_path(source, options)
|
68
|
+
end
|
69
|
+
|
70
|
+
def font_path(source, options = {})
|
71
|
+
asset_path(source, options)
|
72
|
+
end
|
73
|
+
|
74
|
+
def javascript_path(source, options = {})
|
75
|
+
asset_path(source, options.merge(:ext => 'js'))
|
76
|
+
end
|
77
|
+
|
78
|
+
def stylesheet_path(source, options = {})
|
79
|
+
asset_path(source, options.merge(:ext => 'css'))
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
def debug_assets?
|
84
|
+
compile_assets? && !!Helpers.debug_assets
|
85
|
+
rescue NameError
|
86
|
+
false
|
87
|
+
end
|
88
|
+
|
89
|
+
def compile_assets?
|
90
|
+
!!Helpers.compile_assets
|
91
|
+
end
|
92
|
+
|
93
|
+
def digest_assets?
|
94
|
+
!!Helpers.digest_assets
|
95
|
+
end
|
96
|
+
|
97
|
+
class AssetPaths
|
98
|
+
URI_REGEXP = %r{^[-a-z]+://|^cid:|^//}
|
99
|
+
|
100
|
+
attr_accessor :asset_environment, :asset_digests, :compile_assets, :digest_assets, :asset_host
|
101
|
+
|
102
|
+
class AssetNotPrecompiledError < StandardError; end
|
103
|
+
|
104
|
+
def asset_for(source, ext)
|
105
|
+
source = source.to_s
|
106
|
+
return nil if is_uri?(source)
|
107
|
+
source = rewrite_extension(source, nil, ext)
|
108
|
+
asset_environment[source]
|
109
|
+
rescue Sprockets::FileOutsidePaths
|
110
|
+
nil
|
111
|
+
end
|
112
|
+
|
113
|
+
def compute_public_path(source, dir, options = {})
|
114
|
+
source = source.to_s
|
115
|
+
return source if is_uri?(source)
|
116
|
+
|
117
|
+
source = rewrite_extension(source, dir, options[:ext]) if options[:ext]
|
118
|
+
source = rewrite_asset_path(source, dir, options)
|
119
|
+
source = rewrite_host_and_protocol(source, options[:protocol])
|
120
|
+
source = rewrite_for_compression(source)
|
121
|
+
source
|
122
|
+
end
|
123
|
+
|
124
|
+
def is_uri?(path)
|
125
|
+
path =~ URI_REGEXP
|
126
|
+
end
|
127
|
+
|
128
|
+
def digest_for(logical_path)
|
129
|
+
if digest_assets && asset_digests && (digest = asset_digests[logical_path])
|
130
|
+
return digest
|
131
|
+
end
|
132
|
+
|
133
|
+
if compile_assets
|
134
|
+
if digest_assets && asset = asset_environment[logical_path]
|
135
|
+
return asset.digest_path
|
136
|
+
end
|
137
|
+
return logical_path
|
138
|
+
else
|
139
|
+
raise AssetNotPrecompiledError.new("#{logical_path} isn't precompiled")
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def rewrite_asset_path(source, dir, options = {})
|
144
|
+
if source[0] == ?/
|
145
|
+
source
|
146
|
+
else
|
147
|
+
source = digest_for(source) unless options[:digest] == false
|
148
|
+
source = File.join(dir, source)
|
149
|
+
source = "/#{source}" unless source =~ /^\//
|
150
|
+
source
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def rewrite_extension(source, dir, ext)
|
155
|
+
if ext && File.extname(source) != ".#{ext}"
|
156
|
+
"#{source}.#{ext}"
|
157
|
+
else
|
158
|
+
source
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def rewrite_for_compression(source)
|
163
|
+
if App.request_gzip and %w[.js .css].index File.extname(source)
|
164
|
+
source + 'gz'
|
165
|
+
else
|
166
|
+
source
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def rewrite_host_and_protocol(source, protocol = nil)
|
171
|
+
asset_host ? "#{asset_host}#{source}" : source
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'rack/test'
|
3
|
+
|
4
|
+
module Spar
|
5
|
+
class StaticCompiler
|
6
|
+
|
7
|
+
VIEW_PATH_SPLITER = /(.*)\/([^\/]+?)/
|
8
|
+
|
9
|
+
attr_accessor :app, :env, :asset_path, :paths
|
10
|
+
|
11
|
+
def self.load_tasks
|
12
|
+
Dir[File.expand_path('../tasks/spar.rake', __FILE__)].each { |ext| load ext }
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(app, options = {})
|
16
|
+
@app = app
|
17
|
+
@env = app.asset_env
|
18
|
+
@public_path = app.public_path
|
19
|
+
@asset_path = File.join(app.public_path, app.asset_prefix)
|
20
|
+
@paths = App.asset_precompile
|
21
|
+
@digest = app.asset_digest
|
22
|
+
@zip_files = options.delete(:zip_files) || /\.(?:css|html|js|svg|txt|xml)$/
|
23
|
+
@view_paths = app.precompile_view_paths || []
|
24
|
+
@digest_free_assets = app.digest_free_assets || []
|
25
|
+
end
|
26
|
+
|
27
|
+
def compile
|
28
|
+
manifest = {}
|
29
|
+
@env.each_logical_path do |logical_path|
|
30
|
+
next unless compile_path?(logical_path)
|
31
|
+
if asset = @env.find_asset(logical_path)
|
32
|
+
manifest[logical_path] = write_asset(asset)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
write_manifest(manifest)
|
36
|
+
browser = Rack::Test::Session.new(Rack::MockSession.new(@app))
|
37
|
+
@view_paths.each do |path|
|
38
|
+
browser.get(path)
|
39
|
+
write_view(path, browser.last_response.body)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def write_manifest(manifest)
|
44
|
+
FileUtils.mkdir_p(@asset_path)
|
45
|
+
File.open("#{@asset_path}/manifest.yml", 'wb') do |f|
|
46
|
+
YAML.dump(manifest, f)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def write_view(path, body)
|
51
|
+
path = File.join(@public_path,path)
|
52
|
+
FileUtils.mkdir_p path
|
53
|
+
File.open(File.join(path,"index.html"), 'wb') do |f|
|
54
|
+
f.write(body)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def write_asset(asset)
|
59
|
+
path_for(asset).tap do |path|
|
60
|
+
filename = File.join(asset_path, path)
|
61
|
+
FileUtils.mkdir_p File.dirname(filename)
|
62
|
+
asset.write_to(filename)
|
63
|
+
# Use "example.txtgz" instead of "example.txt.gz" for Safari gzip workaround.
|
64
|
+
asset.write_to("#{filename}gz") if filename.to_s =~ @zip_files
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def compile_path?(logical_path)
|
69
|
+
@paths.each do |path|
|
70
|
+
case path
|
71
|
+
when Regexp
|
72
|
+
return true if path.match(logical_path)
|
73
|
+
when Proc
|
74
|
+
return true if path.call(logical_path)
|
75
|
+
else
|
76
|
+
return true if File.fnmatch(path.to_s, logical_path)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
false
|
80
|
+
end
|
81
|
+
|
82
|
+
def path_for(asset)
|
83
|
+
@digest_free_assets.each do |digest_free_path|
|
84
|
+
if digest_free_path.match(asset.logical_path)
|
85
|
+
return asset.logical_path
|
86
|
+
end
|
87
|
+
end
|
88
|
+
@digest ? asset.digest_path : asset.logical_path
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/lib/spar/tasks.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
namespace :assets do
|
2
|
+
desc "Compile all the assets"
|
3
|
+
task :precompile => :clean do
|
4
|
+
Spar::Helpers.configure do |config|
|
5
|
+
config.compile_assets = true
|
6
|
+
end
|
7
|
+
|
8
|
+
compiler = Spar::StaticCompiler.new(App)
|
9
|
+
compiler.compile
|
10
|
+
end
|
11
|
+
|
12
|
+
desc "Remove compiled assets"
|
13
|
+
task :clean => :environment do
|
14
|
+
system "/bin/rm", "-rf", App.public_path
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
desc "Deploy a freshly precompiled site to S3 and purge CloudFront as is appropriate."
|
19
|
+
task :deploy => "assets:precompile" do
|
20
|
+
Spar::Deployer.new(App).deploy
|
21
|
+
end
|
data/lib/spar/version.rb
ADDED
metadata
ADDED
@@ -0,0 +1,291 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: spar
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.7
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Matt Hodgson
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-08-29 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: cucumber
|
16
|
+
requirement: &70277087043500 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70277087043500
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: aruba
|
27
|
+
requirement: &70277087076820 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70277087076820
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: sinatra
|
38
|
+
requirement: &70277087076300 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '1.3'
|
44
|
+
- - ! '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 1.3.2
|
47
|
+
type: :runtime
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: *70277087076300
|
50
|
+
- !ruby/object:Gem::Dependency
|
51
|
+
name: sass
|
52
|
+
requirement: &70277087075520 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ~>
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '3.1'
|
58
|
+
- - ! '>='
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: 3.1.10
|
61
|
+
type: :runtime
|
62
|
+
prerelease: false
|
63
|
+
version_requirements: *70277087075520
|
64
|
+
- !ruby/object:Gem::Dependency
|
65
|
+
name: haml
|
66
|
+
requirement: &70277087074780 !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
68
|
+
requirements:
|
69
|
+
- - ~>
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '3.1'
|
72
|
+
- - ! '>='
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: 3.1.4
|
75
|
+
type: :runtime
|
76
|
+
prerelease: false
|
77
|
+
version_requirements: *70277087074780
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: coffee-script
|
80
|
+
requirement: &70277087074040 !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ~>
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '2.2'
|
86
|
+
- - ! '>='
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: 2.2.0
|
89
|
+
type: :runtime
|
90
|
+
prerelease: false
|
91
|
+
version_requirements: *70277087074040
|
92
|
+
- !ruby/object:Gem::Dependency
|
93
|
+
name: uglifier
|
94
|
+
requirement: &70277087073300 !ruby/object:Gem::Requirement
|
95
|
+
none: false
|
96
|
+
requirements:
|
97
|
+
- - ~>
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '1.0'
|
100
|
+
- - ! '>='
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: 1.0.3
|
103
|
+
type: :runtime
|
104
|
+
prerelease: false
|
105
|
+
version_requirements: *70277087073300
|
106
|
+
- !ruby/object:Gem::Dependency
|
107
|
+
name: sprockets
|
108
|
+
requirement: &70277087072560 !ruby/object:Gem::Requirement
|
109
|
+
none: false
|
110
|
+
requirements:
|
111
|
+
- - ~>
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '2.4'
|
114
|
+
- - ! '>='
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: 2.4.0
|
117
|
+
type: :runtime
|
118
|
+
prerelease: false
|
119
|
+
version_requirements: *70277087072560
|
120
|
+
- !ruby/object:Gem::Dependency
|
121
|
+
name: sprockets-sass
|
122
|
+
requirement: &70277087071820 !ruby/object:Gem::Requirement
|
123
|
+
none: false
|
124
|
+
requirements:
|
125
|
+
- - ~>
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: '0.5'
|
128
|
+
- - ! '>='
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '0.5'
|
131
|
+
type: :runtime
|
132
|
+
prerelease: false
|
133
|
+
version_requirements: *70277087071820
|
134
|
+
- !ruby/object:Gem::Dependency
|
135
|
+
name: bundler
|
136
|
+
requirement: &70277087071080 !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ~>
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '1.0'
|
142
|
+
- - ! '>='
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: 1.0.18
|
145
|
+
type: :runtime
|
146
|
+
prerelease: false
|
147
|
+
version_requirements: *70277087071080
|
148
|
+
- !ruby/object:Gem::Dependency
|
149
|
+
name: thor
|
150
|
+
requirement: &70277087070340 !ruby/object:Gem::Requirement
|
151
|
+
none: false
|
152
|
+
requirements:
|
153
|
+
- - ~>
|
154
|
+
- !ruby/object:Gem::Version
|
155
|
+
version: '0.14'
|
156
|
+
- - ! '>='
|
157
|
+
- !ruby/object:Gem::Version
|
158
|
+
version: 0.14.6
|
159
|
+
type: :runtime
|
160
|
+
prerelease: false
|
161
|
+
version_requirements: *70277087070340
|
162
|
+
- !ruby/object:Gem::Dependency
|
163
|
+
name: rake
|
164
|
+
requirement: &70277087069600 !ruby/object:Gem::Requirement
|
165
|
+
none: false
|
166
|
+
requirements:
|
167
|
+
- - ~>
|
168
|
+
- !ruby/object:Gem::Version
|
169
|
+
version: '0.9'
|
170
|
+
- - ! '>='
|
171
|
+
- !ruby/object:Gem::Version
|
172
|
+
version: 0.9.2
|
173
|
+
type: :runtime
|
174
|
+
prerelease: false
|
175
|
+
version_requirements: *70277087069600
|
176
|
+
- !ruby/object:Gem::Dependency
|
177
|
+
name: rack-test
|
178
|
+
requirement: &70277087068860 !ruby/object:Gem::Requirement
|
179
|
+
none: false
|
180
|
+
requirements:
|
181
|
+
- - ~>
|
182
|
+
- !ruby/object:Gem::Version
|
183
|
+
version: '0.6'
|
184
|
+
- - ! '>='
|
185
|
+
- !ruby/object:Gem::Version
|
186
|
+
version: 0.6.1
|
187
|
+
type: :runtime
|
188
|
+
prerelease: false
|
189
|
+
version_requirements: *70277087068860
|
190
|
+
- !ruby/object:Gem::Dependency
|
191
|
+
name: test-unit
|
192
|
+
requirement: &70277086965040 !ruby/object:Gem::Requirement
|
193
|
+
none: false
|
194
|
+
requirements:
|
195
|
+
- - ! '>='
|
196
|
+
- !ruby/object:Gem::Version
|
197
|
+
version: '0'
|
198
|
+
type: :runtime
|
199
|
+
prerelease: false
|
200
|
+
version_requirements: *70277086965040
|
201
|
+
- !ruby/object:Gem::Dependency
|
202
|
+
name: mime-types
|
203
|
+
requirement: &70277086964580 !ruby/object:Gem::Requirement
|
204
|
+
none: false
|
205
|
+
requirements:
|
206
|
+
- - ! '>='
|
207
|
+
- !ruby/object:Gem::Version
|
208
|
+
version: '0'
|
209
|
+
type: :runtime
|
210
|
+
prerelease: false
|
211
|
+
version_requirements: *70277086964580
|
212
|
+
- !ruby/object:Gem::Dependency
|
213
|
+
name: aws-sdk
|
214
|
+
requirement: &70277086964160 !ruby/object:Gem::Requirement
|
215
|
+
none: false
|
216
|
+
requirements:
|
217
|
+
- - ! '>='
|
218
|
+
- !ruby/object:Gem::Version
|
219
|
+
version: '0'
|
220
|
+
type: :runtime
|
221
|
+
prerelease: false
|
222
|
+
version_requirements: *70277086964160
|
223
|
+
- !ruby/object:Gem::Dependency
|
224
|
+
name: cloudfront-invalidator
|
225
|
+
requirement: &70277086963660 !ruby/object:Gem::Requirement
|
226
|
+
none: false
|
227
|
+
requirements:
|
228
|
+
- - ~>
|
229
|
+
- !ruby/object:Gem::Version
|
230
|
+
version: '0.2'
|
231
|
+
type: :runtime
|
232
|
+
prerelease: false
|
233
|
+
version_requirements: *70277086963660
|
234
|
+
description: Spar uses Sprockets and Sinatra to provide an asset development environment
|
235
|
+
very similar to the asset pipeline found in Rails. It allows you to use all the
|
236
|
+
awesome features of the asset pipeline without all the heft of Rails.
|
237
|
+
email:
|
238
|
+
- matt@boundlesslearning.com
|
239
|
+
executables:
|
240
|
+
- spar
|
241
|
+
extensions: []
|
242
|
+
extra_rdoc_files: []
|
243
|
+
files:
|
244
|
+
- README.md
|
245
|
+
- bin/spar
|
246
|
+
- lib/spar/assets.rb
|
247
|
+
- lib/spar/base.rb
|
248
|
+
- lib/spar/cli.rb
|
249
|
+
- lib/spar/css_compressor.rb
|
250
|
+
- lib/spar/deployer.rb
|
251
|
+
- lib/spar/generators/application.rb
|
252
|
+
- lib/spar/generators/templates/app/assets/javascripts/javascript.js.coffee
|
253
|
+
- lib/spar/generators/templates/app/assets/stylesheets/application.css.sass
|
254
|
+
- lib/spar/generators/templates/app/views/index.haml
|
255
|
+
- lib/spar/generators/templates/config/application.rb
|
256
|
+
- lib/spar/generators/templates/config.ru
|
257
|
+
- lib/spar/generators/templates/Gemfile
|
258
|
+
- lib/spar/generators/templates/Rakefile
|
259
|
+
- lib/spar/generators/templates/README
|
260
|
+
- lib/spar/generators/templates/script/server
|
261
|
+
- lib/spar/helpers.rb
|
262
|
+
- lib/spar/static_compiler.rb
|
263
|
+
- lib/spar/tasks.rb
|
264
|
+
- lib/spar/version.rb
|
265
|
+
- lib/spar.rb
|
266
|
+
homepage: http://github.com/BoundlessLearning/spar
|
267
|
+
licenses: []
|
268
|
+
post_install_message:
|
269
|
+
rdoc_options: []
|
270
|
+
require_paths:
|
271
|
+
- lib
|
272
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
273
|
+
none: false
|
274
|
+
requirements:
|
275
|
+
- - ! '>='
|
276
|
+
- !ruby/object:Gem::Version
|
277
|
+
version: 1.9.2
|
278
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
279
|
+
none: false
|
280
|
+
requirements:
|
281
|
+
- - ! '>='
|
282
|
+
- !ruby/object:Gem::Version
|
283
|
+
version: '0'
|
284
|
+
requirements: []
|
285
|
+
rubyforge_project:
|
286
|
+
rubygems_version: 1.8.10
|
287
|
+
signing_key:
|
288
|
+
specification_version: 3
|
289
|
+
summary: A simple framework for developing single page web apps with support for haml,
|
290
|
+
sass, coffeescript, and pretty much anything else.
|
291
|
+
test_files: []
|