adva-static 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/adva-static.rb +1 -0
- data/lib/adva/static.rb +13 -0
- data/lib/adva/static/export.rb +104 -0
- data/lib/adva/static/export/page.rb +45 -0
- data/lib/adva/static/export/path.rb +49 -0
- data/lib/adva/static/export/queue.rb +27 -0
- data/lib/adva/static/export/store.rb +30 -0
- data/lib/adva/static/export/templates/config.ru +14 -0
- data/lib/adva/static/import.rb +42 -0
- data/lib/adva/static/import/format.rb +58 -0
- data/lib/adva/static/import/model.rb +21 -0
- data/lib/adva/static/import/model/base.rb +78 -0
- data/lib/adva/static/import/model/blog.rb +33 -0
- data/lib/adva/static/import/model/page.rb +28 -0
- data/lib/adva/static/import/model/post.rb +78 -0
- data/lib/adva/static/import/model/section.rb +51 -0
- data/lib/adva/static/import/model/site.rb +59 -0
- data/lib/adva/static/import/request.rb +92 -0
- data/lib/adva/static/import/source.rb +82 -0
- data/lib/adva/static/rack.rb +15 -0
- data/lib/adva/static/rack/export.rb +59 -0
- data/lib/adva/static/rack/request.rb +39 -0
- data/lib/adva/static/rack/static.rb +40 -0
- data/lib/adva/static/rack/watch.rb +88 -0
- data/lib/adva/static/setup.rb +68 -0
- data/lib/adva/static/watch.rb +7 -0
- data/lib/adva/static/watch/handler.rb +57 -0
- data/lib/adva/tasks/static.rb +73 -0
- data/lib/adva_static/version.rb +3 -0
- data/lib/testing/step_definitions.rb +85 -0
- data/lib/testing/test_helper.rb +133 -0
- metadata +35 -5
- data/lib/bundler/repository.rb +0 -118
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module Adva
|
4
|
+
class Static
|
5
|
+
module Rack
|
6
|
+
class Export
|
7
|
+
include Request
|
8
|
+
|
9
|
+
attr_reader :app, :target, :store
|
10
|
+
|
11
|
+
def initialize(app, options = {})
|
12
|
+
@app = app
|
13
|
+
@target = Pathname.new(options[:target] || File.expand_path('./export'))
|
14
|
+
@store = Adva::Static::Export::Store.new(target)
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(env)
|
18
|
+
path = env['PATH_INFO'].dup # gets modified by routing_filter
|
19
|
+
app.call(env).tap do |status, headers, response|
|
20
|
+
export(path, response) if export?(env, status)
|
21
|
+
if headers.key?(PURGE_HEADER)
|
22
|
+
paths = normalize_paths(headers[PURGE_HEADER])
|
23
|
+
paths.each do |path|
|
24
|
+
purge(path)
|
25
|
+
request(path)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
|
33
|
+
def export?(env, status)
|
34
|
+
env[STORE_HEADER].present? and status == 200
|
35
|
+
end
|
36
|
+
|
37
|
+
def export(path, response)
|
38
|
+
page = Adva::Static::Export::Page.new(path, response)
|
39
|
+
Adva.out.puts " storing #{page.url.filename}"
|
40
|
+
store.write(page.url, page.body)
|
41
|
+
end
|
42
|
+
|
43
|
+
def purge(path)
|
44
|
+
Adva.out.puts " purging #{path}"
|
45
|
+
store.purge(Adva::Static::Export::Path.new(path))
|
46
|
+
end
|
47
|
+
|
48
|
+
def request(path)
|
49
|
+
super('GET', Adva::Static::Export::Path.new(path), STORE_HEADER => true)
|
50
|
+
end
|
51
|
+
|
52
|
+
def normalize_paths(paths)
|
53
|
+
paths = paths.split("\n") if paths.is_a?(String)
|
54
|
+
Array(paths)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'rack/utils'
|
2
|
+
|
3
|
+
module Adva
|
4
|
+
class Static
|
5
|
+
module Rack
|
6
|
+
module Request
|
7
|
+
protected
|
8
|
+
def request(method, path, params = {})
|
9
|
+
Adva.out.puts " #{params['_method'] ? params['_method'].upcase : method} #{path} "
|
10
|
+
call(env_for(method, path, params)).tap do |status, headers, response|
|
11
|
+
Adva.out.puts " => #{status} " + (status == 302 ? "(Location: #{headers['Location']})" : '')
|
12
|
+
Adva.out.puts response if status == 500
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def env_for(method, path, params)
|
17
|
+
::Rack::MockRequest.env_for("http://#{site.host}#{path}", :method => method,
|
18
|
+
:input => ::Rack::Utils.build_nested_query(params),
|
19
|
+
'CONTENT_TYPE' => 'application/x-www-form-urlencoded',
|
20
|
+
'HTTP_AUTHORIZATION' => 'Basic ' + ["#{username}:#{password}"].pack('m*'),
|
21
|
+
STORE_HEADER => params[STORE_HEADER]
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
def username
|
26
|
+
'admin@admin.org'
|
27
|
+
end
|
28
|
+
|
29
|
+
def password
|
30
|
+
'admin!'
|
31
|
+
end
|
32
|
+
|
33
|
+
def site
|
34
|
+
@site ||= Site.first || raise('could not find any site')
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'rack/utils'
|
2
|
+
|
3
|
+
module Adva
|
4
|
+
class Static
|
5
|
+
module Rack
|
6
|
+
class Static < ::Rack::File
|
7
|
+
attr_reader :app, :root
|
8
|
+
|
9
|
+
def initialize(app, root)
|
10
|
+
@app = app
|
11
|
+
@root = root
|
12
|
+
Adva.out.puts "serving from #{root}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def call(env)
|
16
|
+
if get?(env) && path = static(env)
|
17
|
+
super(env.merge('PATH_INFO' => path))
|
18
|
+
else
|
19
|
+
app.call(env)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
|
25
|
+
def static(env)
|
26
|
+
path = env['PATH_INFO'].chomp('/')
|
27
|
+
[path, "#{path}.html", "#{path}/index.html"].detect { |path| file?(path) }
|
28
|
+
end
|
29
|
+
|
30
|
+
def file?(path)
|
31
|
+
File.file?(File.join(root, ::Rack::Utils.unescape(path)))
|
32
|
+
end
|
33
|
+
|
34
|
+
def get?(env)
|
35
|
+
env['REQUEST_METHOD'] == 'GET'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'watchr'
|
2
|
+
|
3
|
+
module Adva
|
4
|
+
class Static
|
5
|
+
module Rack
|
6
|
+
class Watch
|
7
|
+
autoload :Handler, 'adva/static/watch/handler'
|
8
|
+
|
9
|
+
include Request
|
10
|
+
|
11
|
+
attr_reader :app, :dir, :watch
|
12
|
+
|
13
|
+
delegate :call, :to => :app
|
14
|
+
|
15
|
+
def initialize(app, options = {}, &block)
|
16
|
+
@app = app
|
17
|
+
@dir = Pathname.new(options[:dir] || File.expand_path('import'))
|
18
|
+
dir.mkpath
|
19
|
+
run!
|
20
|
+
end
|
21
|
+
|
22
|
+
def update(path, event_type = nil)
|
23
|
+
Adva.out.puts "\n#{event_type}: #{path}"
|
24
|
+
import = Adva::Static::Import.new(:source => dir)
|
25
|
+
request = import.request_for(path)
|
26
|
+
status, headers, response = self.request('POST', request.path, request.params)
|
27
|
+
response = get(path) if !request.destroy? && status == 302
|
28
|
+
rescue Exception => e
|
29
|
+
Adva.out.puts e.message
|
30
|
+
e.backtrace.each { |line| Adva.out.puts line }
|
31
|
+
end
|
32
|
+
|
33
|
+
def get(path)
|
34
|
+
import = Adva::Static::Import.new(:source => dir)
|
35
|
+
request = import.request_for(path)
|
36
|
+
self.request('GET', request.public_path, STORE_HEADER => true)
|
37
|
+
rescue Exception => e
|
38
|
+
Adva.out.puts e.message
|
39
|
+
e.backtrace.each { |line| Adva.out.puts line }
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
|
44
|
+
def run!
|
45
|
+
@watch = fork { watch!(Adva.out) }
|
46
|
+
at_exit { kill_watch }
|
47
|
+
end
|
48
|
+
|
49
|
+
def watch!(out)
|
50
|
+
Adva.out.puts "watching #{dir} for changes"
|
51
|
+
Dir.chdir(dir)
|
52
|
+
handler.listen
|
53
|
+
rescue SignalException, SystemExit
|
54
|
+
rescue Exception => e
|
55
|
+
p e
|
56
|
+
e.backtrace.each { |line| puts line }
|
57
|
+
end
|
58
|
+
|
59
|
+
def trap_interrupt
|
60
|
+
Signal.trap('INT') do
|
61
|
+
if Time.now - @interrupt < 1
|
62
|
+
exit
|
63
|
+
else
|
64
|
+
STDERR.puts "\nReloading watched paths ... interrupt again to exit."
|
65
|
+
handler.refresh(watched_paths)
|
66
|
+
end
|
67
|
+
@interrupt = Time.now
|
68
|
+
end
|
69
|
+
@interrupt = Time.now
|
70
|
+
end
|
71
|
+
|
72
|
+
def handler
|
73
|
+
@handler ||= Adva::Static::Watch::Handler.new(self, dir.join("**/*.{#{Import::Source::TYPES.join(',')}}"))
|
74
|
+
end
|
75
|
+
|
76
|
+
def kill_watch
|
77
|
+
Process.kill('TERM', watch)
|
78
|
+
end
|
79
|
+
|
80
|
+
def watched_paths
|
81
|
+
paths = Dir[dir.join('**/*')].map {|path| Pathname(path).expand_path }
|
82
|
+
paths << Pathname.new(__FILE__) if paths.empty?
|
83
|
+
paths
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'core_ext/ruby/kernel/silence_stream'
|
2
|
+
|
3
|
+
module Adva
|
4
|
+
class Static
|
5
|
+
class Setup
|
6
|
+
attr_reader :app, :root, :source, :target, :host, :title, :remote
|
7
|
+
|
8
|
+
def initialize(options)
|
9
|
+
@app = options[:app] || Rails.application
|
10
|
+
@root = Pathname.new(options[:root] || Dir.pwd)
|
11
|
+
@source = root.join(options[:source] || 'import')
|
12
|
+
@target = root.join(options[:target] || 'export')
|
13
|
+
@remote = options[:remote]
|
14
|
+
@host = options[:host] || 'example.org'
|
15
|
+
@title = options[:title] || host
|
16
|
+
|
17
|
+
Adva.out = StringIO.new('')
|
18
|
+
end
|
19
|
+
|
20
|
+
def run
|
21
|
+
setup_directories
|
22
|
+
initial_import_and_export
|
23
|
+
setup_source_repository
|
24
|
+
setup_export_repository
|
25
|
+
end
|
26
|
+
|
27
|
+
def setup_directories
|
28
|
+
source.mkdir rescue Errno::EEXIST
|
29
|
+
target.mkdir rescue Errno::EEXIST
|
30
|
+
site = source.join('site.yml')
|
31
|
+
File.open(site, 'w+') { |f| f.write(YAML.dump(:host => host, :title => title)) } unless site.exist?
|
32
|
+
end
|
33
|
+
|
34
|
+
def initial_import_and_export
|
35
|
+
Import.new(:source => source).run
|
36
|
+
Export.new(app, :target => target).run
|
37
|
+
end
|
38
|
+
|
39
|
+
def setup_source_repository
|
40
|
+
root.join('.gitignore').rmtree rescue Errno::ENOENT
|
41
|
+
root.join('.git').rmtree rescue Errno::ENOENT
|
42
|
+
|
43
|
+
File.open(root.join('.gitignore'), 'w+') { |f| f.write('export') }
|
44
|
+
|
45
|
+
Dir.chdir(root) do
|
46
|
+
`git init`
|
47
|
+
`git add .`
|
48
|
+
`git commit -am '#{host} source'`
|
49
|
+
`git branch source`
|
50
|
+
`git checkout --quiet source`
|
51
|
+
`git branch -D master`
|
52
|
+
`git remote add origin #{remote} -t source` if remote
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def setup_export_repository
|
57
|
+
root.join('export/.git').rmtree rescue Errno::ENOENT
|
58
|
+
|
59
|
+
Dir.chdir(target) do
|
60
|
+
`git init`
|
61
|
+
`git add .`
|
62
|
+
`git commit -am '#{host} export'`
|
63
|
+
`git remote add origin #{remote} -t master` if remote
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'observer'
|
2
|
+
require 'watchr'
|
3
|
+
require 'watchr/event_handlers/portable'
|
4
|
+
|
5
|
+
module Adva
|
6
|
+
class Static
|
7
|
+
module Watch
|
8
|
+
class Handler
|
9
|
+
include Observable
|
10
|
+
|
11
|
+
def initialize(observable, pattern)
|
12
|
+
add_observer(observable)
|
13
|
+
@pattern = pattern
|
14
|
+
@current = Dir[pattern]
|
15
|
+
@mtime = Time.now
|
16
|
+
end
|
17
|
+
|
18
|
+
def listen
|
19
|
+
loop { trigger; sleep(0.5) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def trigger
|
23
|
+
events.each do |path, event|
|
24
|
+
changed(true)
|
25
|
+
notify_observers(path, event)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
protected
|
30
|
+
|
31
|
+
def events
|
32
|
+
@last = @current.dup
|
33
|
+
@current = Dir[@pattern]
|
34
|
+
deleted + created + modified
|
35
|
+
end
|
36
|
+
|
37
|
+
def modified
|
38
|
+
(@current & @last).each do |path|
|
39
|
+
mtime = File.mtime(path)
|
40
|
+
if mtime > @mtime
|
41
|
+
@mtime = mtime
|
42
|
+
return [[path, :modified]]
|
43
|
+
end
|
44
|
+
end && []
|
45
|
+
end
|
46
|
+
|
47
|
+
def created
|
48
|
+
(@current - @last).map { |path| [path, :created] }
|
49
|
+
end
|
50
|
+
|
51
|
+
def deleted
|
52
|
+
(@last - @current).map { |path| [path, :deleted] }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'thor/group'
|
3
|
+
require 'patches/thor/core_ext/hash'
|
4
|
+
require 'patches/thor/group/symbolized_options'
|
5
|
+
|
6
|
+
module Adva
|
7
|
+
module Tasks
|
8
|
+
class Static
|
9
|
+
class Setup < Thor::Group
|
10
|
+
namespace 'adva:static:setup'
|
11
|
+
desc 'Setup a static version of your site'
|
12
|
+
class_option :source, :required => false, :banner => 'source directory (defaults to import)'
|
13
|
+
class_option :target, :required => false, :banner => 'source directory (defaults to export)'
|
14
|
+
class_option :host, :required => false, :banner => 'hostname of your site (defaults to example.org)'
|
15
|
+
class_option :title, :required => false, :banner => 'title of your site (defaults to the hostname)'
|
16
|
+
class_option :remote, :required => false, :banner => 'github repository url (defaults to none)'
|
17
|
+
|
18
|
+
def export
|
19
|
+
require 'config/environment'
|
20
|
+
Adva::Static::Setup.new(symbolized_options).run
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class Import < Thor::Group
|
25
|
+
namespace 'adva:static:import'
|
26
|
+
desc 'Import a site from a directory'
|
27
|
+
class_option :source, :required => false
|
28
|
+
|
29
|
+
def import
|
30
|
+
require 'config/environment'
|
31
|
+
Adva::Static::Import.new(symbolized_options).run
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class Export < Thor::Group
|
36
|
+
namespace 'adva:static:export'
|
37
|
+
desc 'Export a static version of a site'
|
38
|
+
class_option :target, :required => false
|
39
|
+
|
40
|
+
def export
|
41
|
+
require 'config/environment'
|
42
|
+
Adva::Static::Export.new(Rails.application, symbolized_options).run
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class Update < Thor::Group
|
47
|
+
namespace 'adva:static:update'
|
48
|
+
desc 'Import and export a static version of a site'
|
49
|
+
class_option :source, :required => false
|
50
|
+
class_option :target, :required => false
|
51
|
+
|
52
|
+
def export
|
53
|
+
require 'config/environment'
|
54
|
+
Adva::Static::Import.new(symbolized_options).run
|
55
|
+
Adva::Static::Export.new(Rails.application, symbolized_options).run
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class Server < Thor::Group
|
60
|
+
namespace 'adva:static:server'
|
61
|
+
desc 'Start the adva:static server and watcher'
|
62
|
+
class_option :root, :required => false, :default => 'export'
|
63
|
+
|
64
|
+
def server
|
65
|
+
ARGV.shift
|
66
|
+
Dir.chdir(symbolized_options[:root])
|
67
|
+
require "rack"
|
68
|
+
::Rack::Server.start
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|