spar 0.2.7

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.
@@ -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: []