spar 0.2.7 → 1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/README.md +272 -1
  2. data/bin/spar +30 -1
  3. data/lib/spar.rb +128 -7
  4. data/lib/spar/assets/404.jpg +0 -0
  5. data/lib/spar/assets/500.jpg +0 -0
  6. data/lib/spar/cli.rb +75 -22
  7. data/lib/spar/compiled_asset.rb +90 -0
  8. data/lib/spar/compiler.rb +74 -0
  9. data/lib/spar/compressor.rb +18 -0
  10. data/lib/spar/deployers/cloudfront_deployer.rb +33 -0
  11. data/lib/spar/deployers/deployer.rb +26 -0
  12. data/lib/spar/deployers/local_deployer.rb +15 -0
  13. data/lib/spar/deployers/s3_deployer.rb +75 -0
  14. data/lib/spar/directive_processor.rb +36 -0
  15. data/lib/spar/exceptions.rb +330 -0
  16. data/lib/spar/generators/templates/{README → base/README} +0 -0
  17. data/lib/spar/generators/templates/base/config.yml +12 -0
  18. data/lib/spar/generators/templates/pages/haml/app/pages/index.html.haml +11 -0
  19. data/lib/spar/generators/templates/pages/html/app/pages/index.html +16 -0
  20. data/lib/spar/generators/templates/{app/assets/javascripts/javascript.js.coffee → scripts/coffee/app/javascripts/application.js.coffee} +6 -3
  21. data/lib/spar/generators/templates/scripts/js/app/javascripts/application.js +11 -0
  22. data/lib/spar/generators/templates/styles/css/app/stylesheets/application.css +18 -0
  23. data/lib/spar/generators/templates/{app/assets → styles/sass/app}/stylesheets/application.css.sass +8 -4
  24. data/lib/spar/helpers.rb +31 -102
  25. data/lib/spar/not_found.rb +64 -0
  26. data/lib/spar/rewrite.rb +29 -0
  27. data/lib/spar/static.rb +46 -0
  28. data/lib/spar/tasks.rb +8 -13
  29. data/lib/spar/version.rb +1 -1
  30. metadata +261 -66
  31. data/lib/spar/assets.rb +0 -78
  32. data/lib/spar/base.rb +0 -54
  33. data/lib/spar/css_compressor.rb +0 -17
  34. data/lib/spar/deployer.rb +0 -198
  35. data/lib/spar/generators/application.rb +0 -42
  36. data/lib/spar/generators/templates/Gemfile +0 -3
  37. data/lib/spar/generators/templates/Rakefile +0 -8
  38. data/lib/spar/generators/templates/app/views/index.haml +0 -7
  39. data/lib/spar/generators/templates/config.ru +0 -9
  40. data/lib/spar/generators/templates/config/application.rb +0 -12
  41. data/lib/spar/generators/templates/script/server +0 -1
  42. 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
@@ -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,3 +0,0 @@
1
- source "http://rubygems.org"
2
-
3
- gem "spar"
@@ -1,8 +0,0 @@
1
- require 'bundler'
2
- Bundler.require
3
-
4
- task :environment do
5
- require './config/application'
6
- end
7
-
8
- require 'spar/tasks'
@@ -1,7 +0,0 @@
1
- !!! Strict
2
- %html
3
- %head
4
- %title Spar App
5
- %link{:rel => "stylesheet", :href => stylesheet_path('application')}
6
- %script{:src => javascript_path('application')}
7
- %body
@@ -1,9 +0,0 @@
1
- require './config/application'
2
-
3
- map "/assets" do
4
- run App.asset_env
5
- end
6
-
7
- map "/" do
8
- run App
9
- end
@@ -1,12 +0,0 @@
1
- require 'bundler'
2
- Bundler.require
3
-
4
- class App < Spar::Base
5
-
6
- register Spar::Assets
7
-
8
- get "/" do
9
- haml :index
10
- end
11
-
12
- end
@@ -1 +0,0 @@
1
- rackup