spar 0.2.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1 @@
1
+ Docs Coming Soon!
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "spar/cli"
@@ -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
@@ -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
@@ -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
@@ -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,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "spar"
@@ -0,0 +1 @@
1
+ Hi! I'm a new Spar application!
@@ -0,0 +1,8 @@
1
+ require 'bundler'
2
+ Bundler.require
3
+
4
+ task :environment do
5
+ require './config/application'
6
+ end
7
+
8
+ require 'spar/tasks'
@@ -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,7 @@
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
@@ -0,0 +1,9 @@
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
@@ -0,0 +1,12 @@
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
@@ -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
@@ -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
@@ -0,0 +1,3 @@
1
+ module Spar
2
+ VERSION = "0.2.7"
3
+ end
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: []