spar 0.2.7 → 1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +272 -1
- data/bin/spar +30 -1
- data/lib/spar.rb +128 -7
- data/lib/spar/assets/404.jpg +0 -0
- data/lib/spar/assets/500.jpg +0 -0
- data/lib/spar/cli.rb +75 -22
- data/lib/spar/compiled_asset.rb +90 -0
- data/lib/spar/compiler.rb +74 -0
- data/lib/spar/compressor.rb +18 -0
- data/lib/spar/deployers/cloudfront_deployer.rb +33 -0
- data/lib/spar/deployers/deployer.rb +26 -0
- data/lib/spar/deployers/local_deployer.rb +15 -0
- data/lib/spar/deployers/s3_deployer.rb +75 -0
- data/lib/spar/directive_processor.rb +36 -0
- data/lib/spar/exceptions.rb +330 -0
- data/lib/spar/generators/templates/{README → base/README} +0 -0
- data/lib/spar/generators/templates/base/config.yml +12 -0
- data/lib/spar/generators/templates/pages/haml/app/pages/index.html.haml +11 -0
- data/lib/spar/generators/templates/pages/html/app/pages/index.html +16 -0
- data/lib/spar/generators/templates/{app/assets/javascripts/javascript.js.coffee → scripts/coffee/app/javascripts/application.js.coffee} +6 -3
- data/lib/spar/generators/templates/scripts/js/app/javascripts/application.js +11 -0
- data/lib/spar/generators/templates/styles/css/app/stylesheets/application.css +18 -0
- data/lib/spar/generators/templates/{app/assets → styles/sass/app}/stylesheets/application.css.sass +8 -4
- data/lib/spar/helpers.rb +31 -102
- data/lib/spar/not_found.rb +64 -0
- data/lib/spar/rewrite.rb +29 -0
- data/lib/spar/static.rb +46 -0
- data/lib/spar/tasks.rb +8 -13
- data/lib/spar/version.rb +1 -1
- metadata +261 -66
- data/lib/spar/assets.rb +0 -78
- data/lib/spar/base.rb +0 -54
- data/lib/spar/css_compressor.rb +0 -17
- data/lib/spar/deployer.rb +0 -198
- data/lib/spar/generators/application.rb +0 -42
- data/lib/spar/generators/templates/Gemfile +0 -3
- data/lib/spar/generators/templates/Rakefile +0 -8
- data/lib/spar/generators/templates/app/views/index.haml +0 -7
- data/lib/spar/generators/templates/config.ru +0 -9
- data/lib/spar/generators/templates/config/application.rb +0 -12
- data/lib/spar/generators/templates/script/server +0 -1
- data/lib/spar/static_compiler.rb +0 -91
data/lib/spar/assets.rb
DELETED
@@ -1,78 +0,0 @@
|
|
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
DELETED
@@ -1,54 +0,0 @@
|
|
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/css_compressor.rb
DELETED
@@ -1,17 +0,0 @@
|
|
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
|
data/lib/spar/deployer.rb
DELETED
@@ -1,198 +0,0 @@
|
|
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
|
@@ -1,42 +0,0 @@
|
|
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
|
@@ -1 +0,0 @@
|
|
1
|
-
rackup
|