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