broadway 0.0.1

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,31 @@
1
+ module Broadway
2
+ module Helpers
3
+ module PartialHelper
4
+
5
+ def partial(template, options = {})
6
+ template = template.to_s.squeeze("/")
7
+ options.symbolize_keys!
8
+ options.merge!(:layout => false)
9
+ template_array = template.to_s.split('/')
10
+ template = "_#{template_array[-1]}"
11
+ local_template = File.join(request.env["PATH_INFO"][1..-1], template)
12
+ template = local_template if File.exists?(File.join("public", local_template) + ".haml")
13
+ template = template_array[0..-2].join('/') + "/#{template}"
14
+ if collection = options.delete(:collection) then
15
+ collection.inject([]) do |buffer, member|
16
+ buffer << haml(:"#{template}", options.merge(:layout =>
17
+ false, :locals => {template_array[-1].to_sym => member}))
18
+ end.join("\n")
19
+ else
20
+ haml(:"#{template}", options)
21
+ end
22
+ end
23
+
24
+ def theme_partial(template, options = {})
25
+ partial(File.join(c(:theme_path), template.to_s), options)
26
+ end
27
+
28
+ end
29
+ end
30
+ end
31
+
@@ -0,0 +1,78 @@
1
+ module Broadway
2
+ module Helpers
3
+ module TextHelper
4
+
5
+ # config helper method
6
+ def c(path)
7
+ result = site.config
8
+ path.to_s.split(".").each { |node| result = result[node.to_sym] if result }
9
+ result.nil? ? [] : result
10
+ end
11
+
12
+ # read the post and parse it with Liquid
13
+ def read(post, attribute = nil)
14
+ post.content = IO.read(post.path)
15
+ if post.content =~ /^(---\s*\n.*?\n?)^(---\s*$\n?)/m
16
+ if attribute
17
+ output = YAML.load($1)[attribute] || ""
18
+ return output
19
+ end
20
+ post.content = post.content[($1.size + $2.size)..-1]
21
+ end
22
+ post.render(site.layouts, site.site_payload)
23
+ output = post.output
24
+ post.content = post.output = nil
25
+ output
26
+ end
27
+
28
+ def read_excerpt(post, max_length = nil)
29
+ result = read(post, "excerpt")
30
+ result = read(post) if result.nil? || result.empty?
31
+ max_length ||= result.length
32
+ result[0..max_length] + "..."
33
+ result
34
+ end
35
+
36
+ def code(code, options={})
37
+ options[:lang] = extension_to_lang(options[:extension]) if options.has_key?(:extension)
38
+ puts options.inspect
39
+ options[:lang] ||= "plain_text" unless Uv.syntaxes.include?(options[:lang])
40
+ options[:line_numbers] = false unless options.has_key?(:line_numbers)
41
+ options[:theme] ||= "twilight"
42
+ Uv.parse(code, "xhtml", options[:lang], options[:line_numbers], options[:theme])
43
+ end
44
+
45
+ def extension_to_lang(ext)
46
+ {
47
+ "js" => "javascript",
48
+ "rb" => "ruby",
49
+ "as" => "actionscript",
50
+ "mxml" => "mxml",
51
+ "xml" => "xml",
52
+ "css" => "css"
53
+ }[ext.gsub(/\./, "")]
54
+ end
55
+
56
+ def space(times = 1)
57
+ haml_concat("\n" * times)
58
+ end
59
+
60
+ def show_code
61
+ root = File.expand_path("public")
62
+ file = params[:file]
63
+ begin
64
+ path = File.expand_path(File.join(root, file)).untaint
65
+ if (File.dirname(path).split("/").length >= root.split("/").length)
66
+ data = IO.read(path)#.gsub(/[']/, '\\\\\'')
67
+ code(data, :extension => File.extname(path))
68
+ else
69
+ #only happens when someone tries to go outside your root directory...
70
+ "You are way out of your league"
71
+ end
72
+ rescue
73
+ "Internal Error"
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,64 @@
1
+ libdir = File.dirname(__FILE__)
2
+ $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
3
+
4
+ require 'base'
5
+
6
+ @site = site = Broadway.build
7
+
8
+ Sinatra::Application.instance_eval do
9
+ cattr_accessor :site
10
+ self.site = site
11
+ end
12
+
13
+ def site
14
+ @site
15
+ end
16
+
17
+ def default_locals(locals = {})
18
+ locals#.merge(site.config.dup).merge({:menu => menu})
19
+ end
20
+
21
+ def theme_path
22
+ site.config[:theme_path]
23
+ end
24
+
25
+ def pages
26
+ Dir.entries("public")
27
+ end
28
+
29
+ Sinatra::Application.class_eval do
30
+ register SinatraMore::MarkupPlugin
31
+ register SinatraMore::RoutingPlugin
32
+ end
33
+
34
+ def page_path(page)
35
+ content_path(page, "index")
36
+ end
37
+
38
+ def post_path(post)
39
+ content_path(post, "show")
40
+ end
41
+
42
+ def content_path(content, action)
43
+ path = File.join(content.dir, action).squeeze("/")
44
+ exists = false
45
+ extensions = %w(html haml erb html.haml html.erb)
46
+ target_path = File.join(site.config[:source], path)
47
+ extensions.each do |ext|
48
+ exists = true if File.exists?("#{target_path}.#{ext}")
49
+ end
50
+ return path if exists
51
+ checked = [target_path]
52
+ path = target_path = File.join(site.config[:theme_path], path).squeeze("/")
53
+ exists = false
54
+ extensions.each do |ext|
55
+ exists = true if File.exists?(File.join(site.config[:source], "#{target_path}.#{ext}"))
56
+ end
57
+ return path if exists
58
+ if content.url == "/"
59
+ return File.join(site.config[:theme_path], "index")
60
+ end
61
+ checked << target_path
62
+ puts "Couldn't find paths for #{content.class.to_s.downcase.split(":").last} ('#{content.url}'): #{checked.inspect}"
63
+ return nil
64
+ end
@@ -0,0 +1,133 @@
1
+ module Broadway
2
+
3
+ class Page
4
+ include Convertible
5
+
6
+ attr_accessor :site
7
+ attr_accessor :name, :ext, :basename, :dir, :path, :slug
8
+ attr_accessor :data, :content, :output, :categories, :tags, :children
9
+
10
+ # Initialize a new Page.
11
+ # +site+ is the Site
12
+ # +base+ is the String path to the <source>
13
+ # +dir+ is the String path between <source> and the file
14
+ # +name+ is the String filename of the file
15
+ #
16
+ # Returns <Page>
17
+ def initialize(options = {})
18
+ self.site = options[:site]
19
+ self.path = options[:path] if options.has_key?(:path)
20
+ self.data = {}
21
+ self.dir = path =~ /\// ? File.dirname(path.gsub(/#{site.config[:source]}\/?/, "")).gsub(/^\//, "") : path
22
+ self.name = File.basename(self.dir)
23
+ self.ext = File.extname(path)
24
+ self.basename = File.basename(path).split('.')[0..-2].first
25
+
26
+ self.categories ||= []
27
+ self.categories.concat self.dir.split('/').reject { |x| x.empty? }
28
+ self.children ||= []
29
+ process(options) unless options.has_key?(:process) and options[:process] == false
30
+ end
31
+
32
+ # The UID for this post (useful in feeds)
33
+ # e.g. /2008/11/05/my-awesome-post
34
+ #
35
+ # Returns <String>
36
+ def id
37
+ File.join(self.dir, self.name)
38
+ end
39
+
40
+ def menu_title
41
+ self.data && self.data['menu_title']
42
+ end
43
+
44
+ def tooltip
45
+ self.data && self.data["tooltip"]
46
+ end
47
+
48
+ def permalink
49
+ self.data && self.data['permalink']
50
+ end
51
+
52
+ def show_children?
53
+ self.data && (!self.data.has_key?("show_children") || self.data["show_children"] == true)
54
+ end
55
+
56
+ def title
57
+ self.data && (self.data['title'] || self.name)
58
+ end
59
+
60
+ def parent
61
+ url ? site.find_page_by_url(url[1..-1].split("/").first) : nil
62
+ end
63
+
64
+ # The generated relative url of this page
65
+ # e.g. /about
66
+ #
67
+ # Returns <String>
68
+ def url
69
+ return permalink if permalink
70
+ @url ||= {
71
+ "name" => CGI.escape(name),
72
+ "categories" => categories[0..-2].join('/')
73
+ }.inject(template) { |result, token|
74
+ result.gsub(/:#{token.first}/, token.last)
75
+ }.gsub(/#{site.config[:posts]}/, "").squeeze("/")
76
+ end
77
+
78
+ def template
79
+ if self.site.permalink_style == :pretty
80
+ "/:categories/:name"
81
+ else
82
+ "/:categories/:name.html"
83
+ end
84
+ end
85
+
86
+ # Extract information from the page filename
87
+ # +name+ is the String filename of the page file
88
+ #
89
+ # Returns nothing
90
+ def process(options = {})
91
+ self.read_yaml(path)
92
+
93
+ self.tags = self.data.pluralized_array("tag", "tags")
94
+ end
95
+
96
+ # Add any necessary layouts to this post
97
+ # +layouts+ is a Hash of {"name" => "layout"}
98
+ # +site_payload+ is the site payload hash
99
+ #
100
+ # Returns nothing
101
+ def render(layouts, site_payload)
102
+ payload = {"page" => self.data}.deep_merge(site_payload)
103
+ do_layout(payload, layouts)
104
+ end
105
+
106
+ # Write the generated page file to the destination directory.
107
+ # +dest_prefix+ is the String path to the destination dir
108
+ # +dest_suffix+ is a suffix path to the destination dir
109
+ #
110
+ # Returns nothing
111
+ def write(dest_prefix, dest_suffix = nil)
112
+ dest = File.join(dest_prefix, @dir)
113
+ dest = File.join(dest, dest_suffix) if dest_suffix
114
+ FileUtils.mkdir_p(dest)
115
+
116
+ # The url needs to be unescaped in order to preserve the correct filename
117
+ path = File.join(dest, CGI.unescape(self.url))
118
+ if self.ext == '.html' && self.url[/\.html$/].nil?
119
+ FileUtils.mkdir_p(path)
120
+ path = File.join(path, "index.html")
121
+ end
122
+
123
+ File.open(path, 'w') do |f|
124
+ f.write(self.output)
125
+ end
126
+ end
127
+
128
+ def inspect
129
+ "#<Broadway:Page @url=#{self.url.inspect} @name=#{self.name.inspect} @categories=#{self.categories.inspect} @tags=#{self.tags.inspect} @data=#{self.data.inspect}>"
130
+ end
131
+ end
132
+
133
+ end
@@ -0,0 +1,196 @@
1
+ module Broadway
2
+
3
+ class Post
4
+ include Comparable
5
+ include Convertible
6
+ include Resource
7
+
8
+ SRC_MATCHER = /^(.+\/)*(?:(\d+-\d+-\d+)-)?(.*)(\.[^.]+)$/
9
+ URL_MATCHER = /^(.+\/)*(.*)$/
10
+
11
+ # Post name validator. Post filenames must be like:
12
+ # 2008-11-05-my-awesome-post.textile
13
+ #
14
+ # Returns <Bool>
15
+ def self.valid?(name, site)
16
+ site.config[:posts_include].include?(File.extname(name))
17
+ end
18
+
19
+ attr_accessor :site
20
+ # where the file is
21
+ attr_accessor :path, :dir, :name, :basename, :ext
22
+ attr_accessor :data, :content, :output
23
+ attr_accessor :date, :slug, :published, :tags, :categories, :asset
24
+
25
+ # Initialize this Post instance.
26
+ # +site+ is the Site
27
+ # +base+ is the String path to the dir containing the post file
28
+ # +name+ is the String filename of the post file
29
+ # +categories+ is an Array of Strings for the categories for this post
30
+ #
31
+ # Returns <Post>
32
+ def initialize(options = {})
33
+ self.site = options[:site]
34
+ self.path = options[:path] if options.has_key?(:path)
35
+ self.data = {}
36
+
37
+ n, cats, date, slug, ext = *path.match(SRC_MATCHER)
38
+ n, cats, slug = *path.match(URL_MATCHER) unless slug
39
+ self.date = Time.parse(date) if date
40
+ self.slug = slug
41
+ self.ext = ext
42
+ self.dir = options.has_key?(:dir) ? options[:dir] : File.dirname(path.gsub(/#{site.config[:source]}\/?/, ""))
43
+
44
+ self.categories = dir.split('/').reject { |x| x.empty? }
45
+
46
+ process(options) unless options.has_key?(:process) and options[:process] == false
47
+ end
48
+
49
+ # Extract information from the post filename
50
+ # +name+ is the String filename of the post file
51
+ #
52
+ # Returns nothing
53
+ def process(options = {})
54
+ self.read_yaml(path)
55
+
56
+ # If we've added a date and time to the yaml, use that instead of the filename date
57
+ # Means we'll sort correctly.
58
+ self.date = Time.parse(self.data["date"].to_s) if self.data.has_key?('date')
59
+
60
+ if self.data.has_key?('published') && self.data['published'] == false
61
+ self.published = false
62
+ else
63
+ self.published = true
64
+ end
65
+
66
+ if self.data.has_key?("asset")
67
+ data["asset"]["title"] ||= self.title
68
+ self.asset = Asset.new(data["asset"])
69
+ end
70
+
71
+ self.tags = self.data.pluralized_array("tag", "tags")
72
+ self.tags ||= []
73
+ end
74
+
75
+ # Spaceship is based on Post#date, slug
76
+ #
77
+ # Returns -1, 0, 1
78
+ def <=>(other)
79
+ return self.url <=> other.url
80
+ end
81
+
82
+ # The UID for this post (useful in feeds)
83
+ # e.g. /2008/11/05/my-awesome-post
84
+ #
85
+ # Returns <String>
86
+ def id
87
+ File.join(self.dir, self.slug)
88
+ end
89
+
90
+ def parent
91
+ url ? site.find_page_by_url(url[1..-1].split("/").first) : nil
92
+ end
93
+
94
+ # The full path and filename of the post.
95
+ # Defined in the YAML of the post body
96
+ # (Optional)
97
+ #
98
+ # Returns <String>
99
+ def permalink
100
+ self.data && self.data['permalink']
101
+ end
102
+
103
+ def title
104
+ self.data && (self.data['title'] || self.slug)
105
+ end
106
+
107
+ def excerpt
108
+ self.data && self.data["excerpt"]
109
+ end
110
+
111
+ def template
112
+ case self.site.permalink_style
113
+ when :pretty
114
+ "/:categories/:year/:month/:day/:title"
115
+ when :none
116
+ "/:categories/:title.html"
117
+ when :date
118
+ "/:categories/:year/:month/:day/:title.html"
119
+ else
120
+ self.site.permalink_style.to_s
121
+ end
122
+ end
123
+
124
+ # The generated relative url of this post
125
+ # e.g. /2008/11/05/my-awesome-post.html
126
+ #
127
+ # Returns <String>
128
+ def url
129
+ return permalink if permalink
130
+ @url ||= {
131
+ "year" => date ? date.strftime("%Y") : "",
132
+ "month" => date ? date.strftime("%m") : "",
133
+ "day" => date ? date.strftime("%d") : "",
134
+ "title" => CGI.escape(slug),
135
+ "categories" => categories.join('/')
136
+ }.inject(template) { |result, token|
137
+ result.gsub(/:#{token.first}/, token.last)
138
+ }.gsub(/#{site.config[:posts]}/, "").squeeze("/")
139
+ end
140
+
141
+ # Add any necessary layouts to this post
142
+ # +layouts+ is a Hash of {"name" => "layout"}
143
+ # +site_payload+ is the site payload hash
144
+ #
145
+ # Returns nothing
146
+ def render(layouts, site_payload)
147
+ # construct payload
148
+ payload =
149
+ {
150
+ "site" => {},
151
+ "page" => self.to_liquid
152
+ }
153
+ payload = payload.deep_merge(site_payload)
154
+
155
+ do_layout(payload, layouts)
156
+ end
157
+
158
+ # Write the generated post file to the destination directory.
159
+ # +dest+ is the String path to the destination dir
160
+ #
161
+ # Returns nothing
162
+ def write(dest)
163
+ FileUtils.mkdir_p(File.join(dest, dir))
164
+
165
+ # The url needs to be unescaped in order to preserve the correct filename
166
+ path = File.join(dest, CGI.unescape(self.url))
167
+
168
+ if template[/\.html$/].nil?
169
+ FileUtils.mkdir_p(path)
170
+ path = File.join(path, "index.html")
171
+ end
172
+
173
+ File.open(path, 'w') do |f|
174
+ f.write(self.output)
175
+ end
176
+ end
177
+
178
+ # Convert this post into a Hash for use in Liquid templates.
179
+ #
180
+ # Returns <Hash>
181
+ def to_liquid
182
+ { "title" => self.data["title"] || self.slug.split('-').select {|w| w.capitalize! || w }.join(' '),
183
+ "url" => self.url,
184
+ "date" => self.date,
185
+ "id" => self.id,
186
+ "categories" => self.categories,
187
+ "tags" => self.tags,
188
+ "content" => self.content }.deep_merge(self.data)
189
+ end
190
+
191
+ def inspect
192
+ "#<Broadway:Post @url=#{self.url.inspect} @categories=#{self.categories.inspect} @tags=#{self.tags.inspect} @data=#{self.data.inspect}>"
193
+ end
194
+ end
195
+
196
+ end