amber 0.2.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,61 @@
1
+ #
2
+ # Array of StaticPages
3
+ #
4
+
5
+ require 'bigdecimal'
6
+
7
+ module Amber
8
+ class PageArray < Array
9
+
10
+ def limit(num)
11
+ PageArray.new(self[0..(num-1)])
12
+ end
13
+
14
+ #
15
+ # available options:
16
+ #
17
+ # :locale -- the locale to use when comparing attributes
18
+ # :direction -- either :asc or :desc
19
+ # :numeric -- if true, attributes are cast as numbers before comparison
20
+ #
21
+ def order_by(attr, options={})
22
+ locale = options[:locale] || I18n.locale
23
+ direction = options[:direction] || :asc
24
+ array = sort do |a,b|
25
+ if direction == :desc
26
+ a, b = b, a
27
+ end
28
+ a_prop = a.prop(locale, attr)
29
+ b_prop = b.prop(locale, attr)
30
+ if options[:numeric]
31
+ a_prop = to_numeric(a_prop)
32
+ b_prop = to_numeric(b_prop)
33
+ end
34
+ if a_prop.nil? && b_prop.nil?
35
+ 0
36
+ elsif a_prop.nil?
37
+ 1
38
+ elsif b_prop.nil?
39
+ -1
40
+ else
41
+ a_prop <=> b_prop
42
+ end
43
+ end
44
+ # remove pages from the results that have no value set for the attr
45
+ array.delete_if do |page|
46
+ page.prop(locale, attr).nil?
47
+ end
48
+ return PageArray.new.replace array
49
+ end
50
+
51
+ def to_numeric(anything)
52
+ num = BigDecimal.new(anything.to_s)
53
+ if num.frac == 0
54
+ num.to_i
55
+ else
56
+ num.to_f
57
+ end
58
+ end
59
+
60
+ end
61
+ end
@@ -0,0 +1,55 @@
1
+ #
2
+ # For rendering things that are assets, like sass files.
3
+ # Maybe images too?
4
+ #
5
+
6
+ require 'sass'
7
+
8
+ module Amber
9
+ module Render
10
+ class Asset
11
+
12
+ RENDER_MAP = {
13
+ '.sass' => {:method => 'render_sass', :new_suffix => '.css', :args => [:sass]},
14
+ '.scss' => {:method => 'render_sass', :new_suffix => '.css', :args => [:scss]}
15
+ }
16
+
17
+ def self.render(src_file, dst_file)
18
+ unless Dir.exists?(File.dirname(dst_file))
19
+ FileUtils.mkdir_p(File.dirname(dst_file))
20
+ end
21
+ File.unlink(dst_file) if File.exists?(dst_file)
22
+ src_ext = File.extname(src_file)
23
+ renderer = RENDER_MAP[src_ext]
24
+ if renderer
25
+ content = self.send(renderer[:method], *([src_file] + renderer[:args]))
26
+ new_dst_file = dst_file.sub(/#{Regexp.escape(src_ext)}$/, renderer[:new_suffix])
27
+ File.open(new_dst_file,'w') do |w|
28
+ w.write(content)
29
+ end
30
+ else
31
+ File.link(src_file, dst_file)
32
+ end
33
+ rescue SystemCallError => exc
34
+ Amber.log_exception(exc)
35
+ end
36
+
37
+ def self.render_dir(src_dir, dst_dir)
38
+ Dir.chdir(src_dir) do
39
+ Dir.glob('*').each do |file|
40
+ next if File.directory?(file) || file =~ /^\./
41
+ src_file = File.join(src_dir, file)
42
+ dst_file = File.join(dst_dir, file)
43
+ render(src_file, dst_file)
44
+ end
45
+ end
46
+ end
47
+
48
+ def self.render_sass(src_file, syntax)
49
+ engine = Sass::Engine.new(File.read(src_file), :syntax => syntax)
50
+ engine.render
51
+ end
52
+
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,78 @@
1
+ # encoding: utf-8
2
+ # adapted from https://github.com/tenderlove/rails_autolink
3
+ # MIT license
4
+
5
+ module Amber::Render::Autolink
6
+
7
+ def self.auto_link(text)
8
+ auto_link_email_addresses(auto_link_urls(text))
9
+ end
10
+
11
+ private
12
+
13
+ AUTO_LINK_RE = %r{
14
+ (?: ((?:ed2k|ftp|http|https|irc|mailto|news|gopher|nntp|telnet|webcal|xmpp|callto|feed|svn|urn|aim|rsync|tag|ssh|sftp|rtsp|afs|file):)// | www\. )
15
+ [^\s<\u00A0]+
16
+ }ix
17
+
18
+ # regexps for determining context, used high-volume
19
+ AUTO_LINK_CRE = [/<[^>]+$/, /^[^>]*>/, /<a\b.*?>/i, /<\/a>/i]
20
+
21
+ AUTO_EMAIL_LOCAL_RE = /[\w.!#\$%&'*\/=?^`{|}~+-]/
22
+ AUTO_EMAIL_RE = /[\w.!#\$%+-]\.?#{AUTO_EMAIL_LOCAL_RE}*@[\w-]+(?:\.[\w-]+)+/
23
+
24
+ BRACKETS = { ']' => '[', ')' => '(', '}' => '{' }
25
+
26
+ WORD_PATTERN = RUBY_VERSION < '1.9' ? '\w' : '\p{Word}'
27
+
28
+ # Turns all urls into clickable links. If a block is given, each url
29
+ # is yielded and the result is used as the link text.
30
+ def self.auto_link_urls(text)
31
+ text.gsub(AUTO_LINK_RE) do
32
+ scheme, href = $1, $&
33
+ punctuation = []
34
+
35
+ if auto_linked?($`, $')
36
+ # do not change string; URL is already linked
37
+ href
38
+ else
39
+ # don't include trailing punctuation character as part of the URL
40
+ while href.sub!(/[^#{WORD_PATTERN}\/-]$/, '')
41
+ punctuation.push $&
42
+ if opening = BRACKETS[punctuation.last] and href.scan(opening).size > href.scan(punctuation.last).size
43
+ href << punctuation.pop
44
+ break
45
+ end
46
+ end
47
+
48
+ #link_text = block_given?? yield(href) : href
49
+ link_text = href.sub(/^#{scheme}\/\//,'')
50
+ href = 'http://' + href unless scheme
51
+ %(<a href="#{href}">#{link_text}</a>) + punctuation.reverse.join('')
52
+ end
53
+ end
54
+ end
55
+
56
+ # Turns all email addresses into clickable links.
57
+ def self.auto_link_email_addresses(text)
58
+ text.gsub(AUTO_EMAIL_RE) do
59
+ text = $&
60
+
61
+ if auto_linked?($`, $')
62
+ text
63
+ else
64
+ #display_text = (block_given?) ? yield(text) : text
65
+ #display_text = text
66
+ text.gsub!('@', '&#064').gsub!('.', '&#046;')
67
+ %(<a href="mailto:#{text}">#{text}</a>)
68
+ end
69
+ end
70
+ end
71
+
72
+ # Detects already linked context or position in the middle of a tag
73
+ def self.auto_linked?(left, right)
74
+ (left =~ AUTO_LINK_CRE[0] and right =~ AUTO_LINK_CRE[1]) or
75
+ (left.rindex(AUTO_LINK_CRE[2]) and $' !~ AUTO_LINK_CRE[3])
76
+ end
77
+
78
+ end
@@ -0,0 +1,33 @@
1
+ # encoding: utf-8
2
+
3
+ #
4
+ # bracket links are links in the form [[label => target]] or [[page-name]]
5
+ #
6
+
7
+ module Amber::Render::Bracketlink
8
+
9
+ # linking using double square brackets
10
+ BRACKET_LINK_RE = /
11
+ \[\[ # start [[
12
+ ([^\[\]]+) # $text : one or more characters that are not [ or ] ($1)
13
+ \]\] # end ]]
14
+ /x
15
+
16
+ def self.bracket_link(text, &block)
17
+ text.gsub(BRACKET_LINK_RE) do |m|
18
+ link_text = $~[1].strip
19
+ if link_text =~ /^.+\s*[-=]>\s*.+$/
20
+ # link_text == "from -> to"
21
+ from, to = link_text.split(/\s*[-=]>\s*/)[0..1]
22
+ from = "" unless from.instance_of? String # \ sanity check for
23
+ to = "" unless from.instance_of? String # / badly formed links
24
+ else
25
+ # link_text == "to" (ie, no link label)
26
+ from = nil
27
+ to = link_text
28
+ end
29
+ yield(from, to)
30
+ end
31
+ end
32
+
33
+ end
@@ -0,0 +1,54 @@
1
+ require 'pathname'
2
+
3
+ module HamlHelper
4
+
5
+ #
6
+ # acts like haml_tag, capture_haml, or haml_concat, depending on how it is called.
7
+ #
8
+ # two or more args --> like haml_tag
9
+ # one arg and a block --> like haml_tag
10
+ # zero args and a block --> like capture_haml
11
+ # one arg and no block --> like haml_concat
12
+ #
13
+ # additionally, we allow the use of more than one class.
14
+ #
15
+ # some examples of these usages:
16
+ #
17
+ # def display_robot(robot)
18
+ # haml do # like capture_haml
19
+ # haml '.head', robot.head_html # like haml_tag
20
+ # haml '.head' do # same
21
+ # haml robot.head_html
22
+ # end
23
+ # haml '.body.metal', robot.body_html # like haml_tag, but with multiple classes
24
+ # haml '<a href="/x">link</a>' # like haml_concat
25
+ # end
26
+ # end
27
+ #
28
+ # wrapping the helper in a capture_haml call is very useful, because then
29
+ # the helper can be used wherever a normal helper would be.
30
+ #
31
+ def haml(name=nil, *args, &block)
32
+ if name
33
+ if args.empty? and block.nil?
34
+ haml_concat name
35
+ else
36
+ if name =~ /^(.*?\.[^\.]*)(\..*)$/
37
+ # allow chaining of classes if there are multiple '.' in the first arg
38
+ name = $1
39
+ classes = $2.gsub('.',' ')
40
+ hsh = args.detect{|i| i.is_a?(Hash)}
41
+ unless hsh
42
+ hsh = {}
43
+ args << hsh
44
+ end
45
+ hsh[:class] = classes
46
+ end
47
+ haml_tag(name, *args, &block)
48
+ end
49
+ else
50
+ capture_haml(&block)
51
+ end
52
+ end
53
+
54
+ end
@@ -0,0 +1,75 @@
1
+ module Amber
2
+ module Render
3
+ module HtmlHelper
4
+
5
+ def html_head_base
6
+ href = (['..'] * @page.path.count).join('/')
7
+ "<base href=\"#{href}\" />"
8
+ end
9
+
10
+ #
11
+ # three forms:
12
+ #
13
+ # (1) link('page-name')
14
+ # (2) link('label' => 'page-name')
15
+ # (3) link('label' => 'https://url')
16
+ #
17
+ # both accept optional options hash:
18
+ #
19
+ # (1) link('page-name', :class => 'x')
20
+ # (2) link('label' => 'page-name', :class => 'x')
21
+ #
22
+ def link(name, options=nil)
23
+ options = nil if options && !options.is_a?(Hash)
24
+ if name.is_a? Hash
25
+ klass = name.delete(:class)
26
+ label, name = name.to_a.first
27
+ if label.is_a? Symbol
28
+ label = I18n.t label
29
+ end
30
+ else
31
+ klass = options[:class] if options
32
+ end
33
+ if name =~ /^#/ || name =~ /^http/ || name =~ /\./
34
+ path = name
35
+ label ||= name
36
+ else
37
+ if index = name.index('#')
38
+ anchor = name[index..-1]
39
+ name_without_anchor = name[0..(index-1)]
40
+ else
41
+ anchor = ''
42
+ name_without_anchor = name
43
+ end
44
+ page = @site.find_page(name_without_anchor)
45
+ if page
46
+ label ||= page.nav_title
47
+ path = page_path(page) + anchor
48
+ else
49
+ puts "warning: dead link to `#{name_without_anchor}` from page `/#{I18n.locale}/#{@page.path.join('/')}`"
50
+ label ||= name_without_anchor
51
+ label += ' [dead link]'
52
+ path = name_without_anchor
53
+ end
54
+ end
55
+ if klass
56
+ %(<a href="#{path}" class="#{klass}">#{label}</a>)
57
+ else
58
+ %(<a href="#{path}">#{label}</a>)
59
+ end
60
+ end
61
+
62
+ #
63
+ # returns the shortest possible path. this would be nice to support some day, but more difficult with statically rendered sites.
64
+ #
65
+ def page_path(page, locale=I18n.locale)
66
+ if page.prop(locale, :alias)
67
+ "/#{locale}/#{page.prop(locale, :alias).first}/#{page.name}"
68
+ else
69
+ "/#{locale}/#{page.path.join('/')}"
70
+ end
71
+ end
72
+
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,25 @@
1
+ module Amber
2
+ module Render
3
+ module LanguageHelper
4
+
5
+ def t(*args)
6
+ I18n.t(*args)
7
+ end
8
+
9
+ def translation_missing?
10
+ !@page.content_file_exists?(I18n.locale)
11
+ end
12
+
13
+ # return array of arrays, each array with: language_name, language_code, current_url_with_locale_switch
14
+ #
15
+ # [ ['English', :en, 'en/about-us'] ]
16
+ #
17
+ def available_languages
18
+ @site.locales.collect { |locale|
19
+ [Amber::POSSIBLE_LANGUAGES[locale][0], locale, "/"+([locale]+current_page_path).join('/')]
20
+ }
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,203 @@
1
+ module Amber
2
+ module Render
3
+ module NavigationHelper
4
+
5
+ def has_navigation?
6
+ if current_page_path.empty? || @site.menu.nil?
7
+ false
8
+ else
9
+ submenu = @site.menu.submenu(current_page_path.first)
10
+ if submenu
11
+ second_level_children_count = submenu.size
12
+ if second_level_children_count.nil?
13
+ false
14
+ else
15
+ second_level_children_count >= 1
16
+ end
17
+ else
18
+ false
19
+ end
20
+ end
21
+ end
22
+
23
+ #
24
+ # yields each item
25
+ #
26
+ def top_navigation_items(options={})
27
+ if !@site.menu
28
+ yield({})
29
+ else
30
+ first = 'first'
31
+ if options[:include_home]
32
+ active = current_page_path.empty? ? 'active' : ''
33
+ yield({:class => [first, active].compact.join(' '), :href => menu_item_path(@site.menu), :label => menu_item_title(@site.menu)})
34
+ first = nil
35
+ end
36
+ @site.menu.each do |item|
37
+ active = current_page_path.first == item.name ? 'active' : ''
38
+ yield({:class => [first, active].compact.join(' '), :href => menu_item_path(item), :label => menu_item_title(item)})
39
+ first = nil
40
+ end
41
+ end
42
+ end
43
+
44
+ #
45
+ # yields each item
46
+ #
47
+ def navigation_items(menu=nil, level=1, &block)
48
+ if menu.nil?
49
+ menu = site.menu.submenu(current_page_path.first)
50
+ end
51
+ if menu
52
+ menu.each do |item|
53
+ title = menu_item_title(item)
54
+ if title
55
+ yield({
56
+ :href => menu_item_path(item),
57
+ :level => level,
58
+ :active => path_active_class(current_page_path, item),
59
+ :label => title
60
+ })
61
+ end
62
+ if path_open?(current_page_path, item)
63
+ navigation_items(item.submenu, level+1, &block)
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+ def current_page_path
70
+ @current_page_path ||= begin
71
+ if @page
72
+ @page.path
73
+ #elsif params[:page].is_a? String
74
+ # params[:page].split('/')
75
+ else
76
+ []
77
+ end
78
+ end
79
+ end
80
+
81
+ def menu_item_path(item)
82
+ "/" + ([I18n.locale]+item.path).join('/')
83
+ end
84
+
85
+ def menu_item_title(item)
86
+ page = @site.find_page_by_path(item.path_str) || @site.find_page_by_name(item.name)
87
+ if page
88
+ page.nav_title(I18n.locale)
89
+ else
90
+ nil
91
+ end
92
+ end
93
+
94
+ #
95
+ # inserts an directory index built from the page's children
96
+ #
97
+ # options:
98
+ # :levels -- the max levels to descend, default is 1
99
+ # :page -- StaticPage instance or nil
100
+ # :include_toc -- true or false
101
+ # :order_by -- arguments to PageArray#order_by
102
+ # :heading -- heading level to use
103
+ #
104
+ def child_summaries(options={})
105
+ page = options.delete(:page) || @page
106
+ unless page.is_a?(StaticPage)
107
+ page = @site.find_pages(page)
108
+ end
109
+ return "" if page.nil? or page.children.empty?
110
+
111
+ levels_max = options[:levels] || 1
112
+ level = options.delete(:level) || 1
113
+ heading = options.delete(:heading) || 2
114
+ locale = @locals[:locale]
115
+ menu = submenu_for_page(page)
116
+ if menu && menu.children.any?
117
+ children = menu.children
118
+ elsif options[:order_by]
119
+ children = page.children.order_by(*options[:order_by])
120
+ else
121
+ children = page.children
122
+ end
123
+
124
+ haml do
125
+ children.each do |child|
126
+ child_page = child.is_a?(Amber::Menu) ? page.child(child.name) : child
127
+ next unless child_page
128
+ haml "h#{heading}" do
129
+ haml :a, child_page.nav_title(locale), :href => page_path(child_page)
130
+ end
131
+ if summary = child_page.prop(locale, 'summary')
132
+ haml :p, summary
133
+ end
134
+ if options[:include_toc]
135
+ toc_html = render_toc(child_page, :locale => locale)
136
+ haml toc_html
137
+ end
138
+ if level < levels_max
139
+ haml(child_summaries({
140
+ :page => child_page,
141
+ :levels => levels_max,
142
+ :level => level+1,
143
+ :include_toc => options[:include_toc],
144
+ :order_by => options[:order_by],
145
+ :heading => heading+1
146
+ }))
147
+ end
148
+ end
149
+ end
150
+ rescue Exception => exc
151
+ Amber.log_exception(exc)
152
+ end
153
+
154
+ private
155
+
156
+ #
157
+ # returns string 'active', 'semi-active', or ''
158
+ #
159
+ def path_active_class(page_path, menu_item)
160
+ active = ''
161
+ if menu_item.path == page_path
162
+ active = 'active'
163
+ elsif menu_item.path_prefix_of?(page_path)
164
+ if menu_item.leaf_for_path?(page_path)
165
+ active = 'active'
166
+ else
167
+ active = 'semi-active'
168
+ end
169
+ end
170
+ active
171
+ end
172
+
173
+ #
174
+ # returns true if menu_item represents an parent of page
175
+ #
176
+ def path_open?(page_path, menu_item)
177
+ menu_item.path == page_path || menu_item.path_prefix_of?(page_path)
178
+ end
179
+
180
+ def submenu_for_page(page)
181
+ menu = @site.menu
182
+ page.path.each do |segment|
183
+ menu = menu.submenu(segment)
184
+ end
185
+ return menu
186
+ rescue
187
+ return nil
188
+ end
189
+
190
+ end
191
+ end
192
+ end
193
+
194
+
195
+ =begin
196
+
197
+ def act_as(page)
198
+ page = site.find_page(page)
199
+ @current_page_path = page.path
200
+ page_body(page)
201
+ end
202
+
203
+ =end