vacation 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +12 -0
- data/.rvmrc +1 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +51 -0
- data/README.md +66 -0
- data/Rakefile +21 -0
- data/bin/vacation +70 -0
- data/lib/tasks/deploy.rake +16 -0
- data/lib/vacation.rb +10 -0
- data/lib/vacation/jekyll.rb +29 -0
- data/lib/vacation/s3.rb +76 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/support/jekyll/_layouts/blog.html +13 -0
- data/spec/support/jekyll/_layouts/default.html +11 -0
- data/spec/support/jekyll/_posts/2011-03-04-i-am-wearing-ramen.markdown +8 -0
- data/spec/support/jekyll/_posts/2011-03-05-about-last-night.markdown +8 -0
- data/spec/support/jekyll/images/geocities.png +0 -0
- data/spec/support/jekyll/index.html +13 -0
- data/spec/support/jekyll/stylesheets/reset.css +260 -0
- data/spec/unit/jekyll_spec.rb +50 -0
- data/spec/unit/s3_spec.rb +89 -0
- data/vacation.gemspec +27 -0
- metadata +132 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use --create ruby-1.8.7@vacation
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
builder (3.0.0)
|
5
|
+
classifier (1.3.3)
|
6
|
+
fast-stemmer (>= 1.0.0)
|
7
|
+
diff-lcs (1.1.2)
|
8
|
+
directory_watcher (1.3.2)
|
9
|
+
excon (0.5.6)
|
10
|
+
fast-stemmer (1.0.0)
|
11
|
+
fog (0.6.0)
|
12
|
+
builder
|
13
|
+
excon (>= 0.5.5)
|
14
|
+
formatador (>= 0.0.16)
|
15
|
+
json
|
16
|
+
mime-types
|
17
|
+
net-ssh (>= 2.0.23)
|
18
|
+
nokogiri (>= 1.4.4)
|
19
|
+
ruby-hmac
|
20
|
+
formatador (0.0.16)
|
21
|
+
jekyll (0.10.0)
|
22
|
+
classifier (>= 1.3.1)
|
23
|
+
directory_watcher (>= 1.1.1)
|
24
|
+
liquid (>= 1.9.0)
|
25
|
+
maruku (>= 0.5.9)
|
26
|
+
json (1.5.1)
|
27
|
+
liquid (2.2.2)
|
28
|
+
maruku (0.6.0)
|
29
|
+
syntax (>= 1.0.0)
|
30
|
+
mime-types (1.16)
|
31
|
+
net-ssh (2.1.3)
|
32
|
+
nokogiri (1.4.4)
|
33
|
+
rspec (2.5.0)
|
34
|
+
rspec-core (~> 2.5.0)
|
35
|
+
rspec-expectations (~> 2.5.0)
|
36
|
+
rspec-mocks (~> 2.5.0)
|
37
|
+
rspec-core (2.5.1)
|
38
|
+
rspec-expectations (2.5.0)
|
39
|
+
diff-lcs (~> 1.1.2)
|
40
|
+
rspec-mocks (2.5.0)
|
41
|
+
ruby-hmac (0.4.0)
|
42
|
+
syntax (1.0.0)
|
43
|
+
|
44
|
+
PLATFORMS
|
45
|
+
ruby
|
46
|
+
|
47
|
+
DEPENDENCIES
|
48
|
+
fog
|
49
|
+
jekyll
|
50
|
+
nokogiri
|
51
|
+
rspec
|
data/README.md
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
# Vacation
|
2
|
+
|
3
|
+
Vacation lets you host Jekyll sites on S3. It is a gem. You add it via `gem install vacation`.
|
4
|
+
|
5
|
+
Then you take a vacation.
|
6
|
+
|
7
|
+
## Creating a Jekyll site
|
8
|
+
|
9
|
+
Make site.
|
10
|
+
|
11
|
+
## Identify your AWS access credentials
|
12
|
+
|
13
|
+
These should look something like the ones you get from Amazon. Not Rackspace.
|
14
|
+
|
15
|
+
Full `~/.fog` support will land in a bit.
|
16
|
+
|
17
|
+
Then identify the name of the bucket you want to hork things into.
|
18
|
+
|
19
|
+
## Backups
|
20
|
+
|
21
|
+
Vacation destructively overwrites the contents of the target bucket for now.
|
22
|
+
Previous content is (optionally, if you use the `vacation` executable) backed
|
23
|
+
up to a secondary bucket.
|
24
|
+
|
25
|
+
If you deploy to `bucket_name` bucket is called `bucket_name`-vacation-backup.
|
26
|
+
|
27
|
+
## Deploying
|
28
|
+
|
29
|
+
Vacation gets invoked via a command line executable script, also called `vacation`!
|
30
|
+
|
31
|
+
Vacation is a way of deploying Jekyll sites to S3.
|
32
|
+
|
33
|
+
Basic Command Line Usage:
|
34
|
+
vacation <bucket name>
|
35
|
+
vacation <bucket name> <path to source>
|
36
|
+
|
37
|
+
Your AWS information can be read from the environment as
|
38
|
+
AWS_ID and AWS_KEY or using the following variables:
|
39
|
+
|
40
|
+
--[no-]backup Back up the contents of the destination bucket
|
41
|
+
--id [ID] Your AWS access key id
|
42
|
+
--key [KEY] Your AWS secret key
|
43
|
+
--version Display current version
|
44
|
+
|
45
|
+
|
46
|
+
## MIT License
|
47
|
+
|
48
|
+
Copyright (c) 2011 Eric "Doc" Ritezel
|
49
|
+
|
50
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
51
|
+
of this software and associated documentation files (the 'Software'), to deal
|
52
|
+
in the Software without restriction, including without limitation the rights
|
53
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
54
|
+
copies of the Software, and to permit persons to whom the Software is
|
55
|
+
furnished to do so, subject to the following conditions:
|
56
|
+
|
57
|
+
The above copyright notice and this permission notice shall be included in all
|
58
|
+
copies or substantial portions of the Software.
|
59
|
+
|
60
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
61
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
62
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
63
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
64
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
65
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
66
|
+
SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'rspec/core/rake_task'
|
2
|
+
import 'lib/tasks/deploy.rake'
|
3
|
+
|
4
|
+
desc "Run all examples"
|
5
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
6
|
+
t.rspec_path = 'rspec'
|
7
|
+
t.rspec_opts = %w[--color]
|
8
|
+
t.verbose = false
|
9
|
+
end
|
10
|
+
|
11
|
+
namespace :hammer do
|
12
|
+
require 'vacation'
|
13
|
+
desc "Fire a given S3 bucket out of a cannon into the sun"
|
14
|
+
task :drop, :aws_access_id, :aws_secret_key, :s3_bucket do |t, args|
|
15
|
+
s3 = Vacation::S3.new(args[:aws_access_id], args[:aws_secret_key], args[:s3_bucket])
|
16
|
+
s3.strip_bucket
|
17
|
+
s3.bucket.destroy
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
task :default => [:spec]
|
data/bin/vacation
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$:.unshift File.expand_path('../../lib', __FILE__)
|
4
|
+
|
5
|
+
help = <<HELP
|
6
|
+
Vacation is a way of deploying Jekyll sites to S3.
|
7
|
+
|
8
|
+
Basic Command Line Usage:
|
9
|
+
vacation <bucket name>
|
10
|
+
vacation <bucket name> <path to source>
|
11
|
+
|
12
|
+
Your AWS information can be read from the environment as
|
13
|
+
AWS_ID and AWS_KEY or using the following variables:
|
14
|
+
|
15
|
+
HELP
|
16
|
+
|
17
|
+
require 'optparse'
|
18
|
+
require 'vacation'
|
19
|
+
|
20
|
+
options = { 'backup' => true }
|
21
|
+
opts = OptionParser.new do |opts|
|
22
|
+
opts.banner = help
|
23
|
+
|
24
|
+
opts.on("--[no-]backup", "Back up the contents of the destination bucket") do |backup|
|
25
|
+
options['backup'] = backup
|
26
|
+
end
|
27
|
+
|
28
|
+
opts.on("--id [ID]", "Your AWS access key id") do |id|
|
29
|
+
options['access_id'] = id
|
30
|
+
end
|
31
|
+
|
32
|
+
opts.on("--key [KEY]", "Your AWS secret key") do |secret_key|
|
33
|
+
options['secret_key'] = secret_key
|
34
|
+
end
|
35
|
+
|
36
|
+
opts.on("--version", "Display current version") do
|
37
|
+
puts "Vacation " + Vacation::VERSION
|
38
|
+
exit 0
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
opts.parse!
|
43
|
+
|
44
|
+
# Get source and destintation from command line
|
45
|
+
case ARGV.size
|
46
|
+
when 1
|
47
|
+
options['bucket'] = ARGV[0]
|
48
|
+
when 2
|
49
|
+
options['bucket'] = ARGV[0]
|
50
|
+
options['source'] = ARGV[1]
|
51
|
+
else
|
52
|
+
puts "Invalid options. Run `vacation --help` for assistance."
|
53
|
+
exit(1)
|
54
|
+
end
|
55
|
+
|
56
|
+
options['access_id'] ||= ENV['AWS_ID']
|
57
|
+
options['secret_key'] ||= ENV['AWS_KEY']
|
58
|
+
|
59
|
+
unless options['access_id'] && options['secret_key']
|
60
|
+
puts "AWS credentials not found. Run `vacation --help` for assistance."
|
61
|
+
exit(1)
|
62
|
+
end
|
63
|
+
|
64
|
+
Dir.mktmpdir do |temp_dir|
|
65
|
+
jekyll = Vacation::Jekyll.new(options['source'])
|
66
|
+
jekyll.compile_to(temp_dir)
|
67
|
+
|
68
|
+
s3 = Vacation::S3.new(options['access_id'], options['secret_key'], options['bucket'])
|
69
|
+
s3.deploy_to_bucket(temp_dir, :backup => options['backup'])
|
70
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require File.expand_path('../../vacation', __FILE__)
|
2
|
+
|
3
|
+
namespace :vacation do
|
4
|
+
require 'tmpdir'
|
5
|
+
|
6
|
+
desc 'Compile and deploy a Jekyll directory to S3'
|
7
|
+
task :deploy, :aws_access_id, :aws_secret_key, :s3_bucket, :target do |t, args|
|
8
|
+
Dir.mktmpdir do |temp_dir|
|
9
|
+
jekyll = Vacation::Jekyll.new(args[:target])
|
10
|
+
jekyll.compile_to(temp_dir)
|
11
|
+
|
12
|
+
s3 = Vacation::S3.new(args[:aws_access_id], args[:aws_secret_key], args[:s3_bucket])
|
13
|
+
s3.deploy_to_bucket(temp_dir)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/vacation.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'jekyll'
|
2
|
+
|
3
|
+
module Vacation
|
4
|
+
class Jekyll
|
5
|
+
attr_accessor :source
|
6
|
+
|
7
|
+
def initialize(source)
|
8
|
+
@source = source
|
9
|
+
throw StandardError unless File::readable_real?(source) and File::directory?(source)
|
10
|
+
end
|
11
|
+
|
12
|
+
def config_hash
|
13
|
+
config_file = File.join(source, '_config.yml')
|
14
|
+
begin
|
15
|
+
config = YAML.load_file(config_file)
|
16
|
+
rescue => err
|
17
|
+
config = {}
|
18
|
+
end
|
19
|
+
|
20
|
+
::Jekyll::DEFAULTS.deep_merge(config).deep_merge('source' => source)
|
21
|
+
end
|
22
|
+
|
23
|
+
def compile_to(destination, options = {})
|
24
|
+
options = config_hash.deep_merge('destination' => destination).deep_merge(options)
|
25
|
+
site = ::Jekyll::Site.new options
|
26
|
+
site.process
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/vacation/s3.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'fog'
|
2
|
+
require 'tmpdir'
|
3
|
+
|
4
|
+
module Vacation
|
5
|
+
class S3
|
6
|
+
attr_reader :access_key_id, :secret_access_key, :storage, :bucket_name
|
7
|
+
|
8
|
+
def initialize(id, key, bucket)
|
9
|
+
@access_key_id = id
|
10
|
+
@secret_access_key = key
|
11
|
+
@bucket_name = bucket
|
12
|
+
@storage = Fog::Storage.new(
|
13
|
+
:provider => 'AWS',
|
14
|
+
:aws_access_key_id => access_key_id,
|
15
|
+
:aws_secret_access_key => secret_access_key
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
def bucket
|
20
|
+
@_bucket ||= storage.directories.create(:key => bucket_name, :privacy => 'public-read')
|
21
|
+
end
|
22
|
+
|
23
|
+
def backup_bucket
|
24
|
+
@_backup_bucket ||= storage.directories.create(:key => "#{bucket_name}-vacation-backup",
|
25
|
+
:privacy => 'private')
|
26
|
+
end
|
27
|
+
|
28
|
+
def strip_bucket
|
29
|
+
bucket.files.reload.each { |file| file.destroy }
|
30
|
+
bucket.files.reload
|
31
|
+
end
|
32
|
+
|
33
|
+
def download_from_bucket(path)
|
34
|
+
return unless storage.directories.get(bucket_name)
|
35
|
+
FileUtils.mkdir_p(path)
|
36
|
+
|
37
|
+
bucket.files.each do |file|
|
38
|
+
location = File.join(path, file.key)
|
39
|
+
FileUtils.mkdir_p(File.dirname(location))
|
40
|
+
File.open(location, 'w') do |local|
|
41
|
+
local.write(file.body)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def upload_to_bucket(path, is_backup = false)
|
47
|
+
return unless File.exists? path
|
48
|
+
|
49
|
+
strip_bucket unless is_backup
|
50
|
+
target = is_backup ? backup_bucket : bucket
|
51
|
+
|
52
|
+
Dir[File.join(path, '**', '*')].each do |file|
|
53
|
+
relative_path = file[path.length + 1..-1]
|
54
|
+
target.files.create(:key => relative_path, :body => File.read(file)) if File.file?(file)
|
55
|
+
end
|
56
|
+
|
57
|
+
target.files.reload
|
58
|
+
end
|
59
|
+
|
60
|
+
def deploy_to_bucket(path, options = { :backup => true })
|
61
|
+
if options[:backup] && options[:backup] == true
|
62
|
+
Dir.mktmpdir do |temp_dir|
|
63
|
+
backup_dir = File.expand_path(Time.now.strftime("%Y-%m-%d@%H:%M:%S(%Z)"), temp_dir)
|
64
|
+
FileUtils.mkdir backup_dir
|
65
|
+
|
66
|
+
download_from_bucket backup_dir
|
67
|
+
upload_to_bucket temp_dir, true
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
upload_to_bucket path
|
72
|
+
|
73
|
+
storage.put_bucket_website bucket_name, 'index.html', :key => '404.html'
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
lib = File.expand_path('../lib/', __FILE__)
|
2
|
+
$:.unshift lib unless $:.include?(lib)
|
3
|
+
|
4
|
+
require 'vacation'
|
5
|
+
require 'rspec'
|
6
|
+
require 'tmpdir'
|
7
|
+
require 'yaml'
|
8
|
+
require 'nokogiri'
|
9
|
+
|
10
|
+
def fake_jekyll
|
11
|
+
File.expand_path('../support/jekyll', __FILE__)
|
12
|
+
end
|
13
|
+
|
14
|
+
def aws_creds
|
15
|
+
YAML.load(File.expand_path('../support/credentials.yml', __FILE__))['aws']
|
16
|
+
end
|
17
|
+
|
18
|
+
RSpec.configure do |c|
|
19
|
+
c.mock_with :rspec
|
20
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<link rel="stylesheet" href="/stylesheets/reset.css" type="text/css" />
|
5
|
+
<title>{{ page.title }}</title>
|
6
|
+
</head>
|
7
|
+
<body>
|
8
|
+
<h1>{{ page.title }}</h1>
|
9
|
+
<h2>{{ page.synopsis }}</h2>
|
10
|
+
{{ content }}
|
11
|
+
<h3 class="posted">Posted by {{ page.author }} on {{ page.date | date_to_long_string }}</h3>
|
12
|
+
</body>
|
13
|
+
</html>
|
Binary file
|
@@ -0,0 +1,13 @@
|
|
1
|
+
---
|
2
|
+
layout: default
|
3
|
+
title: Index
|
4
|
+
---
|
5
|
+
|
6
|
+
<h2>Bloggage</h2>
|
7
|
+
<img src="/images/geocities.png" class="lulz" />
|
8
|
+
|
9
|
+
{% for post in site.posts limit:5 %}
|
10
|
+
<h3 class="chunk"><a href=".{{ post.url }}">{{ post.title }}</a></h3>
|
11
|
+
<q>{{ post.synopsis }}</q>
|
12
|
+
<h4>Posted by {{ post.author }} on {{ post.date | date_to_long_string }}.</h4>
|
13
|
+
{% endfor %}
|
@@ -0,0 +1,260 @@
|
|
1
|
+
/**
|
2
|
+
* HTML5 ✰ Boilerplate
|
3
|
+
*
|
4
|
+
* style.css contains a reset, font normalization and some base styles.
|
5
|
+
*
|
6
|
+
* Credit is left where credit is due.
|
7
|
+
* Much inspiration was taken from these projects:
|
8
|
+
* - yui.yahooapis.com/2.8.1/build/base/base.css
|
9
|
+
* - camendesign.com/design/
|
10
|
+
* - praegnanz.de/weblog/htmlcssjs-kickstart
|
11
|
+
*/
|
12
|
+
|
13
|
+
|
14
|
+
/**
|
15
|
+
* html5doctor.com Reset Stylesheet (Eric Meyer's Reset Reloaded + HTML5 baseline)
|
16
|
+
* v1.6.1 2010-09-17 | Authors: Eric Meyer & Richard Clark
|
17
|
+
* html5doctor.com/html-5-reset-stylesheet/
|
18
|
+
*/
|
19
|
+
|
20
|
+
html, body, div, span, object, iframe,
|
21
|
+
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
22
|
+
abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp,
|
23
|
+
small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li,
|
24
|
+
fieldset, form, label, legend,
|
25
|
+
table, caption, tbody, tfoot, thead, tr, th, td,
|
26
|
+
article, aside, canvas, details, figcaption, figure,
|
27
|
+
footer, header, hgroup, menu, nav, section, summary,
|
28
|
+
time, mark, audio, video {
|
29
|
+
margin: 0;
|
30
|
+
padding: 0;
|
31
|
+
border: 0;
|
32
|
+
font-size: 100%;
|
33
|
+
font: inherit;
|
34
|
+
vertical-align: baseline;
|
35
|
+
}
|
36
|
+
|
37
|
+
article, aside, details, figcaption, figure,
|
38
|
+
footer, header, hgroup, menu, nav, section {
|
39
|
+
display: block;
|
40
|
+
}
|
41
|
+
|
42
|
+
blockquote, q { quotes: none; }
|
43
|
+
|
44
|
+
blockquote:before, blockquote:after,
|
45
|
+
q:before, q:after { content: ''; content: none; }
|
46
|
+
|
47
|
+
ins { background-color: #ff9; color: #000; text-decoration: none; }
|
48
|
+
|
49
|
+
mark { background-color: #ff9; color: #000; font-style: italic; font-weight: bold; }
|
50
|
+
|
51
|
+
del { text-decoration: line-through; }
|
52
|
+
|
53
|
+
abbr[title], dfn[title] { border-bottom: 1px dotted; cursor: help; }
|
54
|
+
|
55
|
+
table { border-collapse: collapse; border-spacing: 0; }
|
56
|
+
|
57
|
+
hr { display: block; height: 1px; border: 0; border-top: 1px solid #ccc; margin: 1em 0; padding: 0; }
|
58
|
+
|
59
|
+
input, select { vertical-align: middle; }
|
60
|
+
|
61
|
+
|
62
|
+
/**
|
63
|
+
* Font normalization inspired by YUI Library's fonts.css: developer.yahoo.com/yui/
|
64
|
+
*/
|
65
|
+
|
66
|
+
body { font:13px/1.231 sans-serif; *font-size:small; } /* Hack retained to preserve specificity */
|
67
|
+
select, input, textarea, button { font:99% sans-serif; }
|
68
|
+
|
69
|
+
/* Normalize monospace sizing:
|
70
|
+
en.wikipedia.org/wiki/MediaWiki_talk:Common.css/Archive_11#Teletype_style_fix_for_Chrome */
|
71
|
+
pre, code, kbd, samp { font-family: monospace, sans-serif; }
|
72
|
+
|
73
|
+
|
74
|
+
/**
|
75
|
+
* Minimal base styles.
|
76
|
+
*/
|
77
|
+
|
78
|
+
/* Always force a scrollbar in non-IE */
|
79
|
+
html { overflow-y: scroll; }
|
80
|
+
|
81
|
+
/* Accessible focus treatment: people.opera.com/patrickl/experiments/keyboard/test */
|
82
|
+
a:hover, a:active { outline: none; }
|
83
|
+
|
84
|
+
ul, ol { margin-left: 2em; }
|
85
|
+
ol { list-style-type: decimal; }
|
86
|
+
|
87
|
+
/* Remove margins for navigation lists */
|
88
|
+
nav ul, nav li { margin: 0; list-style:none; list-style-image: none; }
|
89
|
+
|
90
|
+
small { font-size: 85%; }
|
91
|
+
strong, th { font-weight: bold; }
|
92
|
+
|
93
|
+
td { vertical-align: top; }
|
94
|
+
|
95
|
+
/* Set sub, sup without affecting line-height: gist.github.com/413930 */
|
96
|
+
sub, sup { font-size: 75%; line-height: 0; position: relative; }
|
97
|
+
sup { top: -0.5em; }
|
98
|
+
sub { bottom: -0.25em; }
|
99
|
+
|
100
|
+
pre {
|
101
|
+
/* www.pathf.com/blogs/2008/05/formatting-quoted-code-in-blog-posts-css21-white-space-pre-wrap/ */
|
102
|
+
white-space: pre; white-space: pre-wrap; word-wrap: break-word;
|
103
|
+
padding: 15px;
|
104
|
+
}
|
105
|
+
|
106
|
+
textarea { overflow: auto; } /* www.sitepoint.com/blogs/2010/08/20/ie-remove-textarea-scrollbars/ */
|
107
|
+
|
108
|
+
.ie6 legend, .ie7 legend { margin-left: -7px; }
|
109
|
+
|
110
|
+
/* Align checkboxes, radios, text inputs with their label by: Thierry Koblentz tjkdesign.com/ez-css/css/base.css */
|
111
|
+
input[type="radio"] { vertical-align: text-bottom; }
|
112
|
+
input[type="checkbox"] { vertical-align: bottom; }
|
113
|
+
.ie7 input[type="checkbox"] { vertical-align: baseline; }
|
114
|
+
.ie6 input { vertical-align: text-bottom; }
|
115
|
+
|
116
|
+
/* Hand cursor on clickable input elements */
|
117
|
+
label, input[type="button"], input[type="submit"], input[type="image"], button { cursor: pointer; }
|
118
|
+
|
119
|
+
/* Webkit browsers add a 2px margin outside the chrome of form elements */
|
120
|
+
button, input, select, textarea { margin: 0; }
|
121
|
+
|
122
|
+
/* Colors for form validity */
|
123
|
+
input:valid, textarea:valid { }
|
124
|
+
input:invalid, textarea:invalid {
|
125
|
+
border-radius: 1px; -moz-box-shadow: 0px 0px 5px red; -webkit-box-shadow: 0px 0px 5px red; box-shadow: 0px 0px 5px red;
|
126
|
+
}
|
127
|
+
.no-boxshadow input:invalid, .no-boxshadow textarea:invalid { background-color: #f0dddd; }
|
128
|
+
|
129
|
+
|
130
|
+
/* These selection declarations have to be separate
|
131
|
+
No text-shadow: twitter.com/miketaylr/status/12228805301
|
132
|
+
Also: hot pink! */
|
133
|
+
::-moz-selection{ background: #FF5E99; color:#fff; text-shadow: none; }
|
134
|
+
::selection { background:#FF5E99; color:#fff; text-shadow: none; }
|
135
|
+
|
136
|
+
/* j.mp/webkit-tap-highlight-color */
|
137
|
+
a:link { -webkit-tap-highlight-color: #FF5E99; }
|
138
|
+
|
139
|
+
/* Make buttons play nice in IE:
|
140
|
+
www.viget.com/inspire/styling-the-button-element-in-internet-explorer/ */
|
141
|
+
button { width: auto; overflow: visible; }
|
142
|
+
|
143
|
+
/* Bicubic resizing for non-native sized IMG:
|
144
|
+
code.flickr.com/blog/2008/11/12/on-ui-quality-the-little-things-client-side-image-resizing/ */
|
145
|
+
.ie7 img { -ms-interpolation-mode: bicubic; }
|
146
|
+
|
147
|
+
/**
|
148
|
+
* You might tweak these..
|
149
|
+
*/
|
150
|
+
|
151
|
+
body, select, input, textarea {
|
152
|
+
/* #444 looks better than black: twitter.com/H_FJ/statuses/11800719859 */
|
153
|
+
color: #444;
|
154
|
+
/* Set your base font here, to apply evenly */
|
155
|
+
/* font-family: Georgia, serif; */
|
156
|
+
}
|
157
|
+
|
158
|
+
/* Headers (h1, h2, etc) have no default font-size or margin; define those yourself */
|
159
|
+
h1, h2, h3, h4, h5, h6 { font-weight: bold; }
|
160
|
+
|
161
|
+
a, a:active, a:visited { color: #607890; }
|
162
|
+
a:hover { color: #036; }
|
163
|
+
|
164
|
+
|
165
|
+
/**
|
166
|
+
* Primary styles
|
167
|
+
*
|
168
|
+
* Author:
|
169
|
+
*/
|
170
|
+
|
171
|
+
|
172
|
+
|
173
|
+
|
174
|
+
|
175
|
+
|
176
|
+
|
177
|
+
|
178
|
+
|
179
|
+
|
180
|
+
|
181
|
+
|
182
|
+
|
183
|
+
|
184
|
+
|
185
|
+
|
186
|
+
/**
|
187
|
+
* Non-semantic helper classes: please define your styles before this section.
|
188
|
+
*/
|
189
|
+
|
190
|
+
/* For image replacement */
|
191
|
+
.ir { display: block; text-indent: -999em; overflow: hidden; background-repeat: no-repeat; text-align: left; direction: ltr; }
|
192
|
+
|
193
|
+
/* Hide for both screenreaders and browsers:
|
194
|
+
css-discuss.incutio.com/wiki/Screenreader_Visibility */
|
195
|
+
.hidden { display: none; visibility: hidden; }
|
196
|
+
|
197
|
+
/* Hide only visually, but have it available for screenreaders: by Jon Neal.
|
198
|
+
www.webaim.org/techniques/css/invisiblecontent/ & j.mp/visuallyhidden */
|
199
|
+
.visuallyhidden { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; }
|
200
|
+
/* Extends the .visuallyhidden class to allow the element to be focusable when navigated to via the keyboard: drupal.org/node/897638 */
|
201
|
+
.visuallyhidden.focusable:active,
|
202
|
+
.visuallyhidden.focusable:focus { clip: auto; height: auto; margin: 0; overflow: visible; position: static; width: auto; }
|
203
|
+
|
204
|
+
/* Hide visually and from screenreaders, but maintain layout */
|
205
|
+
.invisible { visibility: hidden; }
|
206
|
+
|
207
|
+
/* The Magnificent Clearfix: Updated to prevent margin-collapsing on child elements.
|
208
|
+
j.mp/bestclearfix */
|
209
|
+
.clearfix:before, .clearfix:after { content: "\0020"; display: block; height: 0; overflow: hidden; }
|
210
|
+
.clearfix:after { clear: both; }
|
211
|
+
/* Fix clearfix: blueprintcss.lighthouseapp.com/projects/15318/tickets/5-extra-margin-padding-bottom-of-page */
|
212
|
+
.clearfix { zoom: 1; }
|
213
|
+
|
214
|
+
|
215
|
+
|
216
|
+
/**
|
217
|
+
* Media queries for responsive design.
|
218
|
+
*
|
219
|
+
* These follow after primary styles so they will successfully override.
|
220
|
+
*/
|
221
|
+
|
222
|
+
@media all and (orientation:portrait) {
|
223
|
+
/* Style adjustments for portrait mode goes here */
|
224
|
+
|
225
|
+
}
|
226
|
+
|
227
|
+
@media all and (orientation:landscape) {
|
228
|
+
/* Style adjustments for landscape mode goes here */
|
229
|
+
|
230
|
+
}
|
231
|
+
|
232
|
+
/* Grade-A Mobile Browsers (Opera Mobile, Mobile Safari, Android Chrome)
|
233
|
+
consider this: www.cloudfour.com/css-media-query-for-mobile-is-fools-gold/ */
|
234
|
+
@media screen and (max-device-width: 480px) {
|
235
|
+
|
236
|
+
|
237
|
+
/* Uncomment if you don't want iOS and WinMobile to mobile-optimize the text for you: j.mp/textsizeadjust */
|
238
|
+
/* html { -webkit-text-size-adjust:none; -ms-text-size-adjust:none; } */
|
239
|
+
}
|
240
|
+
|
241
|
+
|
242
|
+
/**
|
243
|
+
* Print styles.
|
244
|
+
*
|
245
|
+
* Inlined to avoid required HTTP connection: www.phpied.com/delay-loading-your-print-css/
|
246
|
+
*/
|
247
|
+
@media print {
|
248
|
+
* { background: transparent !important; color: black !important; text-shadow: none !important; filter:none !important;
|
249
|
+
-ms-filter: none !important; } /* Black prints faster: sanbeiji.com/archives/953 */
|
250
|
+
a, a:visited { color: #444 !important; text-decoration: underline; }
|
251
|
+
a[href]:after { content: " (" attr(href) ")"; }
|
252
|
+
abbr[title]:after { content: " (" attr(title) ")"; }
|
253
|
+
.ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after { content: ""; } /* Don't show links for images, or javascript/internal links */
|
254
|
+
pre, blockquote { border: 1px solid #999; page-break-inside: avoid; }
|
255
|
+
thead { display: table-header-group; } /* css-discuss.incutio.com/wiki/Printing_Tables */
|
256
|
+
tr, img { page-break-inside: avoid; }
|
257
|
+
@page { margin: 0.5cm; }
|
258
|
+
p, h2, h3 { orphans: 3; widows: 3; }
|
259
|
+
h2, h3{ page-break-after: avoid; }
|
260
|
+
}
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require File.expand_path('../../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe Vacation::Jekyll do
|
4
|
+
let (:jk) { Vacation::Jekyll.new(fake_jekyll) }
|
5
|
+
|
6
|
+
describe '#initialize' do
|
7
|
+
it 'should assign the path to a Jekyll blog' do
|
8
|
+
jk.source.should == fake_jekyll
|
9
|
+
end
|
10
|
+
|
11
|
+
{ 'not valid' => '/trøløløl', 'not readable' => '/var/root', 'not a directory' => __FILE__ }.each do |reason, path|
|
12
|
+
it "should bail if the path is #{reason}" do
|
13
|
+
lambda {
|
14
|
+
Vacation::Jekyll.new(path)
|
15
|
+
}.should raise_exception
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '#compile_to' do
|
21
|
+
let(:temp_dir) { Dir.mktmpdir }
|
22
|
+
let(:index_path) { File.expand_path('index.html', temp_dir) }
|
23
|
+
after { FileUtils.remove_entry_secure temp_dir }
|
24
|
+
|
25
|
+
it 'should compile that sumbitch to a tempdir' do
|
26
|
+
jk.compile_to(temp_dir)
|
27
|
+
|
28
|
+
File.exists?(index_path).should be_true
|
29
|
+
index_content = IO.read(index_path)
|
30
|
+
|
31
|
+
index = Nokogiri::HTML.parse(index_content)
|
32
|
+
index.css('title').inner_text.should == 'Index'
|
33
|
+
index.css('h4').count.should == 2
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should create the destination directory if it does not exist' do
|
37
|
+
Dir.rmdir(temp_dir)
|
38
|
+
File.exists?(temp_dir).should be_false
|
39
|
+
|
40
|
+
jk.compile_to(temp_dir)
|
41
|
+
File.exists?(index_path).should be_true
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'should throw an exception if the destination is not writable' do
|
45
|
+
lambda {
|
46
|
+
jk.compile_to('/var/root')
|
47
|
+
}.should raise_exception
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require File.expand_path('../../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe Vacation::S3 do
|
4
|
+
before { Fog.mock! }
|
5
|
+
|
6
|
+
let(:bucket_name) { 'pants' }
|
7
|
+
let(:s3) { Vacation::S3.new('fake', 'key', bucket_name)}
|
8
|
+
let(:file) { s3.bucket.files.create(:key => 'socks/feet', :body => 'toes') }
|
9
|
+
before { s3 && file }
|
10
|
+
|
11
|
+
describe '#initialize' do
|
12
|
+
it 'should assign AWS credentials' do
|
13
|
+
s3.access_key_id.should == 'fake'
|
14
|
+
s3.secret_access_key.should == 'key'
|
15
|
+
s3.bucket_name.should == 'pants'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#strip_bucket' do
|
20
|
+
it 'should destroy existing files' do
|
21
|
+
s3.strip_bucket
|
22
|
+
s3.bucket.files.count.should == 0
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '#download_from_bucket' do
|
27
|
+
let(:temp_dir) { Dir.mktmpdir }
|
28
|
+
after { FileUtils.remove_entry_secure temp_dir }
|
29
|
+
|
30
|
+
it 'should create directories and files existing in the bucket' do
|
31
|
+
s3.download_from_bucket temp_dir
|
32
|
+
|
33
|
+
File.exists?(File.expand_path('socks', temp_dir)).should be_true
|
34
|
+
File.exists?(File.expand_path('socks/feet', temp_dir)).should be_true
|
35
|
+
File.read(File.expand_path('socks/feet', temp_dir)).should == 'toes'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe '#upload_to_bucket' do
|
40
|
+
let(:temp_dir) { Dir.mktmpdir }
|
41
|
+
after { FileUtils.remove_entry_secure temp_dir }
|
42
|
+
|
43
|
+
it 'should pave over existing files' do
|
44
|
+
lambda {
|
45
|
+
s3.upload_to_bucket temp_dir
|
46
|
+
}.should change { s3.bucket.files.reload.count }.by -1
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'should create directories and files existing on the filesystem' do
|
50
|
+
FileUtils.mkdir(File.expand_path('pocket', temp_dir))
|
51
|
+
File.open(File.expand_path('pocket/chili', temp_dir), 'w') { |local| local.write('beans')}
|
52
|
+
|
53
|
+
s3.upload_to_bucket temp_dir
|
54
|
+
|
55
|
+
s3.bucket.files.count.should == 1
|
56
|
+
s3.bucket.files.first.key.should == 'pocket/chili'
|
57
|
+
s3.bucket.files.first.body.should == 'beans'
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe '#deploy_to_bucket' do
|
62
|
+
let(:backup_bucket) { s3.storage.directories.create(:key => "#{bucket_name}-vacation-backup") }
|
63
|
+
before do
|
64
|
+
s3.storage.stub!(:put_bucket_website) do |name, index, opts|
|
65
|
+
name.should == s3.bucket.key
|
66
|
+
index.should == 'index.html'
|
67
|
+
opts[:key].should == '404.html'
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'should deploy to the specified bucket' do
|
72
|
+
s3.deploy_to_bucket fake_jekyll
|
73
|
+
s3.bucket.files.count.should == 7
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'should obey a no-backup parameter' do
|
77
|
+
lambda {
|
78
|
+
s3.deploy_to_bucket fake_jekyll, :backup => false
|
79
|
+
}.should_not change{ s3.backup_bucket.files.count }
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'should enable the static hosting parameter and pages' do
|
83
|
+
pending 'this functionality is not mocked out in Fog yet'
|
84
|
+
s3.deploy_to_bucket fake_jekyll, :backup => false
|
85
|
+
s3.storage.get_bucket_website(s3.bucket.key).body['IndexDocument']['Suffix'].should == 'index.html'
|
86
|
+
s3.storage.get_bucket_website(s3.bucket.key).body['ErrorDocument']['Key'].should == '404.html'
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/vacation.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
lib = File.expand_path('../lib/', __FILE__)
|
2
|
+
$:.unshift lib unless $:.include?(lib)
|
3
|
+
|
4
|
+
require 'vacation'
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "vacation"
|
8
|
+
s.version = Vacation::VERSION
|
9
|
+
s.platform = Gem::Platform::RUBY
|
10
|
+
s.authors = ["Doc Ritezel"]
|
11
|
+
s.email = ['ritezel+doc@gmail.com']
|
12
|
+
s.homepage = "http://github.com/ohrite/vacation"
|
13
|
+
s.summary = "Send your Jekyll blog on an S3 vacation"
|
14
|
+
s.description = "You got your peanut butter on my static file host! You got your static blog content on my chocolate!"
|
15
|
+
|
16
|
+
s.required_rubygems_version = ">= 1.3.6"
|
17
|
+
|
18
|
+
s.add_development_dependency "rspec"
|
19
|
+
s.add_dependency "fog"
|
20
|
+
s.add_dependency "jekyll"
|
21
|
+
|
22
|
+
s.files = `git ls-files`.split("\n")
|
23
|
+
s.test_files = `git ls-files -- {spec}/*`.split("\n")
|
24
|
+
s.require_path = 'lib'
|
25
|
+
s.executables = %w(vacation)
|
26
|
+
s.default_executable = "vacation"
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: vacation
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 31
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 2
|
10
|
+
version: 0.1.2
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Doc Ritezel
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-03-06 00:00:00 -08:00
|
19
|
+
default_executable: vacation
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: rspec
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :development
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: fog
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 3
|
44
|
+
segments:
|
45
|
+
- 0
|
46
|
+
version: "0"
|
47
|
+
type: :runtime
|
48
|
+
version_requirements: *id002
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: jekyll
|
51
|
+
prerelease: false
|
52
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
hash: 3
|
58
|
+
segments:
|
59
|
+
- 0
|
60
|
+
version: "0"
|
61
|
+
type: :runtime
|
62
|
+
version_requirements: *id003
|
63
|
+
description: You got your peanut butter on my static file host! You got your static blog content on my chocolate!
|
64
|
+
email:
|
65
|
+
- ritezel+doc@gmail.com
|
66
|
+
executables:
|
67
|
+
- vacation
|
68
|
+
extensions: []
|
69
|
+
|
70
|
+
extra_rdoc_files: []
|
71
|
+
|
72
|
+
files:
|
73
|
+
- .gitignore
|
74
|
+
- .rvmrc
|
75
|
+
- Gemfile
|
76
|
+
- Gemfile.lock
|
77
|
+
- README.md
|
78
|
+
- Rakefile
|
79
|
+
- bin/vacation
|
80
|
+
- lib/tasks/deploy.rake
|
81
|
+
- lib/vacation.rb
|
82
|
+
- lib/vacation/jekyll.rb
|
83
|
+
- lib/vacation/s3.rb
|
84
|
+
- spec/spec_helper.rb
|
85
|
+
- spec/support/jekyll/_layouts/blog.html
|
86
|
+
- spec/support/jekyll/_layouts/default.html
|
87
|
+
- spec/support/jekyll/_posts/2011-03-04-i-am-wearing-ramen.markdown
|
88
|
+
- spec/support/jekyll/_posts/2011-03-05-about-last-night.markdown
|
89
|
+
- spec/support/jekyll/images/geocities.png
|
90
|
+
- spec/support/jekyll/index.html
|
91
|
+
- spec/support/jekyll/stylesheets/reset.css
|
92
|
+
- spec/unit/jekyll_spec.rb
|
93
|
+
- spec/unit/s3_spec.rb
|
94
|
+
- vacation.gemspec
|
95
|
+
has_rdoc: true
|
96
|
+
homepage: http://github.com/ohrite/vacation
|
97
|
+
licenses: []
|
98
|
+
|
99
|
+
post_install_message:
|
100
|
+
rdoc_options: []
|
101
|
+
|
102
|
+
require_paths:
|
103
|
+
- lib
|
104
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ">="
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
hash: 3
|
110
|
+
segments:
|
111
|
+
- 0
|
112
|
+
version: "0"
|
113
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
114
|
+
none: false
|
115
|
+
requirements:
|
116
|
+
- - ">="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
hash: 23
|
119
|
+
segments:
|
120
|
+
- 1
|
121
|
+
- 3
|
122
|
+
- 6
|
123
|
+
version: 1.3.6
|
124
|
+
requirements: []
|
125
|
+
|
126
|
+
rubyforge_project:
|
127
|
+
rubygems_version: 1.6.1
|
128
|
+
signing_key:
|
129
|
+
specification_version: 3
|
130
|
+
summary: Send your Jekyll blog on an S3 vacation
|
131
|
+
test_files: []
|
132
|
+
|