amber 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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