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 @@
1
+ require 'adva/static'
@@ -0,0 +1,13 @@
1
+ require 'adva/core'
2
+
3
+ module Adva
4
+ class Static < ::Rails::Engine
5
+ autoload :Export, 'adva/static/export'
6
+ autoload :Import, 'adva/static/import'
7
+ autoload :Watch, 'adva/static/watch'
8
+ autoload :Rack, 'adva/static/rack'
9
+ autoload :Setup, 'adva/static/setup'
10
+
11
+ include Adva::Engine
12
+ end
13
+ end
@@ -0,0 +1,104 @@
1
+ require 'nokogiri'
2
+ require 'uri'
3
+ require 'benchmark'
4
+
5
+ module Adva
6
+ class Static
7
+ class Export
8
+ autoload :Page, 'adva/static/export/page'
9
+ autoload :Path, 'adva/static/export/path'
10
+ autoload :Queue, 'adva/static/export/queue'
11
+ autoload :Store, 'adva/static/export/store'
12
+
13
+ attr_reader :app, :queue, :store, :options
14
+
15
+ DEFAULT_OPTIONS = {
16
+ :source => "#{Dir.pwd}/public",
17
+ :target => "#{Dir.pwd}/export"
18
+ }
19
+
20
+ def initialize(app, options = {})
21
+ @options = options.reverse_merge!(DEFAULT_OPTIONS)
22
+
23
+ @app = app
24
+ @store = Store.new(target)
25
+ @queue = Queue.new
26
+
27
+ queue.push(options[:queue] || Path.new('/'))
28
+
29
+ FileUtils.rm_r(Dir[target.join('*')])
30
+ end
31
+
32
+ def run
33
+ configure
34
+ copy_assets
35
+ process(queue.shift) until queue.empty?
36
+ end
37
+
38
+ protected
39
+
40
+ def source
41
+ @source ||= Pathname.new(options[:source])
42
+ end
43
+
44
+ def target
45
+ @target ||= Pathname.new(options[:target])
46
+ end
47
+
48
+ def copy_assets
49
+ %w(images javascripts stylesheets).each do |dir|
50
+ FileUtils.cp_r(source.join(dir), target.join(dir)) if source.join(dir).exist?
51
+ end
52
+ end
53
+
54
+ def process(path)
55
+ if page = get(path)
56
+ store.write(path, page.body)
57
+ enqueue_urls(page) if path.html?
58
+ end
59
+ end
60
+
61
+ def get(path)
62
+ result = nil
63
+ bench = Benchmark.measure do
64
+ result = app.call(env_for(path))
65
+ result = follow_redirects(result)
66
+ end
67
+
68
+ status, headers, response = result
69
+ if status == 200
70
+ Adva.out.puts "#{bench.total.to_s[0..3]}s: exporting #{path}"
71
+ Page.new(path, headers['X-Sendfile'] ? File.read(headers['X-Sendfile']) : response)
72
+ else
73
+ Adva.out.puts "can not export #{path} (status: #{status})"
74
+ end
75
+ end
76
+
77
+ def follow_redirects(response)
78
+ response = app.call(env_for(response[1]['Location'])) while redirect?(response[0])
79
+ response
80
+ end
81
+
82
+ def redirect?(status)
83
+ status == 301
84
+ end
85
+
86
+ def env_for(path)
87
+ site = Site.first || raise('could not find any site')
88
+ name, port = site.host.split(':')
89
+ ::Rack::MockRequest.env_for(path).merge('SERVER_NAME' => name,'SERVER_PORT' => port || '80')
90
+ end
91
+
92
+ def enqueue_urls(page)
93
+ queue.push(page.urls.reject { |path| path.remote? || store.exists?(path) }.uniq)
94
+ end
95
+
96
+ def configure
97
+ config = Path.new('config.ru')
98
+ unless store.exists?(config)
99
+ store.write(config, File.read(File.expand_path('../export/templates/config.ru', __FILE__)))
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,45 @@
1
+ require 'rack'
2
+
3
+ module Adva
4
+ class Static
5
+ class Export
6
+ class Page
7
+ URL_ATTRIBUTES = {
8
+ '//a[@href]' => 'href',
9
+ '//script[@src]' => 'src',
10
+ '//link[@rel="stylesheet"]' => 'href'
11
+ }
12
+
13
+ attr_reader :url, :response
14
+
15
+ def initialize(url, response)
16
+ @url = Path.new(url)
17
+ @response = response
18
+ end
19
+
20
+ def urls
21
+ URL_ATTRIBUTES.inject([]) do |urls, (xpath, name)|
22
+ urls += dom.xpath(xpath).map { |node| Path.new(node.attributes[name]) }
23
+ end
24
+ end
25
+
26
+ def body
27
+ @body ||= case response
28
+ when ActionDispatch::Response
29
+ response.body
30
+ when ::Rack::File
31
+ File.read(response.path)
32
+ else
33
+ response.to_s
34
+ end
35
+ end
36
+
37
+ protected
38
+
39
+ def dom
40
+ @dom ||= Nokogiri::HTML(body)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,49 @@
1
+ require 'uri'
2
+
3
+ module Adva
4
+ class Static
5
+ class Export
6
+ class Path < String
7
+ attr_reader :host
8
+
9
+ def initialize(path)
10
+ @host = URI.parse(path.to_s).host rescue 'invalid.host'
11
+ path = normalize_path(path)
12
+ super
13
+ end
14
+
15
+ def filename
16
+ @filename ||= normalize_filename(self)
17
+ end
18
+
19
+ def extname
20
+ @extname ||= File.extname(self)
21
+ end
22
+
23
+ def html?
24
+ extname.blank? || extname == '.html'
25
+ end
26
+
27
+ def remote?
28
+ host.present?
29
+ end
30
+
31
+ protected
32
+
33
+ def normalize_path(path)
34
+ path = URI.parse(path.to_s).path || '/' rescue '/' # extract path
35
+ path = path[0..-2] if path[-1, 1] == '/' # remove trailing slash
36
+ path = "/#{path}" unless path[0, 1] == '/' # add leading slash
37
+ path
38
+ end
39
+
40
+ def normalize_filename(path)
41
+ path = path[1..-1] if path[0, 1] == '/' # remove leading slash
42
+ path = 'index' if path.empty? # use 'index' instead of empty paths
43
+ path = (html? ? "#{path.gsub(extname, '')}.html" : path) # add .html extension if necessary
44
+ path
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,27 @@
1
+ module Adva
2
+ class Static
3
+ class Export
4
+ class Queue < Array
5
+ def push(*elements)
6
+ elements = Array(elements).flatten.uniq
7
+ elements.reject! { |element| seen?(element) }
8
+ seen(elements)
9
+ super
10
+ end
11
+
12
+ def seen?(element)
13
+ log.include?(element)
14
+ end
15
+
16
+ def seen(elements)
17
+ @log = log.concat(elements)
18
+ log.uniq!
19
+ end
20
+
21
+ def log
22
+ @log ||= []
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,30 @@
1
+ require 'fileutils'
2
+
3
+ module Adva
4
+ class Static
5
+ class Export
6
+ class Store
7
+ attr_reader :dir
8
+
9
+ def initialize(dir)
10
+ @dir = Pathname.new(dir.to_s)
11
+ FileUtils.mkdir_p(dir)
12
+ end
13
+
14
+ def exists?(path)
15
+ File.exists?(dir.join(path.filename))
16
+ end
17
+
18
+ def write(path, body)
19
+ path = dir.join(path.filename)
20
+ FileUtils.mkdir_p(File.dirname(path))
21
+ File.open(path, 'w+') { |f| f.write(body) }
22
+ end
23
+
24
+ def purge(path)
25
+ dir.join(path.filename).delete rescue Errno::ENOENT
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,14 @@
1
+ Dir.chdir('..') until File.exists?('config/environment.rb')
2
+
3
+ require 'config/environment.rb'
4
+
5
+ Rails::Application.configure do
6
+ ActionController::Base.allow_forgery_protection = false
7
+ end
8
+
9
+ use Adva::Static::Rack::Watch
10
+ use Adva::Static::Rack::Export
11
+ use Adva::Static::Rack::Static, ::File.expand_path('../export', __FILE__)
12
+
13
+ puts 'listening.'
14
+ run Rails.application
@@ -0,0 +1,42 @@
1
+ module Adva
2
+ class Static
3
+ class Import
4
+ autoload :Format, 'adva/static/import/format'
5
+ autoload :Model, 'adva/static/import/model'
6
+ autoload :Request, 'adva/static/import/request'
7
+ autoload :Source, 'adva/static/import/source'
8
+
9
+ attr_reader :root
10
+
11
+ def initialize(options = {})
12
+ @root = Pathname.new(File.expand_path(options[:source] || 'import'))
13
+ end
14
+
15
+ def run
16
+ Adva.out.puts "importing from #{root}"
17
+ Account.all.each(&:destroy)
18
+ Model::Site.new(root).updated_record.save!
19
+ end
20
+
21
+ def import(path)
22
+ model = recognize(path).first
23
+ model.updated_record.save! if model
24
+ end
25
+
26
+ def request_for(path)
27
+ model = recognize(path).first
28
+ Request.new(model.source, model.record, model.attributes)
29
+ end
30
+
31
+ protected
32
+
33
+ def source(path)
34
+ Source.new(path, root)
35
+ end
36
+
37
+ def recognize(path)
38
+ Model.recognize([source(path)])
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,58 @@
1
+ module Adva
2
+ class Static
3
+ class Import
4
+ module Format
5
+ def self.for(path)
6
+ name = File.extname(path).gsub('.', '').camelize
7
+ const_get(name).new(path) if name.present?
8
+ end
9
+
10
+ class Base
11
+ attr_reader :path
12
+
13
+ def initialize(path)
14
+ @path = path
15
+ end
16
+
17
+ def load(target)
18
+ data.each do |name, value|
19
+ define_attribute(target, name) if define_attribute?(target, name)
20
+ target.instance_variable_set(:"@#{name}", value)
21
+ end if data.is_a?(Hash)
22
+ end
23
+
24
+ def define_attribute?(target, name)
25
+ !target.attribute_name?(name) && target.column_name?(name)
26
+ end
27
+
28
+ def define_attribute(target, name)
29
+ target.attribute_names << name
30
+ target.attribute_names.uniq!
31
+ target.class.send(:attr_reader, name) unless target.respond_to?(name)
32
+ end
33
+ end
34
+
35
+ class Yml < Base
36
+ def data
37
+ @data ||= YAML.load_file(path)
38
+ end
39
+ end
40
+
41
+ class Jekyll < Base
42
+ def data
43
+ @data ||= begin
44
+ file =~ /^(---\s*\n.*?\n?)^---\s*$\n?(.*)/m
45
+ data = YAML.load($1) rescue {}
46
+ data.merge!(:body => $2) if $2
47
+ data
48
+ end
49
+ end
50
+
51
+ def file
52
+ @file ||= File.read(path)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,21 @@
1
+ module Adva
2
+ class Static
3
+ class Import
4
+ module Model
5
+ autoload :Base, 'adva/static/import/model/base'
6
+ autoload :Blog, 'adva/static/import/model/blog'
7
+ autoload :Page, 'adva/static/import/model/page'
8
+ autoload :Post, 'adva/static/import/model/post'
9
+ autoload :Section, 'adva/static/import/model/section'
10
+ autoload :Site, 'adva/static/import/model/site'
11
+
12
+ class << self
13
+ def recognize(sources)
14
+ types = [Site, Post, Section]
15
+ types.map { |type| type.recognize(sources) }.flatten.compact.sort
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,78 @@
1
+ require 'core_ext/ruby/array/flatten_once'
2
+
3
+ module Adva
4
+ class Static
5
+ class Import
6
+ module Model
7
+ class Base
8
+ attr_reader :source, :attribute_names
9
+
10
+ def initialize(source)
11
+ @source = source
12
+ load
13
+ end
14
+
15
+ def attributes
16
+ attributes = attribute_names.map { |name| [name, self.send(name)] unless self.send(name).nil? }
17
+ attributes = Hash[*attributes.compact.flatten_once]
18
+ record && record.id ? attributes.merge(:id => record.id.to_s) : attributes
19
+ end
20
+
21
+ def attribute_name?(name)
22
+ attribute_names.include?(name.to_sym)
23
+ end
24
+
25
+ def column_name?(name)
26
+ model.column_names.include?(name.to_s)
27
+ end
28
+
29
+ def updated_record
30
+ record.tap { |record| record.attributes = attributes }
31
+ end
32
+
33
+ def model
34
+ self.class.name.demodulize.constantize
35
+ end
36
+
37
+ def site_id
38
+ site.id.to_s
39
+ end
40
+
41
+ def slug
42
+ source.basename
43
+ end
44
+
45
+ def path
46
+ source.path
47
+ end
48
+
49
+ def body
50
+ @body || ''
51
+ end
52
+
53
+ def updated_at
54
+ source.mtime
55
+ end
56
+
57
+ def loadable
58
+ @loadable ||= source.full_path
59
+ end
60
+
61
+ def load
62
+ if loadable.exist?
63
+ format = Format.for(loadable) and format.load(self)
64
+ end
65
+ end
66
+
67
+ def ==(other)
68
+ source == other
69
+ end
70
+
71
+ def <=>(other)
72
+ source <=> other.source
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end