spar 0.2.7
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|