statica 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,6 @@
1
+ Gemfile.lock
2
+
3
+ *.swp
4
+
5
+ pkg
6
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+
3
+ gemspec
@@ -0,0 +1,41 @@
1
+ # statica
2
+
3
+ Statica is a static asset caching solution.
4
+
5
+ # Note
6
+
7
+ Not quite ready for production use, but not far off either. I will remove this notice when I feel it's production ready.
8
+
9
+ TODO:
10
+
11
+ * Keep track of assets Statica already has the digest for [production setting]
12
+ * Finish the README
13
+ * More tests
14
+
15
+ # Synopsis
16
+
17
+ The goal is to achieve the simplicity of appending a query parameter and the precision offered by implementing a per file digest system.
18
+
19
+ In order to achieve this, Statica takes the simple approach of appendin the hex digest to the end of the static asset path like so:
20
+
21
+ > Statica.digest_url("/javascripts/application.js")
22
+ =>"/javascripts/application.js/d79dbf6e6c051b22f1ab91fec4cf81855883cc7d0a3f3e242b56b97fbcd756cb"
23
+
24
+ Notice how the file name does not change. We are simply appending a qualifier to the resource for caching. In order to serve the asset, Statica comes with Rack middleware that simply removes the digest portion allowing your application to serve the asset as normal.
25
+
26
+ # If Statica path, remove the digest portion, allow app to serve normally
27
+ use Rack::StaticaServer
28
+
29
+ If you want an HTTP Server like [Apache](http://httpd.apache.org/) or [Nginx](http://wiki.nginx.org/Main) you can setup rules to ignore the digest and server the asset. I'll sample rules here later.
30
+
31
+ # About
32
+
33
+ More to come...
34
+
35
+ # Acknowledgement
36
+
37
+ Huge thanks to my company, [Primedia](http://primedia.com) for encouraging open source contributions.
38
+
39
+ # Contributors
40
+
41
+ I highly value contributions and will happily list all those who submit accepted pull requests.
@@ -0,0 +1,46 @@
1
+ begin
2
+ require 'bundler'
3
+ Bundler::GemHelper.install_tasks
4
+ rescue Exception => e
5
+ end
6
+
7
+ require 'rake/testtask'
8
+
9
+ Rake::TestTask.new('test') do |t|
10
+ t.libs << 'lib' << 'test'
11
+ t.test_files = Dir.glob('test/**/test_*.rb')
12
+ t.verbose = true
13
+ end
14
+
15
+ task 'test:ci' do |t|
16
+ Rake::Task[ENV['TASK']].execute
17
+ end
18
+
19
+ begin
20
+ require 'rcov/rcovtask'
21
+ Rcov::RcovTask.new do |t|
22
+ t.libs << 'lib' << 'test'
23
+ t.test_files = Dir.glob('test/**/test_*.rb')
24
+ t.verbose = true
25
+ end
26
+ rescue LoadError
27
+ task :rcov do
28
+ abort "RCov is not available. In order to run rcov, you must: gem install rcov"
29
+ end
30
+ end
31
+
32
+ begin
33
+ require 'yard'
34
+ YARD::Rake::YardocTask.new do |t|
35
+ t.files = %w(lib/**/*.rb)
36
+ end
37
+ rescue LoadError
38
+ task :yard do
39
+ abort "YARD is not available. In order to run yard, you must: gem install yard"
40
+ end
41
+ end
42
+
43
+ desc "Generate Documentation"
44
+ task :doc => :yard
45
+
46
+ task :default => 'test'
@@ -0,0 +1,20 @@
1
+ require 'rack'
2
+ require 'statica'
3
+
4
+ module Rack
5
+ class StaticaServer
6
+ def initialize(app)
7
+ @app = app
8
+ end
9
+
10
+ def call(env)
11
+ dup._call(env)
12
+ end
13
+
14
+ def _call(env)
15
+ env['PATH_INFO'] = Statica.resolve(env['PATH_INFO'])
16
+
17
+ @app.call(env)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,49 @@
1
+ require 'digest/sha2'
2
+
3
+ module Statica
4
+ class ResourceNotFoundError < StandardError; end
5
+
6
+ extend self
7
+
8
+ # Fully qualified path to the root directory
9
+ attr_accessor :root_dir
10
+
11
+ def digest_class=(klass)
12
+ @digest = klass
13
+ end
14
+
15
+ def digest
16
+ (@digest || ::Digest::SHA256).new
17
+ end
18
+
19
+ def sub_dirs
20
+ @sub_dirs ||= %w{javascripts stylesheets images}
21
+ end
22
+
23
+ def sub_dirs=(array)
24
+ @sub_dirs = array
25
+ end
26
+
27
+ def digest_for(path)
28
+ path.chop! if path.end_with?("/")
29
+ fname = File.join(root_dir, path)
30
+
31
+ raise ResourceNotFoundError, "#{fname} not found!" unless File.exist?(fname)
32
+
33
+ digest.file(fname).hexdigest
34
+ end
35
+
36
+ def digest_url(url)
37
+ "#{url}/#{digest_for(url)}"
38
+ end
39
+
40
+ def resolve(path)
41
+ (path =~ regex) ? path.sub($2, '') : path
42
+ end
43
+
44
+ def regex
45
+ leaders = sub_dirs.collect{|t| Regexp.escape(t)}.join("|")
46
+ @regex ||= /\A\/?(#{leaders})\S*(\/[a-z0-9]+\/?)\Z/
47
+ end
48
+
49
+ end
@@ -0,0 +1,5 @@
1
+ module Statica
2
+ # Statica version string
3
+ # @api public
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.dirname(__FILE__) + '/lib/statica/version'
3
+ require 'date'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'statica'
7
+ s.version = Statica::VERSION
8
+ s.date = Date.today.to_s
9
+ s.authors = ['Andrew Stone']
10
+ s.email = ['andy@stonean.com']
11
+ s.summary = 'Statica is a static asset caching solution.'
12
+ s.homepage = 'http://github.com/stonean/statica'
13
+ s.extra_rdoc_files = %w(README.md)
14
+ s.rdoc_options = %w(--charset=UTF-8)
15
+ s.rubyforge_project = s.name
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = %w(lib)
20
+ end
@@ -0,0 +1,52 @@
1
+ require 'minitest/unit'
2
+ require 'statica'
3
+
4
+ MiniTest::Unit.autorun
5
+
6
+ module R
7
+ extend self
8
+
9
+ def tmp_dir
10
+ File.join(File.dirname(__FILE__), '..', 'tmp')
11
+ end
12
+
13
+ def maybe_mkdir(dir)
14
+ Dir.mkdir(dir) unless Dir.exist?(dir)
15
+ end
16
+
17
+ def test_file(name = 'test.txt')
18
+ out_dir = tmp_dir
19
+
20
+ maybe_mkdir(out_dir)
21
+
22
+ parts = name.split('/').delete_if{ |a| a.empty? }
23
+ name = parts.pop
24
+
25
+ parts.each do |subdir|
26
+ out_dir = File.join(out_dir, subdir)
27
+ maybe_mkdir(out_dir)
28
+ end
29
+
30
+ fname = File.join(out_dir, name)
31
+ f = File.open( fname, 'w')
32
+
33
+ letters = [('a'..'z'),('A'..'Z')].map{|i| i.to_a}.flatten
34
+ contents = []
35
+ (0..rand(1000)).each do
36
+ contents << (0..rand(12)).map{ letters[rand(letters.length)] }.join
37
+ end
38
+
39
+ f.write contents.join(' ')
40
+ f.flush
41
+ f.close
42
+
43
+ yield f
44
+ end
45
+ end
46
+
47
+
48
+ class TestBase < MiniTest::Unit::TestCase
49
+ def setup
50
+ Statica.root_dir = R.tmp_dir
51
+ end
52
+ end
@@ -0,0 +1,67 @@
1
+ require 'helper'
2
+
3
+ class TestStatica < TestBase
4
+
5
+ def test_digest_for_raises_error_on_resource_not_found
6
+ assert_raises Statica::ResourceNotFoundError do
7
+ Statica.digest_for('/javascripts/foo.js')
8
+ end
9
+ end
10
+
11
+ def test_digests_are_unique
12
+ digest1 = ''
13
+ digest2 = ''
14
+
15
+ path = "/javascripts/application.js"
16
+
17
+ R.test_file(path){ |f| digest1 = Statica.digest_for(path) }
18
+ R.test_file(path){ |f| digest2 = Statica.digest_for(path) }
19
+
20
+ refute_equal digest1, digest2
21
+ end
22
+
23
+ def test_digest_path_format
24
+ url = "/javascripts/application.js"
25
+ digest = ""
26
+ buster = ""
27
+
28
+ R.test_file(url) do |f|
29
+ digest = Statica.digest_for(url)
30
+ buster = Statica.digest_url(url)
31
+ end
32
+
33
+ assert_equal "#{url}/#{digest}", buster
34
+ end
35
+
36
+ def test_resolve_on_non_match
37
+ url = "/js/application.js"
38
+ assert_equal url, Statica.resolve(url)
39
+ end
40
+
41
+ def test_resolve_match_no_digest
42
+ url = "/javascripts/application.js"
43
+ assert_equal url, Statica.resolve(url)
44
+ end
45
+
46
+ def test_resolve_match_no_digest_no_leading_slash
47
+ url = "javascripts/application.js"
48
+ assert_equal url, Statica.resolve(url)
49
+ end
50
+
51
+ def test_resolve
52
+ urls = %w{
53
+ javascripts/application.js
54
+ /javascripts/third-party.js/
55
+ /images/home/logo.png
56
+ /stylesheets/CrazyStylz.css
57
+ /stylesheets/CrazyStylz.css
58
+ /images/my_precious.gif
59
+ }
60
+
61
+ urls.each do |url|
62
+ R.test_file(url) do |f|
63
+ assert_equal url, Statica.resolve( Statica.digest_url(url) )
64
+ end
65
+ end
66
+ end
67
+ end
metadata ADDED
@@ -0,0 +1,57 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: statica
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Andrew Stone
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-12-07 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description:
15
+ email:
16
+ - andy@stonean.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files:
20
+ - README.md
21
+ files:
22
+ - .gitignore
23
+ - Gemfile
24
+ - README.md
25
+ - Rakefile
26
+ - lib/rack/statica_server.rb
27
+ - lib/statica.rb
28
+ - lib/statica/version.rb
29
+ - statica.gemspec
30
+ - test/helper.rb
31
+ - test/test_statica.rb
32
+ homepage: http://github.com/stonean/statica
33
+ licenses: []
34
+ post_install_message:
35
+ rdoc_options:
36
+ - --charset=UTF-8
37
+ require_paths:
38
+ - lib
39
+ required_ruby_version: !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ required_rubygems_version: !ruby/object:Gem::Requirement
46
+ none: false
47
+ requirements:
48
+ - - ! '>='
49
+ - !ruby/object:Gem::Version
50
+ version: '0'
51
+ requirements: []
52
+ rubyforge_project: statica
53
+ rubygems_version: 1.8.10
54
+ signing_key:
55
+ specification_version: 3
56
+ summary: Statica is a static asset caching solution.
57
+ test_files: []