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.
@@ -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,7 @@
1
+ module Adva
2
+ class Static
3
+ module Watch
4
+ autoload :Handler, 'adva/static/watch/handler'
5
+ end
6
+ end
7
+ 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
@@ -0,0 +1,3 @@
1
+ module AdvaStatic
2
+ VERSION = "0.0.4"
3
+ end