pekky 0.4 → 0.4.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/CHANGES +24 -0
- data/lib/pekky.rb +2 -2
- data/lib/pekky/content.rb +142 -0
- data/lib/pekky/format.rb +247 -0
- metadata +14 -9
data/CHANGES
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
VERSION 0.4
|
2
|
+
Change to the configuration syntax. The generate option is now gone. Instead
|
3
|
+
there are the #page and #pages methods.
|
4
|
+
|
5
|
+
Now, instead of saying:
|
6
|
+
|
7
|
+
page '/', 'home'
|
8
|
+
|
9
|
+
You would specify:
|
10
|
+
|
11
|
+
page :index, '/', 'home'
|
12
|
+
|
13
|
+
The only change in this case is that you _must_ now name all the pages in your
|
14
|
+
config.
|
15
|
+
|
16
|
+
The replacement for #generate is the #pages method. Similar signature to #page
|
17
|
+
but it slurps up a directory and generates multiple pages from each yaml file
|
18
|
+
contained within it. Here is an example of generating weblog posts.
|
19
|
+
|
20
|
+
pages :post, '/posts/:slug', 'posts', :view => 'post'
|
21
|
+
|
22
|
+
VERSION 0.3
|
23
|
+
No external changes. Mainly just some reorganisation of the code.
|
24
|
+
|
data/lib/pekky.rb
CHANGED
@@ -26,8 +26,8 @@ require 'yaml'
|
|
26
26
|
# serves as a container and shortcut.
|
27
27
|
module Pekky
|
28
28
|
# The third release, dedicated to stunt-men everywhere.
|
29
|
-
VERSION = '0.
|
30
|
-
VERSION_NAME = "Shit-scared"
|
29
|
+
VERSION = '0.4.1'
|
30
|
+
VERSION_NAME = "Still Shit-scared"
|
31
31
|
|
32
32
|
# A base class for all the errors in Pekky. We’re using custom errors so that
|
33
33
|
# we can filter out errors concerning configuration in one place, while still
|
@@ -0,0 +1,142 @@
|
|
1
|
+
module Pekky
|
2
|
+
# The content class is used to wrap around the YAML files loaded from disk.
|
3
|
+
# It provides coercions and some nice syntax for accessing the fields.
|
4
|
+
class Content
|
5
|
+
# This error will be raised if a content YAML file cannot be found in the
|
6
|
+
# content directory -- stored in Config[:content_dir]
|
7
|
+
class ContentMissingError < PekkyError
|
8
|
+
def initialize(path)
|
9
|
+
@message = "The content file '#{path}.yml' could not be found."
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Raised if the user tries to access a field that doesn't exist.
|
14
|
+
class FieldMissingError < PekkyError
|
15
|
+
def initialize(field)
|
16
|
+
@message = "The field '#{field}' doesn't exist."
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# A class ivar for storing content instances.
|
21
|
+
@cache = {}
|
22
|
+
|
23
|
+
# Returns a content instance populated with data from the file at the
|
24
|
+
# specified path. Errors if the file missing.
|
25
|
+
def self.[](path)
|
26
|
+
@cache[path] ||= load_file(path)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.get(path)
|
30
|
+
@cache[path] ||= begin
|
31
|
+
full_path = File.join(Config[:content_dir], path)
|
32
|
+
if File.directory?(full_path)
|
33
|
+
contents = []
|
34
|
+
Dir.foreach(full_path) do |filename|
|
35
|
+
unless File.directory?(filename)
|
36
|
+
contents << new("#{path}/#{filename}")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
contents
|
40
|
+
else
|
41
|
+
new(path)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Clears out any cached content instances.
|
47
|
+
def self.flush!
|
48
|
+
@cache.clear
|
49
|
+
end
|
50
|
+
|
51
|
+
# On initialization, the content class creates an instance of the Fields
|
52
|
+
# class. This instance encapsulates most of the logic for accessing values.
|
53
|
+
def initialize(path)
|
54
|
+
@path = path
|
55
|
+
load_file
|
56
|
+
end
|
57
|
+
|
58
|
+
# A simple accessor for grabbing an attribute without having to use
|
59
|
+
# #method_missing fall-back.
|
60
|
+
def [](name)
|
61
|
+
@fields[name]
|
62
|
+
end
|
63
|
+
|
64
|
+
# Returns the fields instance. Allows users to do things like:
|
65
|
+
#
|
66
|
+
# content.fields.each {|name, value| puts value}
|
67
|
+
#
|
68
|
+
def fields
|
69
|
+
@fields
|
70
|
+
end
|
71
|
+
|
72
|
+
def reload
|
73
|
+
load_file
|
74
|
+
end
|
75
|
+
|
76
|
+
# Method missing is being used to allow us to access fields on the content
|
77
|
+
# instance as if they were declared as readers. Yes, it's slower than
|
78
|
+
# using #class_eval, but it's simple and the speed doesn't matter frankly.
|
79
|
+
def method_missing(name)
|
80
|
+
if match = name.to_s.match(/^(\S+)\?$/)
|
81
|
+
@fields.has?(name)
|
82
|
+
else
|
83
|
+
@fields.has?(name) ? @fields[name] : super
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
# Loads the content file from disk and creates a new Content instance. If
|
90
|
+
# the content file is missing, it has a little cry and raises an error.
|
91
|
+
def load_file
|
92
|
+
filename = @path.include?('.yml') ? @path : "#{@path}.yml"
|
93
|
+
full_path = File.join(Config[:content_dir], filename)
|
94
|
+
if File.exists?(full_path)
|
95
|
+
@fields = Fields.new(YAML.load_file(full_path))
|
96
|
+
else
|
97
|
+
raise ContentMissingError.new(@path)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# The fields class provides an interface for iterating over a record's
|
102
|
+
# fields, checking to see if a field exists and other sundry tasks.
|
103
|
+
class Fields
|
104
|
+
|
105
|
+
# The proxy is essentially a collection, we want it to behave like an
|
106
|
+
# enumerable.
|
107
|
+
include Enumerable
|
108
|
+
|
109
|
+
# The fields proxy is initialized with the raw values pulled from the
|
110
|
+
# content YAML files.
|
111
|
+
def initialize(fields)
|
112
|
+
@fields = fields
|
113
|
+
end
|
114
|
+
|
115
|
+
# Allows the proxy to be used by a hash, but under the hood it will
|
116
|
+
# actually coerce values and push them into the cache. This is the
|
117
|
+
# entirety of the lazy-coercion. Very simple.
|
118
|
+
def [](name)
|
119
|
+
name_ = name.to_sym
|
120
|
+
if @fields.has_key?(name)
|
121
|
+
@fields[name]
|
122
|
+
elsif @fields.has_key?(name_)
|
123
|
+
@fields[name_]
|
124
|
+
else
|
125
|
+
raise FieldMissingError.new(name)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# Checks to see if the specified field exists, returning true or false.
|
130
|
+
def has?(name)
|
131
|
+
!!@fields.has_key?(name)
|
132
|
+
end
|
133
|
+
|
134
|
+
# The each method, which is required by the Enumerable module. We iterate
|
135
|
+
# over the raw values, since they may not be in the cache. We then access
|
136
|
+
# them via #[] to ensure that they are coerced and cached.
|
137
|
+
def each
|
138
|
+
@fields.each {|k, v| yield k, v }
|
139
|
+
end
|
140
|
+
end # AttributesProxy
|
141
|
+
end # Conent
|
142
|
+
end # Pekky
|
data/lib/pekky/format.rb
ADDED
@@ -0,0 +1,247 @@
|
|
1
|
+
module Pekky
|
2
|
+
# The Format module acts as a container for the renderers and filters.
|
3
|
+
# Renderers take markup of some kind and output HTML. Filters accept HTML
|
4
|
+
# and modify it in some way. It also contains sundry utility methods for
|
5
|
+
# HTML manipulation.
|
6
|
+
module Format
|
7
|
+
# A custom error class for encapsulating load errors, so we can add a custom
|
8
|
+
# error message when a renderer can't find the library it needs. This is
|
9
|
+
# to prevent users being presented with a potentially confusing message.
|
10
|
+
class LibaryMissingError < PekkyError
|
11
|
+
def initialize(klass, error)
|
12
|
+
@message = "While trying to use the #{klass.to_s} transformer, we got this error:\n #{error.message}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# The wrapper class acts as a container for a set of renderers or filters.
|
17
|
+
# It's most essential job is to allow syntax like this:
|
18
|
+
#
|
19
|
+
# Format::Formatters.textile("h1. blah")
|
20
|
+
#
|
21
|
+
# This class should never be instanced, or accessed directly. Instead, we
|
22
|
+
# use the Renderers and Filters subclasses.
|
23
|
+
class Wrapper
|
24
|
+
# Ahh, the good old class instance attribute accessors. This is a bit of
|
25
|
+
# voodoo which is better explained by more patient people.
|
26
|
+
class << self
|
27
|
+
attr_accessor :klasses, :call_with
|
28
|
+
end
|
29
|
+
|
30
|
+
# Adds a transformer to the existing collection.
|
31
|
+
def self.add(name, klass)
|
32
|
+
(self.klasses ||= {})[name] = klass
|
33
|
+
end
|
34
|
+
|
35
|
+
# Shortcut for accessing a transformer class by name.
|
36
|
+
def self.[](name)
|
37
|
+
self.klasses[name]
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns all the names of the transformers.
|
41
|
+
def self.keys
|
42
|
+
self.klasses.keys
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns the actual transformers themselves.
|
46
|
+
def self.values
|
47
|
+
self.klasses.values
|
48
|
+
end
|
49
|
+
|
50
|
+
# An iterator for looping over the transformer. The block will recieve
|
51
|
+
# the name and the actual class.
|
52
|
+
def self.each
|
53
|
+
self.klasses.each { |k, v| yield k, v }
|
54
|
+
end
|
55
|
+
|
56
|
+
# Method missing is used here to provide nice syntax for accessing
|
57
|
+
# transformer as methods.
|
58
|
+
def self.method_missing(name, *args)
|
59
|
+
if self.klasses[name]
|
60
|
+
self.klasses[name].send(self.call_with, *args)
|
61
|
+
else
|
62
|
+
super
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# The formatter wrapper. Adds a few additional methods for defaults etc.
|
68
|
+
class Formatters < Wrapper
|
69
|
+
self.call_with = :_format
|
70
|
+
|
71
|
+
# Returns the default text formatter -- textile
|
72
|
+
def self.defaults
|
73
|
+
Config[:text_formatter]
|
74
|
+
end
|
75
|
+
|
76
|
+
# This is the default formatter. It'll use the setting set in the site
|
77
|
+
# config, or the built in default -- textile.
|
78
|
+
def self.text(text, opts = {})
|
79
|
+
self.klasses[defaults]._format(text, opts)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Call the renderer by name rather than having the code drop down to
|
83
|
+
# method missing. This will mainly be used internally in Pekky.
|
84
|
+
def self.format(name, text, opts = {})
|
85
|
+
self.klasses[name]._format(text, opts)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# The filters class just wraps the filters. Nothing special. Poor thing.
|
90
|
+
class Filters < Wrapper
|
91
|
+
self.call_with = :_filter
|
92
|
+
|
93
|
+
# Returns the array of filters defined in site config, or all of the
|
94
|
+
# filters.
|
95
|
+
def self.defaults
|
96
|
+
@defaults ||= Config[:text_filters] ? Config[:text_filters] << :links : [:links]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Transformer class for the formatters and filters. A subclass can actually
|
101
|
+
# be both a formatter -- takes text and makes HTML -- or a filter -- takes
|
102
|
+
# HTML and modifies it in some way. The reason for this is that the logic
|
103
|
+
# for doing both is often shared. For example in the code formatting, we
|
104
|
+
# sometimes want to render code directly from text, or we want to modify
|
105
|
+
# existing markup -- like some embedded in textile output.
|
106
|
+
class Transformer
|
107
|
+
# Class attribute reader bo!
|
108
|
+
class << self
|
109
|
+
attr_reader :loaded
|
110
|
+
end
|
111
|
+
|
112
|
+
# The loaded variable is used to keep track if the dependencies of the
|
113
|
+
# renderer has been loaded. The loading is handled by the
|
114
|
+
# #check_for_requirements method.
|
115
|
+
@loaded = false
|
116
|
+
|
117
|
+
# This hook is being used to see if the subclasses define either the
|
118
|
+
# #format or #filter methods and thus need to be added to the relevant
|
119
|
+
# wrappers.
|
120
|
+
def self.singleton_method_added(method_name)
|
121
|
+
case method_name
|
122
|
+
when :format then Formatters.add(name, self)
|
123
|
+
when :filter then Filters.add(name, self)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# Returns the snake case version of this class name.
|
128
|
+
def self.name
|
129
|
+
@name ||= self.to_s.match(/::(\w+)$/)[1].downcase.to_sym
|
130
|
+
end
|
131
|
+
|
132
|
+
# This method wraps the actual #format method defined by the subclass.
|
133
|
+
# This is so we can make sure the dependencies are loaded and then run
|
134
|
+
# any of the specified filters -- or the defaults.
|
135
|
+
def self._format(text, opts = {})
|
136
|
+
check_for_requirements
|
137
|
+
|
138
|
+
output = if method(:format).arity > 1
|
139
|
+
format(text, opts)
|
140
|
+
else
|
141
|
+
format(text)
|
142
|
+
end
|
143
|
+
|
144
|
+
filters = opts.delete(:filters) || Filters.defaults
|
145
|
+
|
146
|
+
if filters.empty?
|
147
|
+
output
|
148
|
+
else
|
149
|
+
doc = Nokogiri::HTML.fragment(output)
|
150
|
+
filters.each do |name|
|
151
|
+
Filters[name].filter(doc, output)
|
152
|
+
end
|
153
|
+
doc.to_html
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# Wraps around the #filter method defined in the subclasses. It does some
|
158
|
+
# preflight stuff like loading requirements and setting up the Nokogiri
|
159
|
+
# document.
|
160
|
+
def self._filter(text)
|
161
|
+
check_for_requirements
|
162
|
+
doc = Nokogiri::HTML.fragment(text)
|
163
|
+
filter(doc, text)
|
164
|
+
doc.to_html
|
165
|
+
end
|
166
|
+
|
167
|
+
private
|
168
|
+
|
169
|
+
# Checks to see if the Renderer has any requirements, or if they have
|
170
|
+
# been run previously. It also catches errors and re-raises them as a
|
171
|
+
# PekkyError so we can present the user with a nicer error message.
|
172
|
+
def self.check_for_requirements
|
173
|
+
if !@loaded and respond_to?(:requirements)
|
174
|
+
requirements
|
175
|
+
@loaded = true
|
176
|
+
end
|
177
|
+
rescue LoadError => error
|
178
|
+
raise LibraryMissingError.new(self, error)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# Formats textile markup into HTML.
|
183
|
+
class Textile < Transformer
|
184
|
+
def self.format(text)
|
185
|
+
Tilt::RedClothTemplate.new {text}.render
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# Formats markdown into HTML.
|
190
|
+
class Markdown < Transformer
|
191
|
+
def self.format(text)
|
192
|
+
Tilt::RDiscountTemplate.new {text}.render
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
# Ahhh, the venerable ERB templates.
|
197
|
+
class Erb < Transformer
|
198
|
+
def self.format(text)
|
199
|
+
Tilt::ERBTemplate.new {text}.render
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
# HAML! I really like Haml. Best template language ever.
|
204
|
+
class Haml < Transformer
|
205
|
+
def self.format(text)
|
206
|
+
Tilt::HamlTemplate.new {text}.render
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# Would you really want to render RDoc? Maybe. I dunno.
|
211
|
+
class Rdoc < Transformer
|
212
|
+
def self.format(text)
|
213
|
+
Tilt::RDocTemplate.new {text}.render
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
# Utility transformer which filters a nokogiri document fragment and
|
218
|
+
# replaces all instances of an anchor using the pekky:// protocol with the
|
219
|
+
# actual path to the page. This allows users to link to pages by name,
|
220
|
+
# without having to remember the path.
|
221
|
+
class Links < Transformer
|
222
|
+
def self.filter(doc, text)
|
223
|
+
doc.css("a[href^=pekky]").each do |a|
|
224
|
+
a.attributes["href"].value = Util.look_up_page(a.attributes["href"].value)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
# Util contains helper methods that don't fit anywhere else.
|
230
|
+
module Util
|
231
|
+
# Looks up a page based on a pekky URL.
|
232
|
+
def self.look_up_page(url)
|
233
|
+
match = url.match(/^pekky:\/\/(\S+)/)
|
234
|
+
qualify_path(Pekky.tree[match[1].to_sym].path)
|
235
|
+
end
|
236
|
+
|
237
|
+
# Qualify path adds a prefix to URLs on export.
|
238
|
+
def self.qualify_path(path)
|
239
|
+
if !path.match(/^http/) and Pekky.exporting? and Config[:path_prefix]
|
240
|
+
File.join(Config[:path_prefix], path)
|
241
|
+
else
|
242
|
+
path
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end # Util
|
246
|
+
end # Format
|
247
|
+
end # Pekky
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pekky
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 0.4.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,12 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-11-23 00:00:00.
|
12
|
+
date: 2011-11-23 00:00:00.000000000 +10:30
|
13
|
+
default_executable:
|
13
14
|
dependencies:
|
14
15
|
- !ruby/object:Gem::Dependency
|
15
16
|
name: tilt
|
16
|
-
requirement: &
|
17
|
+
requirement: &70172670560860 !ruby/object:Gem::Requirement
|
17
18
|
none: false
|
18
19
|
requirements:
|
19
20
|
- - ! '>='
|
@@ -21,10 +22,10 @@ dependencies:
|
|
21
22
|
version: '0'
|
22
23
|
type: :runtime
|
23
24
|
prerelease: false
|
24
|
-
version_requirements: *
|
25
|
+
version_requirements: *70172670560860
|
25
26
|
- !ruby/object:Gem::Dependency
|
26
27
|
name: nokogiri
|
27
|
-
requirement: &
|
28
|
+
requirement: &70172670560400 !ruby/object:Gem::Requirement
|
28
29
|
none: false
|
29
30
|
requirements:
|
30
31
|
- - ! '>='
|
@@ -32,10 +33,10 @@ dependencies:
|
|
32
33
|
version: '0'
|
33
34
|
type: :runtime
|
34
35
|
prerelease: false
|
35
|
-
version_requirements: *
|
36
|
+
version_requirements: *70172670560400
|
36
37
|
- !ruby/object:Gem::Dependency
|
37
38
|
name: rack
|
38
|
-
requirement: &
|
39
|
+
requirement: &70172670559980 !ruby/object:Gem::Requirement
|
39
40
|
none: false
|
40
41
|
requirements:
|
41
42
|
- - ! '>='
|
@@ -43,7 +44,7 @@ dependencies:
|
|
43
44
|
version: '0'
|
44
45
|
type: :runtime
|
45
46
|
prerelease: false
|
46
|
-
version_requirements: *
|
47
|
+
version_requirements: *70172670559980
|
47
48
|
description: A friendly static site builder
|
48
49
|
email: me@lukematthewsutton.com
|
49
50
|
executables:
|
@@ -53,17 +54,21 @@ extra_rdoc_files: []
|
|
53
54
|
files:
|
54
55
|
- LICENSE
|
55
56
|
- README
|
57
|
+
- CHANGES
|
56
58
|
- bin/pekky
|
57
59
|
- lib/pekky.rb
|
58
60
|
- lib/pekky/builder.rb
|
59
61
|
- lib/pekky/console.rb
|
60
62
|
- lib/pekky/context.rb
|
63
|
+
- lib/pekky/content.rb
|
64
|
+
- lib/pekky/format.rb
|
61
65
|
- lib/pekky/pages.rb
|
62
66
|
- lib/pekky/server.rb
|
63
67
|
- lib/pekky/config.rb
|
64
68
|
- lib/pekky/tree.rb
|
65
69
|
- lib/pekky/templates/site.rb
|
66
70
|
- lib/pekky/templates/helpers.rb
|
71
|
+
has_rdoc: true
|
67
72
|
homepage: http://lukematthewsutton.com/pekky
|
68
73
|
licenses: []
|
69
74
|
post_install_message:
|
@@ -84,7 +89,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
84
89
|
version: '0'
|
85
90
|
requirements: []
|
86
91
|
rubyforge_project:
|
87
|
-
rubygems_version: 1.
|
92
|
+
rubygems_version: 1.6.2
|
88
93
|
signing_key:
|
89
94
|
specification_version: 2
|
90
95
|
summary: Pekky’s main strengths come from the fact that it chooses explicitness over
|