amber 0.2.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,189 @@
1
+ #
2
+ # All pages and layouts are evaluated in the context of a View object.
3
+ #
4
+ # Any methods you want to make available to these templates should be added to this class.
5
+ #
6
+
7
+ require 'i18n'
8
+ require 'amber/render/helpers/html_helper'
9
+ require 'amber/render/helpers/navigation_helper'
10
+ require 'amber/render/helpers/haml_helper'
11
+ require 'amber/render/helpers/language_helper'
12
+
13
+ module Amber
14
+ module Render
15
+ class View
16
+ attr_reader :locals
17
+ attr_reader :page
18
+ attr_reader :site
19
+
20
+ # include helpers (get added as member functions)
21
+ include HtmlHelper
22
+ include NavigationHelper
23
+ include HamlHelper
24
+ include LanguageHelper
25
+
26
+ def initialize(page, site)
27
+ @page = page
28
+ @site = site
29
+ @stack = []
30
+ @locals = {}
31
+ @this = StaticPage::PropertySet.new # TODO: come up with a better way to handle this.
32
+ # @this is not actually used, it is just there so haml headers don't bomb out.
33
+ end
34
+
35
+ #
36
+ # more or less the same as Rails render()
37
+ #
38
+ # supported options:
39
+ # :page -- page path or page object to render
40
+ # :file -- renders the file specified, using suffix to determine type.
41
+ # :partial -- same as :file, but disables layout
42
+ # :text -- string to render
43
+ # :type -- required for :text
44
+ #
45
+ def render(options={}, locals={}, toc_only=false, &block)
46
+ push_context @locals, @page
47
+ @locals = @locals.merge(locals)
48
+ locale = I18n.locale = @locals[:locale]
49
+ options = parse_render_options(locale, options)
50
+ @page = options[:page] if options[:page]
51
+ render_toc = should_render_toc?(locale, options, @page)
52
+ template = pick_template(locale, options)
53
+ if toc_only
54
+ template.render(self, :mode => :toc, :href_base => options[:href_base])
55
+ else
56
+ layout = pick_layout(locale, options)
57
+ if layout
58
+ layout.render(self) do |layout_yield_argument|
59
+ template.render(self, :mode => layout_yield_argument, :toc => render_toc)
60
+ end
61
+ else
62
+ template.render(self, :mode => :content, :toc => render_toc)
63
+ end
64
+ end
65
+ rescue StandardError => exc
66
+ if @site.continue_on_error
67
+ report_error(exc, options)
68
+ else
69
+ raise exc
70
+ end
71
+ ensure
72
+ @locals, @page = pop_context
73
+ I18n.locale = @locals[:locale]
74
+ end
75
+
76
+ def render_toc(options={}, locals={})
77
+ render(options, locals, true)
78
+ end
79
+
80
+ private
81
+
82
+ def find_file(path, site, page, locale)
83
+ search = [
84
+ path,
85
+ "#{site.pages_dir}/#{path}",
86
+ "#{page.file_path}/#{path}",
87
+ "#{File.dirname(page.file_path)}/#{path}",
88
+ "#{site.config_dir}/#{path}"
89
+ ]
90
+ search.each do |path|
91
+ return path if File.exists?(path)
92
+ Dir["#{path}.#{locale}.*"].each do |path_with_locale|
93
+ return path_with_locale if File.exists?(path_with_locale)
94
+ end
95
+ Dir["#{path}.*"].each do |path_with_suffix|
96
+ return path_with_suffix if File.exists?(path_with_suffix)
97
+ end
98
+ end
99
+ raise MissingTemplate.new(path)
100
+ end
101
+
102
+ def partialize(path)
103
+ File.dirname(path) + "/_" + File.basename(path)
104
+ end
105
+
106
+ def push_context(locals, page)
107
+ @stack.push([locals, page])
108
+ end
109
+
110
+ def pop_context
111
+ @stack.pop
112
+ end
113
+
114
+ #
115
+ # cleans up the `options` arg that is passed to render()
116
+ #
117
+ def parse_render_options(locale, options)
118
+ # handle non-hash options
119
+ if options.is_a?(String)
120
+ page = @site.find_page(options)
121
+ if page
122
+ options = {:page => page}
123
+ else
124
+ options = {:partial => options}
125
+ end
126
+ elsif options.is_a?(StaticPage)
127
+ options = {:page => options}
128
+ end
129
+
130
+ # convert :page, :partial, or :file to the real deal
131
+ if options[:page]
132
+ if options[:page].is_a?(String)
133
+ options[:page] = @site.find_page(options[:page])
134
+ end
135
+ options[:href_base] ||= page_path(options[:page], locale)
136
+ elsif options[:partial]
137
+ options[:partial] = find_file(partialize(options[:partial]), @site, @page, locale)
138
+ elsif options[:file]
139
+ options[:file] = find_file(options[:file], @site, @page, locale)
140
+ end
141
+ return options
142
+ end
143
+
144
+ def should_render_toc?(locale, options, page)
145
+ if options[:partial].nil?
146
+ if page.prop(locale, :toc).nil?
147
+ true
148
+ else
149
+ page.prop(locale, :toc)
150
+ end
151
+ else
152
+ false
153
+ end
154
+ end
155
+
156
+ def pick_template(locale, options)
157
+ if options[:page]
158
+ Template.new(file: options[:page].content_file(locale))
159
+ elsif options[:file]
160
+ Template.new(file: options[:file])
161
+ elsif options[:partial]
162
+ Template.new(file: options[:partial], partial: true)
163
+ elsif options[:text]
164
+ Template.new(content: options[:text], type: (options[:type] || :text))
165
+ end
166
+ end
167
+
168
+ def pick_layout(locale, options)
169
+ if options[:layout] && !options[:partial]
170
+ Render::Layout[options[:layout]]
171
+ else
172
+ nil
173
+ end
174
+ end
175
+
176
+ def report_error(exc, options)
177
+ if exc.is_a? MissingTemplate
178
+ msg = "ERROR: render() could not find file from #{options.inspect}"
179
+ Amber.logger.error(msg)
180
+ Amber.log_exception(exc)
181
+ "<pre>%s</pre>" % [msg, exc, exc.backtrace].flatten.join("\n")
182
+ else
183
+ Amber.log_exception(exc)
184
+ "<pre>%s</pre>" % [exc, exc.backtrace].flatten.join("\n")
185
+ end
186
+ end
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,113 @@
1
+ #
2
+ # A very simple http server for use when previewing pages.
3
+ #
4
+
5
+ require 'webrick'
6
+
7
+ module Amber
8
+ class Server
9
+ attr_reader :site
10
+ attr_reader :port
11
+
12
+ def self.start(options)
13
+ Server.new(options).start
14
+ end
15
+
16
+ def initialize(options)
17
+ @site = options[:site]
18
+ @port = options[:port]
19
+ @server = WEBrick::HTTPServer.new :Port => @port, :BindAddress => '127.0.0.1', :DocumentRoot => @site.dest_dir
20
+ @server.mount '/', StaticPageServlet, self
21
+ end
22
+
23
+ def start
24
+ trap 'INT' do
25
+ @server.shutdown
26
+ end
27
+ @server.start
28
+ end
29
+ end
30
+
31
+ class StaticPageServlet < WEBrick::HTTPServlet::FileHandler
32
+
33
+ def initialize(http_server, amber_server)
34
+ @logger = http_server.logger
35
+ @server = amber_server
36
+ super(http_server, @server.site.dest_dir, {:FanyIndexing => true})
37
+ end
38
+
39
+ def do_GET(request, response)
40
+ dest_dir = @server.site.dest_dir
41
+
42
+ # add local prefix for all paths that don't have a file suffix
43
+ if request.path !~ /\.[a-z]{2,4}$/
44
+ locale = get_locale(request)
45
+ if !locale
46
+ location = "http://localhost:#{@server.port}/#{I18n.default_locale}#{request.path.sub(/\/$/,'')}"
47
+ @logger.info "Redirect %s ==> %s" % [request.path, location]
48
+ response.header['Location'] = location
49
+ response.status = 307
50
+ return
51
+ end
52
+ end
53
+
54
+ if File.exists?(File.join(dest_dir, request.path))
55
+ @logger.info "Serve static file %s" % File.join(dest_dir, request.path)
56
+ super(request, response)
57
+ else
58
+ path = strip_locale(request.path)
59
+ @server.site.load_pages
60
+ page = @server.site.find_page_by_path(path)
61
+ if page
62
+ @logger.info "Serving Page %s" % page.path.join('/')
63
+ response.status = 200
64
+ response.content_type = "text/html; charset=utf-8"
65
+ # always refresh the page we are fetching
66
+ Amber::Render::Layout.reload
67
+ @server.site.render
68
+ page.render_to_file(dest_dir, :force => true)
69
+ file = page.destination_file(dest_dir, locale)
70
+ if File.exists?(file)
71
+ content = File.read(file)
72
+ else
73
+ file = page.destination_file(dest_dir, I18n.default_locale)
74
+ if File.exists?(file)
75
+ content = File.read(file)
76
+ else
77
+ view = Render::View.new(page, @server.site)
78
+ content = view.render(:text => "No file found at #{file}")
79
+ end
80
+ end
81
+ response.body = content
82
+ else
83
+ request = request.clone
84
+ request.instance_variable_set(:@path_info, "/" + strip_locale(request.path_info))
85
+ @logger.info "Serve static file %s" % File.join(dest_dir, request.path_info)
86
+ super(request, response)
87
+ end
88
+ end
89
+ end
90
+ #rescue Exception => exc
91
+ # @logger.error exc.to_s
92
+ # @logger.error exc.backtrace
93
+ # response.status = 500
94
+ # response.content_type = 'text/text'
95
+ # response.body = exc.to_s + "\n\n\n\n" + exc.backtrace
96
+
97
+ private
98
+
99
+ def strip_locale(path)
100
+ path.sub(/^\/(#{Amber::POSSIBLE_LANGUAGE_CODES.join('|')})(\/|$)/, '').sub(/\/$/, '')
101
+ end
102
+
103
+ def get_locale(request)
104
+ match = /\/(#{Amber::POSSIBLE_LANGUAGE_CODES.join('|')})(\/|$)/.match(request.path)
105
+ if match.nil?
106
+ nil
107
+ else
108
+ match[1]
109
+ end
110
+ end
111
+ end
112
+
113
+ end
data/lib/amber/site.rb ADDED
@@ -0,0 +1,154 @@
1
+ require 'forwardable'
2
+ require 'fileutils'
3
+
4
+ module Amber
5
+ class Site
6
+ extend Forwardable
7
+
8
+ attr_accessor :page_list
9
+ attr_accessor :root
10
+ attr_accessor :continue_on_error
11
+
12
+ def_delegators :@config, :dest_dir, :locales, :default_locale
13
+
14
+ def initialize(root_dir)
15
+ @continue_on_error = true
16
+ @config = SiteConfiguration.load(self, root_dir)
17
+ end
18
+
19
+ def load_pages
20
+ @root = nil
21
+ @pages_by_path = {}
22
+ @pages_by_name = {}
23
+ @page_list = PageArray.new
24
+ @dir_list = []
25
+ @config.mount_points.each do |mp|
26
+ add_mount_point(mp)
27
+ mp.reset_timestamp
28
+ end
29
+ end
30
+
31
+ def reload_pages_if_needed
32
+ if @pages_by_path.nil? || @config.pages_changed?
33
+ puts "Reloading pages ................."
34
+ load_pages
35
+ end
36
+ end
37
+
38
+ def render
39
+ @page_list.each do |page|
40
+ page.render_to_file(@config.dest_dir)
41
+ putc '.'; $stdout.flush
42
+ end
43
+ @dir_list.each do |directory|
44
+ src = File.join(@config.pages_dir, directory)
45
+ dst = File.join(@config.dest_dir, directory)
46
+ Render::Asset.render_dir(src, dst)
47
+ putc '.'; $stdout.flush
48
+ end
49
+ puts
50
+ end
51
+
52
+ def clear
53
+ Dir.glob("#{@config.dest_dir}/*").each do |file|
54
+ FileUtils.rm_r(file)
55
+ end
56
+ end
57
+
58
+ def with_destination(new_dest)
59
+ dest_dir = @config.dest_dir
60
+ @config.dest_dir = new_dest
61
+ yield
62
+ @config.dest_dir = dest_dir
63
+ end
64
+
65
+ def find_pages(filter)
66
+ filter = filter.downcase
67
+ if filter =~ /\//
68
+ path = filter.split('/').map{|segment| segment.gsub(/[^0-9a-z_-]/, '')}
69
+ path_str = path.join('/')
70
+ if (page = @pages_by_path[path_str])
71
+ page
72
+ elsif matched_path = @page_paths.grep(/#{Regexp.escape(path_str)}/).first
73
+ @pages_by_path[matched_path]
74
+ elsif page = @pages_by_name[path.last]
75
+ page
76
+ else
77
+ nil
78
+ end
79
+ else
80
+ @pages_by_name[filter]
81
+ end
82
+ end
83
+
84
+ def find_page(filter)
85
+ find_pages(filter)
86
+ end
87
+
88
+ def all_pages
89
+ @page_list
90
+ end
91
+
92
+ def find_page_by_path(path)
93
+ @pages_by_path[path]
94
+ end
95
+
96
+ def find_page_by_name(name)
97
+ @pages_by_name[name]
98
+ end
99
+
100
+ private
101
+
102
+ def add_mount_point(mp)
103
+ # create base_page
104
+ base_page = begin
105
+ if mp.path == '/'
106
+ @root = StaticPage.new(nil, 'root', mp.pages_dir)
107
+ add_page(@root)
108
+ @root
109
+ else
110
+ name = File.basename(mp.path)
111
+ page = StaticPage.new(find_parent(mp.path), name, mp.pages_dir)
112
+ add_page(page)
113
+ page
114
+ end
115
+ end
116
+ base_page.mount_point = mp
117
+
118
+ # load menu and locals
119
+ I18n.load_path += Dir[File.join(mp.locales_dir, '/*.{rb,yml,yaml}')] if mp.locales_dir
120
+
121
+ # add the full directory tree
122
+ base_page.scan_directory_tree do |page, asset_dir|
123
+ add_page(page) if page
124
+ @dir_list << asset_dir if asset_dir
125
+ end
126
+ @page_paths = @pages_by_path.keys
127
+ end
128
+
129
+ def add_page(page)
130
+ @pages_by_name[page.name] = page
131
+ @pages_by_path[page.path.join('/')] = page
132
+ page.aliases.each do |alias_path|
133
+ if @pages_by_path[alias_path]
134
+ Amber.logger.warn "WARNING: page `#{page.path.join('/')}` has alias `#{alias_path}`, but this path is already taken"
135
+ else
136
+ @pages_by_path[alias_path] = page
137
+ end
138
+ end
139
+ @page_list << page
140
+ end
141
+
142
+ def find_parent(path)
143
+ so_far = []
144
+ path.split('/').compact.each do |path_segment|
145
+ so_far << path_segment
146
+ if page = @pages_by_path[so_far.join('/')]
147
+ return page
148
+ end
149
+ end
150
+ return @root
151
+ end
152
+
153
+ end
154
+ end