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.
- data/README.textile +300 -0
- data/Rakefile +80 -0
- data/lib/broadway.rb +7 -0
- data/lib/broadway/api.rb +51 -0
- data/lib/broadway/asset.rb +17 -0
- data/lib/broadway/base.rb +121 -0
- data/lib/broadway/convertible.rb +89 -0
- data/lib/broadway/core_ext.rb +93 -0
- data/lib/broadway/helpers.rb +7 -0
- data/lib/broadway/helpers/collection_helper.rb +152 -0
- data/lib/broadway/helpers/partial_helper.rb +31 -0
- data/lib/broadway/helpers/text_helper.rb +78 -0
- data/lib/broadway/main.rb +64 -0
- data/lib/broadway/page.rb +133 -0
- data/lib/broadway/post.rb +196 -0
- data/lib/broadway/resource.rb +5 -0
- data/lib/broadway/runner.rb +49 -0
- data/lib/broadway/site.rb +398 -0
- data/lib/broadway/static_file.rb +32 -0
- metadata +119 -0
@@ -0,0 +1,121 @@
|
|
1
|
+
def files(path, from = __FILE__, &block)
|
2
|
+
Dir.glob(File.join(File.dirname(from), path)) {|file| yield file}
|
3
|
+
end
|
4
|
+
|
5
|
+
def require_local(path, from = __FILE__)
|
6
|
+
files(path, from) {|file| require file}
|
7
|
+
end
|
8
|
+
|
9
|
+
require "rubygems"
|
10
|
+
require 'redcloth'
|
11
|
+
require 'nokogiri'
|
12
|
+
require 'active_support'
|
13
|
+
require 'action_mailer'
|
14
|
+
require 'action_view'
|
15
|
+
require 'action_view/base'
|
16
|
+
|
17
|
+
# core
|
18
|
+
require 'fileutils'
|
19
|
+
require 'time'
|
20
|
+
require 'yaml'
|
21
|
+
require 'nokogiri'
|
22
|
+
require 'cgi'
|
23
|
+
require 'sinatra'
|
24
|
+
require 'sinatra/base'
|
25
|
+
require 'sinatra_more'
|
26
|
+
require 'haml'
|
27
|
+
|
28
|
+
# stdlib
|
29
|
+
|
30
|
+
# 3rd party
|
31
|
+
require 'redcloth'
|
32
|
+
|
33
|
+
$:.push(File.dirname(__FILE__))
|
34
|
+
require 'core_ext'
|
35
|
+
require 'convertible'
|
36
|
+
require 'resource'
|
37
|
+
require 'site'
|
38
|
+
require 'asset'
|
39
|
+
require 'page'
|
40
|
+
require 'post'
|
41
|
+
require 'static_file'
|
42
|
+
require 'runner'
|
43
|
+
require "helpers"
|
44
|
+
|
45
|
+
module Broadway
|
46
|
+
VERSION = "0.0.1"
|
47
|
+
# Default options. Overriden by values in _config.yml or command-line opts.
|
48
|
+
# (Strings rather symbols used for compatability with YAML)
|
49
|
+
DEFAULTS = {
|
50
|
+
:auto => false,
|
51
|
+
:server => false,
|
52
|
+
:server_port => 4000,
|
53
|
+
|
54
|
+
:source => '.',
|
55
|
+
:destination => File.join('..', '_posts'),
|
56
|
+
|
57
|
+
:lsi => false,
|
58
|
+
:pygments => false,
|
59
|
+
:markdown => 'maruku',
|
60
|
+
:permalink => 'pretty',
|
61
|
+
:url_type => 'relative',
|
62
|
+
:url => 'http://localhost:4567',
|
63
|
+
:language => 'en-US',
|
64
|
+
|
65
|
+
:maruku => {
|
66
|
+
:use_tex => false,
|
67
|
+
:use_divs => false,
|
68
|
+
:png_engine => 'blahtex',
|
69
|
+
:png_dir => 'images/latex',
|
70
|
+
:png_url => '/images/latex'
|
71
|
+
},
|
72
|
+
|
73
|
+
:layouts => "_layouts",
|
74
|
+
:posts => "",#"_posts"
|
75
|
+
:posts_include => [".textile", ".markdown"]
|
76
|
+
}
|
77
|
+
|
78
|
+
# Generate a Broadway configuration Hash by merging the default options
|
79
|
+
# with anything in _config.yml, and adding the given options on top
|
80
|
+
# +override+ is a Hash of config directives
|
81
|
+
#
|
82
|
+
# Returns Hash
|
83
|
+
def self.configuration(override)
|
84
|
+
# _config.yml may override default source location, but until
|
85
|
+
# then, we need to know where to look for _config.yml
|
86
|
+
source = override[:source] || Broadway::DEFAULTS[:source]
|
87
|
+
|
88
|
+
# Get configuration from <source>/_config.yml
|
89
|
+
config_file = File.join(source, '_config.yml')
|
90
|
+
begin
|
91
|
+
config = YAML.load_file(config_file)
|
92
|
+
raise "Invalid configuration - #{config_file}" if !config.is_a?(Hash)
|
93
|
+
$stdout.puts "Configuration from #{config_file}"
|
94
|
+
rescue => err
|
95
|
+
$stderr.puts "WARNING: Could not read configuration. Using defaults (and options)."
|
96
|
+
$stderr.puts "\t" + err.to_s
|
97
|
+
config = {}
|
98
|
+
end
|
99
|
+
|
100
|
+
# Merge DEFAULTS < _config.yml < override
|
101
|
+
Broadway::DEFAULTS.deep_merge(config).deep_merge(override)
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.build(options = {})
|
105
|
+
self.process(:build, options)
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.generate(options = {})
|
109
|
+
self.process(:process, options)
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.process(method, options = {})
|
113
|
+
options = Broadway.configuration(options)
|
114
|
+
|
115
|
+
site = Broadway::Site.new(options)
|
116
|
+
|
117
|
+
site.send method
|
118
|
+
|
119
|
+
site
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# Convertible provides methods for converting a pagelike item
|
2
|
+
# from a certain type of markup into actual content
|
3
|
+
#
|
4
|
+
# Requires
|
5
|
+
# self.site -> Jekyll::Site
|
6
|
+
# self.content=
|
7
|
+
# self.data=
|
8
|
+
# self.ext=
|
9
|
+
# self.output=
|
10
|
+
require 'liquid'
|
11
|
+
module Broadway
|
12
|
+
module Convertible
|
13
|
+
# Return the contents as a string
|
14
|
+
def to_s
|
15
|
+
self.content || ''
|
16
|
+
end
|
17
|
+
|
18
|
+
# Read the YAML frontmatter
|
19
|
+
# +base+ is the String path to the dir containing the file
|
20
|
+
# +name+ is the String filename of the file
|
21
|
+
# You can also get the content from xml, so this might not need to run
|
22
|
+
# Returns nothing
|
23
|
+
def read_yaml(path)
|
24
|
+
return if !File.exists?(path) || File.directory?(path)
|
25
|
+
|
26
|
+
self.content = IO.read(path)
|
27
|
+
|
28
|
+
if self.content =~ /^(---\s*\n.*?\n?)^(---\s*$\n?)/m
|
29
|
+
self.content = self.content[($1.size + $2.size)..-1]
|
30
|
+
|
31
|
+
self.data.merge!(YAML.load($1))
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Transform the contents based on the file extension.
|
36
|
+
#
|
37
|
+
# Returns nothing
|
38
|
+
def transform
|
39
|
+
case self.content_type
|
40
|
+
when 'textile'
|
41
|
+
self.ext = ".html"
|
42
|
+
self.content = self.site.textile(self.content)
|
43
|
+
when 'markdown'
|
44
|
+
self.ext = ".html"
|
45
|
+
self.content = self.site.markdown(self.content)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Determine which formatting engine to use based on this convertible's
|
50
|
+
# extension
|
51
|
+
#
|
52
|
+
# Returns one of :textile, :markdown or :unknown
|
53
|
+
def content_type
|
54
|
+
case self.ext[1..-1]
|
55
|
+
when /textile/i
|
56
|
+
return 'textile'
|
57
|
+
when /markdown/i, /mkdn/i, /md/i, /mkd/i
|
58
|
+
return 'markdown'
|
59
|
+
end
|
60
|
+
return 'unknown'
|
61
|
+
end
|
62
|
+
|
63
|
+
# Add any necessary layouts to this convertible document
|
64
|
+
# +layouts+ is a Hash of {"name" => "layout"}
|
65
|
+
# +site_payload+ is the site payload hash
|
66
|
+
#
|
67
|
+
# Returns nothing
|
68
|
+
def do_layout(payload, layouts)
|
69
|
+
info = { :filters => [], :registers => { :site => self.site } }
|
70
|
+
|
71
|
+
# render and transform content (this becomes the final content of the object)
|
72
|
+
payload["content_type"] = self.content_type
|
73
|
+
self.content = Liquid::Template.parse(self.content).render(payload, info)
|
74
|
+
self.transform
|
75
|
+
|
76
|
+
# output keeps track of what will finally be written
|
77
|
+
self.output = self.content
|
78
|
+
|
79
|
+
# recursively render layouts
|
80
|
+
layout = layouts[self.data["layout"]]
|
81
|
+
while layout
|
82
|
+
payload = payload.deep_merge({"content" => self.output, "page" => layout.data})
|
83
|
+
self.output = Liquid::Template.parse(layout.content).render(payload, info)
|
84
|
+
|
85
|
+
layout = layouts[layout.data["layout"]]
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
class Hash
|
2
|
+
def recursive_symbolize_keys!
|
3
|
+
self.symbolize_keys!
|
4
|
+
self.values.each do |v|
|
5
|
+
if v.is_a? Hash
|
6
|
+
v.recursive_symbolize_keys!
|
7
|
+
elsif v.is_a? Array
|
8
|
+
v.recursive_symbolize_keys!
|
9
|
+
end
|
10
|
+
end
|
11
|
+
self
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Array
|
16
|
+
def recursive_symbolize_keys!
|
17
|
+
self.each do |item|
|
18
|
+
if item.is_a? Hash
|
19
|
+
item.recursive_symbolize_keys!
|
20
|
+
elsif item.is_a? Array
|
21
|
+
item.recursive_symbolize_keys!
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# http://snippets.dzone.com/posts/show/3486
|
27
|
+
def chunk(pieces)
|
28
|
+
q, r = length.divmod(pieces)
|
29
|
+
(0..pieces).map { |i| i * q + [r, i].min }.enum_cons(2).map { |a, b| slice(a...b) }
|
30
|
+
end
|
31
|
+
|
32
|
+
def / len
|
33
|
+
a = []
|
34
|
+
each_with_index do |x,i|
|
35
|
+
a << [] if i % len == 0
|
36
|
+
a.last << x
|
37
|
+
end
|
38
|
+
a
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class Hash
|
43
|
+
# Merges self with another hash, recursively.
|
44
|
+
#
|
45
|
+
# This code was lovingly stolen from some random gem:
|
46
|
+
# http://gemjack.com/gems/tartan-0.1.1/classes/Hash.html
|
47
|
+
#
|
48
|
+
# Thanks to whoever made it.
|
49
|
+
def deep_merge(hash)
|
50
|
+
target = dup
|
51
|
+
|
52
|
+
hash.keys.each do |key|
|
53
|
+
if hash[key].is_a? Hash and self[key].is_a? Hash
|
54
|
+
target[key] = target[key].deep_merge(hash[key])
|
55
|
+
next
|
56
|
+
end
|
57
|
+
|
58
|
+
target[key] = hash[key]
|
59
|
+
end
|
60
|
+
|
61
|
+
target
|
62
|
+
end
|
63
|
+
|
64
|
+
# Read array from the supplied hash favouring the singular key
|
65
|
+
# and then the plural key, and handling any nil entries.
|
66
|
+
# +hash+ the hash to read from
|
67
|
+
# +singular_key+ the singular key
|
68
|
+
# +plural_key+ the singular key
|
69
|
+
#
|
70
|
+
# Returns an array
|
71
|
+
def pluralized_array(singular_key, plural_key)
|
72
|
+
hash = self
|
73
|
+
if hash.has_key?(singular_key)
|
74
|
+
array = [hash[singular_key]] if hash[singular_key]
|
75
|
+
elsif hash.has_key?(plural_key)
|
76
|
+
case hash[plural_key]
|
77
|
+
when String
|
78
|
+
array = hash[plural_key].split
|
79
|
+
when Array
|
80
|
+
array = hash[plural_key].compact
|
81
|
+
end
|
82
|
+
end
|
83
|
+
array || []
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Thanks, ActiveSupport!
|
88
|
+
class Date
|
89
|
+
# Converts datetime to an appropriate format for use in XML
|
90
|
+
def xmlschema
|
91
|
+
strftime("%Y-%m-%dT%H:%M:%S%Z")
|
92
|
+
end if RUBY_VERSION < '1.9'
|
93
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
module Broadway
|
2
|
+
module Helpers
|
3
|
+
module CollectionHelper
|
4
|
+
|
5
|
+
# comment from http://openmonkey.com/articles/2009/02/a-cycle-helper-for-sinatra
|
6
|
+
# You can then call it like this:
|
7
|
+
# .user_badge{ :class => cycle('normal', 'normal', 'last') }
|
8
|
+
def cycle(*states)
|
9
|
+
states[@_cycle = ((@_cycle || -1) + 1) % states.size]
|
10
|
+
end
|
11
|
+
|
12
|
+
def grid_for(array, options = {}, &block)
|
13
|
+
return "" if array.empty?
|
14
|
+
columns = options[:columns] || 3
|
15
|
+
(array / columns).each do |row|
|
16
|
+
yield row
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def row_for(array, options = {}, &block)
|
21
|
+
return "" if array.empty?
|
22
|
+
array.each_with_index do |node, i|
|
23
|
+
attributes = options.has_key?(:li_attributes) ? options[:li_attributes] : {}
|
24
|
+
attributes[:class] ||= ""
|
25
|
+
if i == 0 and options.has_key?(:first)
|
26
|
+
attributes[:class] << "#{options[:first]}"
|
27
|
+
elsif i == array.length - 1 and options.has_key?(:last)
|
28
|
+
attributes[:class] << "#{options[:last]}"
|
29
|
+
end
|
30
|
+
haml_tag :li, attributes do
|
31
|
+
if block_given?
|
32
|
+
yield node, i
|
33
|
+
else
|
34
|
+
haml_concat node.title
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def menu_for(item, options = {}, &block)
|
41
|
+
if item.is_a?(Site)
|
42
|
+
menu_tag(item.tree, options, &block)
|
43
|
+
elsif item.is_a?(Page)
|
44
|
+
menu_tag(item.children, options, &block)
|
45
|
+
elsif item.is_a?(Post)
|
46
|
+
""
|
47
|
+
else
|
48
|
+
menu_tag(item, options, &block)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# - menu_tag(c(:menu)) do |attributes, node|
|
53
|
+
# - attributes[:class] = "one two"
|
54
|
+
# - node[:title]
|
55
|
+
def menu_tag(array, options = {}, &block)
|
56
|
+
return "" if array.empty?
|
57
|
+
menu_attributes = nil
|
58
|
+
if options.has_key?(:menu_attributes)
|
59
|
+
menu_attributes = options[:menu_attributes]
|
60
|
+
options.delete(:menu_attributes)
|
61
|
+
end
|
62
|
+
haml_tag :ul, menu_attributes do
|
63
|
+
array.each_with_index do |node, i|
|
64
|
+
attributes = options.has_key?(:li_attributes) ? options[:li_attributes] : {}
|
65
|
+
attributes[:class] ||= ""
|
66
|
+
if i == 0 and options.has_key?(:first)
|
67
|
+
attributes[:class] << " #{options[:first]}"
|
68
|
+
elsif i == array.length - 1 and options.has_key?(:last)
|
69
|
+
attributes[:class] << " #{options[:last]}"
|
70
|
+
end
|
71
|
+
haml_tag :li, attributes do
|
72
|
+
if block_given?
|
73
|
+
yield node
|
74
|
+
else
|
75
|
+
haml_concat node.title
|
76
|
+
end
|
77
|
+
next if (options.has_key?(:show_children) and options[:show_children] == false)
|
78
|
+
next unless (node.respond_to?(:children) and node.show_children?)
|
79
|
+
|
80
|
+
menu_tag(node.children, options, &block)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def breadcrumb_for(item, options = {}, &block)
|
87
|
+
options[:from] ||= "/" + item.url.gsub(/^\//, "").split("/").first
|
88
|
+
options[:to] ||= item.url
|
89
|
+
breadcrumb(options[:from], options, &block)
|
90
|
+
end
|
91
|
+
|
92
|
+
def breadcrumb(current_url, options, &block)
|
93
|
+
item = site.find_by_url(current_url)
|
94
|
+
return "" unless item
|
95
|
+
haml_tag :a, options.merge(:href => current_url) do
|
96
|
+
if block_given?
|
97
|
+
yield item
|
98
|
+
else
|
99
|
+
haml_concat item.title
|
100
|
+
end
|
101
|
+
end
|
102
|
+
if options[:to] != current_url
|
103
|
+
haml_concat options[:delimiter] || " » "
|
104
|
+
next_node = options[:to].gsub(/#{current_url}\/?/, "").split("/").first
|
105
|
+
breadcrumb("#{current_url}/#{next_node}", options, &block)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def find_index_dir(dir)
|
110
|
+
return dir if File.exists?(File.join(dir, "index.haml"))
|
111
|
+
return dir == "." ? dir : find_index_dir(File.dirname(dir))
|
112
|
+
end
|
113
|
+
|
114
|
+
# TODO
|
115
|
+
def load_tree
|
116
|
+
root = File.expand_path("public")
|
117
|
+
dir = params[:dir]
|
118
|
+
result = "<ul class=\"jqueryFileTree\" style=\"display: none;\">"
|
119
|
+
begin
|
120
|
+
path = File.expand_path(File.join(root, dir)).untaint
|
121
|
+
if (path.split("/").length >= root.split("/").length)
|
122
|
+
current_dir = Dir.pwd
|
123
|
+
Dir.chdir(File.expand_path(path).untaint);
|
124
|
+
#loop through all directories
|
125
|
+
Dir.glob("*") {
|
126
|
+
|x|
|
127
|
+
if not File.directory?(x.untaint) then next end
|
128
|
+
result << "<li class=\"directory collapsed\"><a href=\"#\" rel=\"#{dir}#{x}/\">#{x}</a></li>";
|
129
|
+
}
|
130
|
+
|
131
|
+
#loop through all files
|
132
|
+
Dir.glob("*") {
|
133
|
+
|x|
|
134
|
+
if not File.file?(x.untaint) then next end
|
135
|
+
ext = File.extname(x)[1..-1]
|
136
|
+
result << "<li class=\"file ext_#{ext}\"><a href=\"#\" rel=\"#{dir}#{x}\">#{x}</a></li>"
|
137
|
+
}
|
138
|
+
else
|
139
|
+
#only happens when someone tries to go outside your root directory...
|
140
|
+
result << "You are way out of your league"
|
141
|
+
end
|
142
|
+
rescue
|
143
|
+
result << "Internal Error"
|
144
|
+
end
|
145
|
+
Dir.chdir(current_dir)
|
146
|
+
result << "</ul>"
|
147
|
+
result
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|