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,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