spar 0.2.7 → 1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|