broadway 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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