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 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.3'
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
@@ -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: '0.4'
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.000000000Z
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: &70364329519300 !ruby/object:Gem::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: *70364329519300
25
+ version_requirements: *70172670560860
25
26
  - !ruby/object:Gem::Dependency
26
27
  name: nokogiri
27
- requirement: &70364329518840 !ruby/object:Gem::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: *70364329518840
36
+ version_requirements: *70172670560400
36
37
  - !ruby/object:Gem::Dependency
37
38
  name: rack
38
- requirement: &70364329518420 !ruby/object:Gem::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: *70364329518420
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.8.10
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