static_sync 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,13 +1,19 @@
1
1
  # StaticSync
2
2
 
3
- This gem provides a stand alone mechanism for uploading static websites to cloud hosting providers such as
4
- [Amazon AWS](http://en.wikipedia.org/wiki/Amazon_S3#Hosting_entire_websites).
3
+ This gem provides a command line tool for uploading static websites to amazon/rackspace.
5
4
 
6
5
  ### Features
7
6
 
8
- * Standalone.
9
- * Configurable caching.
10
- * Automatic gzip compression.
7
+ * Compares the md5 cheksum of each file before deciding to upload it.
8
+ * Automatically gzips non binary files.
9
+ * Sets appropriate HTTP metadata header information for serving html files.
10
+ * Content Type (text/css, image/png etc).
11
+ * Cache Control (public, max-age=31536000).
12
+ * Content Enconding (gzip).
13
+
14
+ ## Requirements
15
+
16
+ * Ruby 1.9
11
17
 
12
18
  ## Installation
13
19
 
@@ -15,23 +21,36 @@ This gem provides a stand alone mechanism for uploading static websites to cloud
15
21
  gem install static_sync
16
22
  ```
17
23
 
18
- ## Usage
24
+ ## Example Usage
19
25
 
20
26
  In your project directory create a `.static` file:
21
27
 
22
28
  ```
23
29
  > cat .static
30
+
31
+ # Source directory
24
32
  local:
25
- directory: build # The directory to upload
33
+ directory: build
26
34
 
35
+ # Target directory
27
36
  remote:
28
37
  provider: AWS
29
38
  username: my-aws-key
30
39
  password: my-aws-secret
31
40
  directory: my-aws-bucket
41
+
42
+ # Number of seconds to cache each content type, defaults to no cache.
43
+ cache:
44
+ html: 31536000
45
+ javascript: 31536000
46
+ css: 31536000
47
+ image: 31536000
48
+
49
+ # Flag to enable / disable automatic gzip compression.
50
+ gzip: true
32
51
  ```
33
52
 
34
- And run the following command any time you want to upload.
53
+ And simply run the following command any time you want to upload.
35
54
 
36
55
  ```bash
37
56
  static_sync
@@ -49,28 +68,6 @@ remote:
49
68
  directory: <%= ENV['s3_bucket'] %>
50
69
  ```
51
70
 
52
- ### Cache Control
53
-
54
- By default uploaded files are not cached.
55
-
56
- You can cache content for a given number of seconds by updating your `.static` file:
57
-
58
- ```
59
- cache:
60
- javascript: 31536000
61
- css: 31536000
62
- ```
63
-
64
- ### Compression
65
-
66
- By default uploaded files are not compressed.
67
-
68
- You can gzip all non binary content by updating your `.static` file:
69
-
70
- ```
71
- gzip: true
72
- ```
73
-
74
71
  ## Contributing
75
72
 
76
73
  1. Fork it
data/bin/static_sync CHANGED
@@ -1,7 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require "rubygems"
4
- require "static_sync"
3
+ require_relative "../lib/static_sync"
5
4
 
6
5
  StaticSync.upload
7
-
data/lib/static_sync.rb CHANGED
@@ -1,10 +1,3 @@
1
- require "rubygems"
2
- require "fog"
3
- require "erb"
4
- require "logger"
5
- require "tempfile"
6
- require "zlib"
7
-
8
1
  require_relative "static_sync/version"
9
2
  require_relative "static_sync/config"
10
3
  require_relative "static_sync/storage"
@@ -1,10 +1,14 @@
1
+ require "erb"
2
+
1
3
  module StaticSync
2
4
  class Config < Hash
3
5
 
4
- # TODO Validate.
6
+ def log
7
+ self.fetch('log', true)
8
+ end
5
9
 
6
10
  def cache
7
- self['cache'] || {}
11
+ self.fetch('cache', {})
8
12
  end
9
13
 
10
14
  def source
@@ -1,45 +1,42 @@
1
+ require "digest/md5"
2
+ require "mime/types"
3
+
4
+ require_relative "meta/caching"
5
+ require_relative "meta/compression"
6
+
1
7
  module StaticSync
2
8
  class Meta
3
9
 
4
10
  def initialize(config)
5
- @config = config
11
+ @config = config
12
+ @compression = Compression.new(@config)
13
+ @caching = Caching.new(@config)
6
14
  end
7
15
 
8
- #TODO This class / method has too many responsibilities.
9
16
  def for(file)
10
17
  meta = {
11
- :key => file,
12
- :body => File.new(file),
13
- :public => true
18
+ :key => file,
19
+ :body => File.new(file),
20
+ :public => true,
21
+ :storage_class => 'REDUCED_REDUNDANCY'
14
22
  }
15
- MIME::Types::of(file).each do |mime|
16
- if @config.gzip
17
- unless mime.binary?
18
- meta.merge!(
19
- :body => gzip(file),
20
- :content_encoding => 'gzip'
21
- )
22
- end
23
- end
23
+
24
+ mime = MIME::Types::of(file).first
25
+
26
+ if mime
24
27
  meta.merge!(
25
28
  :content_type => MIME::Type.simplified(mime)
26
29
  )
27
- if @config.cache.has_key?(mime.sub_type)
28
- expiry = @config.cache[mime.sub_type].to_i
29
- meta.merge!(
30
- :cache_control => "public, max-age=#{expiry}"
31
- )
32
- end
30
+ meta.merge!(@compression.for(file, mime))
31
+ meta.merge!(@caching.for(file, mime))
33
32
  end
34
- meta
35
- end
36
33
 
37
- def gzip(file)
38
- zipped = Tempfile.new("static_sync")
39
- Zlib::GzipWriter.open(zipped) do |archive|
40
- archive.write File.read(file)
41
- end
42
- zipped
34
+ body = meta[:body].read
35
+
36
+ meta.merge!(
37
+ :etag => Digest::MD5.hexdigest(body), # A local version of the etag expected after upload.
38
+ :content_md5 => Digest::MD5.base64digest(body) # Enable checksum validation during uploads.
39
+ )
43
40
  end
44
41
 
45
42
  end
@@ -0,0 +1,24 @@
1
+ module StaticSync
2
+ class Meta
3
+ class Caching
4
+
5
+ def initialize(config)
6
+ @config = config
7
+ end
8
+
9
+ def for(file, mime)
10
+ meta = {}
11
+ type = mime.sub_type
12
+ type = mime.media_type if mime.media_type == "image"
13
+ if @config.cache.has_key?(type)
14
+ expiry = @config.cache[type].to_i
15
+ meta.merge!(
16
+ :cache_control => "public, max-age=#{expiry}"
17
+ )
18
+ end
19
+ meta
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,38 @@
1
+ require "tempfile"
2
+ require "zlib"
3
+
4
+ module StaticSync
5
+ class Meta
6
+ class Compression
7
+
8
+ def initialize(config)
9
+ @config = config
10
+ end
11
+
12
+ def for(file, mime)
13
+ meta = {}
14
+ if @config.gzip && !mime.binary?
15
+ meta.merge!(
16
+ :body => gzip(file),
17
+ :content_encoding => 'gzip'
18
+ )
19
+ end
20
+ meta
21
+ end
22
+
23
+ private
24
+
25
+ def gzip(file)
26
+ zipped = Tempfile.new("static_sync")
27
+ Zlib::GzipWriter.open(zipped) do |archive|
28
+ # Gzip by default incorporates file creation time into the content.
29
+ # For the purpose of repeatable etag comparison we want avoid this.
30
+ archive.mtime = 1
31
+ archive.write File.read(file)
32
+ end
33
+ zipped
34
+ end
35
+
36
+ end
37
+ end
38
+ end
@@ -1,3 +1,6 @@
1
+ require "fog"
2
+ require "logger"
3
+
1
4
  require_relative "meta"
2
5
 
3
6
  module StaticSync
@@ -9,18 +12,40 @@ module StaticSync
9
12
  end
10
13
 
11
14
  def sync
15
+ remote_keys = []
16
+ remote_directory.files.each do |file|
17
+ remote_keys << [file.key, file.etag]
18
+ end
12
19
  Dir.chdir(@config.source) do
13
- Dir.glob("**/*.*") do |file|
14
- log.info("Uploading #{file}")
15
- @config.storage.directories.get(@config.storage_directory).files.create(
16
- @meta.for(file)
17
- )
20
+ local_files.each do |file|
21
+ current_file = @meta.for(file)
22
+ current_file_key = [current_file[:key], current_file[:etag]]
23
+
24
+ unless remote_keys.include?(current_file_key)
25
+ log.info("Uploading #{file}") if @config.log
26
+ begin
27
+ remote_directory.files.create(current_file)
28
+ rescue => error
29
+ log.error("Failed to upload #{file}")
30
+ raise error
31
+ end
32
+ end
18
33
  end
19
34
  end
20
35
  end
21
36
 
22
37
  private
23
38
 
39
+ def local_files
40
+ Dir.glob("**/*.*").reject do |file|
41
+ File.directory?(file)
42
+ end
43
+ end
44
+
45
+ def remote_directory
46
+ @config.storage.directories.new(:key => @config.storage_directory)
47
+ end
48
+
24
49
  def log
25
50
  @log ||= begin
26
51
  logger = Logger.new(STDOUT)
@@ -1,3 +1,3 @@
1
1
  module StaticSync
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
File without changes
File without changes
@@ -0,0 +1,75 @@
1
+ require "mime/types"
2
+ require "./lib/static_sync/config"
3
+ require "./lib/static_sync/meta/caching"
4
+
5
+ describe StaticSync::Meta::Caching do
6
+
7
+ let(:config) { StaticSync::Config.new }
8
+
9
+ let(:html) { MIME::Type.new('text/html') }
10
+ let(:plain) { MIME::Type.new('text/plain') }
11
+ let(:css) { MIME::Type.new('text/css') }
12
+ let(:gif) { MIME::Type.new('image/gif') }
13
+ let(:jpg) { MIME::Type.new('image/jpg') }
14
+ let(:png) { MIME::Type.new('image/png') }
15
+
16
+ subject do
17
+ StaticSync::Meta::Caching.new(config)
18
+ end
19
+
20
+ context "when caching is completely disabled" do
21
+
22
+ before do
23
+ config['cache'] = { }
24
+ end
25
+
26
+ describe "#for" do
27
+ it "does not set cache headers for html files" do
28
+ subject.for("index.html", html).should_not include(:cache_control)
29
+ end
30
+ end
31
+ end
32
+
33
+ context "when caching is enabled for html only" do
34
+
35
+ before do
36
+ config['cache'] = { 'html' => '86400' }
37
+ end
38
+
39
+ describe "#for" do
40
+ it "sets cache headers for html files" do
41
+ subject.for("index.html", html).should include(:cache_control)
42
+ end
43
+
44
+ it "does not set cache headers for text files" do
45
+ subject.for("data.txt", plain).should_not include(:cache_control)
46
+ end
47
+
48
+ it "does not set cache headers for css files" do
49
+ subject.for("screen.css", css).should_not include(:cache_control)
50
+ end
51
+ end
52
+ end
53
+
54
+ context "when caching is enabled for images" do
55
+
56
+ before do
57
+ config['cache'] = { 'image' => '86400' }
58
+ end
59
+
60
+ describe "#for" do
61
+ it "sets cache headers for gif files" do
62
+ subject.for("spinner.gif", gif).should include(:cache_control)
63
+ end
64
+
65
+ it "sets cache headers for jpg files" do
66
+ subject.for("kitten.jpg", jpg).should include(:cache_control)
67
+ end
68
+
69
+ it "sets cache headers for png files" do
70
+ subject.for("cat.png", png).should include(:cache_control)
71
+ end
72
+ end
73
+ end
74
+
75
+ end
@@ -0,0 +1,50 @@
1
+ require "mime/types"
2
+ require "./lib/static_sync/config"
3
+ require "./lib/static_sync/meta/compression"
4
+
5
+ describe StaticSync::Meta::Compression do
6
+
7
+ let(:config) { StaticSync::Config.new }
8
+
9
+ let(:html) { MIME::Type.new('text/html') }
10
+ let(:image) { MIME::Type.new('image/gif') }
11
+
12
+ subject do
13
+ StaticSync::Meta::Compression.new(config)
14
+ end
15
+
16
+ before do
17
+ File.stub(:read).and_return("")
18
+ end
19
+
20
+ context "when compression is disabled" do
21
+
22
+ before do
23
+ config['gzip'] = false
24
+ end
25
+
26
+ describe "#for" do
27
+ it "does not set compression headers for html files" do
28
+ subject.for("index.html", html).should_not include(:content_encoding => "gzip")
29
+ end
30
+ end
31
+ end
32
+
33
+ context "when compression is enabled" do
34
+
35
+ before do
36
+ config['gzip'] = true
37
+ end
38
+
39
+ describe "#for" do
40
+ it "sets compression headers for html files" do
41
+ subject.for("index.html", html).should include(:content_encoding => "gzip")
42
+ end
43
+
44
+ it "does not set compression headers for binary files" do
45
+ subject.for("assets/images/spinner.gif", image).should_not include(:content_encoding => "gzip")
46
+ end
47
+ end
48
+ end
49
+
50
+ end
data/spec/meta_spec.rb CHANGED
@@ -1,7 +1,3 @@
1
- require "cgi"
2
- require "mime/types"
3
- require "tempfile"
4
- require "zlib"
5
1
  require "./lib/static_sync/config"
6
2
  require "./lib/static_sync/meta"
7
3
 
@@ -13,6 +9,21 @@ describe StaticSync::Meta do
13
9
  StaticSync::Meta.new(config)
14
10
  end
15
11
 
12
+ it "sets files as public" do
13
+ Dir.chdir("spec/fixtures/site") do
14
+ subject.for("index.html").should include(:public => true)
15
+ end
16
+ end
17
+
18
+ it "sets appropriate content types for a range of common file types" do
19
+ Dir.chdir("spec/fixtures/site") do
20
+ subject.for("index.html").should include(:content_type => "text/html")
21
+ subject.for("assets/images/spinner.gif").should include(:content_type => "image/gif")
22
+ subject.for("assets/stylesheets/screen.css").should include(:content_type => "text/css")
23
+ subject.for("assets/javascripts/jquery.min.js").should include(:content_type => "application/javascript")
24
+ end
25
+ end
26
+
16
27
  describe "all files" do
17
28
 
18
29
  it "should be public" do
@@ -35,41 +46,5 @@ describe StaticSync::Meta do
35
46
  end
36
47
  end
37
48
 
38
- it "should cache files when requested" do
39
- config['cache'] = {
40
- 'css' => '86400',
41
- 'javascript' => '86400'
42
- }
43
- Dir.chdir("spec/fixtures/site") do
44
- subject.for("index.html").should_not include(
45
- :cache_control
46
- )
47
- subject.for("assets/stylesheets/screen.css").should include(
48
- :cache_control
49
- )
50
- subject.for("assets/javascripts/jquery.min.js").should include(
51
- :cache_control
52
- )
53
- end
54
- end
55
-
56
- it "should gzip files when requested" do
57
- Dir.chdir("spec/fixtures/site") do
58
- subject.for("index.html").should_not include(
59
- :content_encoding => 'gzip'
60
- )
61
- config['gzip'] = true
62
- subject.for("index.html").should include(
63
- :content_encoding => 'gzip'
64
- )
65
- subject.for("assets/stylesheets/screen.css").should include(
66
- :content_encoding => 'gzip'
67
- )
68
- subject.for("assets/javascripts/jquery.min.js").should include(
69
- :content_encoding => 'gzip'
70
- )
71
- end
72
- end
73
-
74
49
  end
75
50
  end
data/spec/storage_spec.rb CHANGED
@@ -1,9 +1,11 @@
1
- require "./lib/static_sync"
1
+ require "./lib/static_sync/config"
2
+ require "./lib/static_sync/storage"
2
3
 
3
4
  describe StaticSync::Storage do
4
5
 
5
6
  let(:config) do
6
7
  StaticSync::Config.new.merge({
8
+ 'log' => false,
7
9
  'local' => {
8
10
  'directory' => 'spec/fixtures/site'
9
11
  },
@@ -22,28 +24,39 @@ describe StaticSync::Storage do
22
24
 
23
25
  before do
24
26
  Fog.mock!
25
- config.storage.directories.create(
26
- :key => config.storage_directory,
27
- :public => true
28
- )
29
27
  end
30
28
 
31
29
  after do
32
30
  Fog::Mock.reset
33
31
  end
34
32
 
35
- describe "html files" do
33
+ context "with a remote storage directory" do
36
34
 
37
- it "are uploaded to the remote directory" do
38
- subject.sync
35
+ before do
36
+ config.storage.directories.create(
37
+ :key => config.storage_directory,
38
+ :public => true
39
+ )
40
+ end
41
+
42
+ context "syncing a new site" do
43
+
44
+ describe "#sync" do
39
45
 
40
- config.storage.directories.get(config.storage_directory).files.map(&:key).should == [
41
- "assets/javascripts/jquery.min.js",
42
- "assets/stylesheets/screen.css",
43
- "index.html"
44
- ]
46
+ it "sets a unique key for each uploaded file" do
47
+ subject.sync
48
+
49
+ config.storage.directories.get(config.storage_directory).files.map(&:key).should == [
50
+ "assets/images/spinner.gif",
51
+ "assets/javascripts/jquery.min.js",
52
+ "assets/stylesheets/screen.css",
53
+ "assets/stylesheets/screen.scss",
54
+ "cat.com/index.html",
55
+ "index.html"
56
+ ]
57
+ end
58
+ end
45
59
  end
46
60
 
47
61
  end
48
-
49
62
  end
data/static_sync.gemspec CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |gem|
8
8
  gem.version = StaticSync::VERSION
9
9
  gem.authors = ["Shanon McQuay"]
10
10
  gem.email = ["shanonmcquay@gmail.com"]
11
- gem.summary = %q{Uploads your static website to cloud storage.}
11
+ gem.summary = %q{Command line tool for uploading static websites to amazon/rackspace.}
12
12
 
13
13
  gem.files = `git ls-files`.split($/)
14
14
  gem.bindir = "bin"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: static_sync
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-01-18 00:00:00.000000000 Z
12
+ date: 2013-02-03 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: fog
@@ -60,11 +60,18 @@ files:
60
60
  - lib/static_sync.rb
61
61
  - lib/static_sync/config.rb
62
62
  - lib/static_sync/meta.rb
63
+ - lib/static_sync/meta/caching.rb
64
+ - lib/static_sync/meta/compression.rb
63
65
  - lib/static_sync/storage.rb
64
66
  - lib/static_sync/version.rb
67
+ - spec/fixtures/site/assets/images/spinner.gif
65
68
  - spec/fixtures/site/assets/javascripts/jquery.min.js
66
69
  - spec/fixtures/site/assets/stylesheets/screen.css
70
+ - spec/fixtures/site/assets/stylesheets/screen.scss
71
+ - spec/fixtures/site/cat.com/index.html
67
72
  - spec/fixtures/site/index.html
73
+ - spec/meta/caching_spec.rb
74
+ - spec/meta/compression_spec.rb
68
75
  - spec/meta_spec.rb
69
76
  - spec/storage_spec.rb
70
77
  - static_sync.gemspec
@@ -91,8 +98,9 @@ rubyforge_project:
91
98
  rubygems_version: 1.8.23
92
99
  signing_key:
93
100
  specification_version: 3
94
- summary: Uploads your static website to cloud storage.
101
+ summary: Command line tool for uploading static websites to amazon/rackspace.
95
102
  test_files:
103
+ - spec/meta/caching_spec.rb
104
+ - spec/meta/compression_spec.rb
96
105
  - spec/meta_spec.rb
97
106
  - spec/storage_spec.rb
98
- has_rdoc: false