plate 0.5.0
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/CHANGELOG.md +6 -0
- data/LICENSE +21 -0
- data/README.md +92 -0
- data/Rakefile +27 -0
- data/bin/plate +4 -0
- data/lib/plate.rb +56 -0
- data/lib/plate/asset.rb +47 -0
- data/lib/plate/builder.rb +375 -0
- data/lib/plate/callbacks.rb +39 -0
- data/lib/plate/cli.rb +203 -0
- data/lib/plate/dynamic_page.rb +27 -0
- data/lib/plate/engine.rb +25 -0
- data/lib/plate/errors.rb +10 -0
- data/lib/plate/haml_template.rb +18 -0
- data/lib/plate/helpers/blogging_helper.rb +102 -0
- data/lib/plate/helpers/meta_helper.rb +71 -0
- data/lib/plate/helpers/url_helper.rb +11 -0
- data/lib/plate/layout.rb +169 -0
- data/lib/plate/markdown_template.rb +22 -0
- data/lib/plate/page.rb +280 -0
- data/lib/plate/post.rb +134 -0
- data/lib/plate/post_collection.rb +116 -0
- data/lib/plate/sass_template.rb +40 -0
- data/lib/plate/scss_template.rb +9 -0
- data/lib/plate/site.rb +249 -0
- data/lib/plate/static_page.rb +37 -0
- data/lib/plate/version.rb +3 -0
- data/lib/plate/view.rb +48 -0
- data/lib/templates/config.yml +1 -0
- data/lib/templates/index.md +6 -0
- data/lib/templates/layout.erb +12 -0
- metadata +143 -0
@@ -0,0 +1,11 @@
|
|
1
|
+
module Plate
|
2
|
+
# Includes basic helpers for managing URLs within your site.
|
3
|
+
module URLHelper
|
4
|
+
# Cleans up a string to make it URl-friendly, removing all special
|
5
|
+
# characters, spaces, and sanitizing to a dashed, lowercase string.
|
6
|
+
def sanitize_slug(str)
|
7
|
+
self.site.sanitize_slug(str)
|
8
|
+
end
|
9
|
+
alias_method :s, :sanitize_slug
|
10
|
+
end
|
11
|
+
end
|
data/lib/plate/layout.rb
ADDED
@@ -0,0 +1,169 @@
|
|
1
|
+
module Plate
|
2
|
+
class Layout
|
3
|
+
attr_accessor :site, :file, :meta, :content
|
4
|
+
|
5
|
+
def initialize(site, file = nil, load_on_initialize = true)
|
6
|
+
self.site = site
|
7
|
+
self.file = file
|
8
|
+
self.meta = {}
|
9
|
+
self.content = ""
|
10
|
+
|
11
|
+
load! if load_on_initialize and file?
|
12
|
+
end
|
13
|
+
|
14
|
+
# The name of the layound, without any path data
|
15
|
+
def basename
|
16
|
+
File.basename(self.file)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Is this layout the default layout, by name.
|
20
|
+
def default?
|
21
|
+
self.name.downcase.strip.start_with? "default"
|
22
|
+
end
|
23
|
+
|
24
|
+
# The layout engine to use. Based off of the last file extension for this layout.
|
25
|
+
def engine
|
26
|
+
@engine ||= self.site.registered_page_engines[self.extension.gsub(/\./, '').to_sym]
|
27
|
+
end
|
28
|
+
|
29
|
+
# The last file extension of this layout.
|
30
|
+
def extension
|
31
|
+
File.extname(self.file)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Does the file exist or not.
|
35
|
+
def file?
|
36
|
+
return false if self.file.nil?
|
37
|
+
File.exists?(self.file)
|
38
|
+
end
|
39
|
+
|
40
|
+
# A unique ID for this layout.
|
41
|
+
def id
|
42
|
+
@id ||= Digest::MD5.hexdigest(relative_file)
|
43
|
+
end
|
44
|
+
|
45
|
+
def inspect
|
46
|
+
"#<#{self.class}:0x#{object_id.to_s(16)} name=#{name.to_s.inspect}>"
|
47
|
+
end
|
48
|
+
|
49
|
+
def load!
|
50
|
+
return if @loaded
|
51
|
+
raise FileNotFound unless file?
|
52
|
+
|
53
|
+
read_file!
|
54
|
+
read_metadata!
|
55
|
+
|
56
|
+
@loaded = true
|
57
|
+
end
|
58
|
+
|
59
|
+
# The name for a layout is just the lowercase, first part of the file name.
|
60
|
+
def name
|
61
|
+
return "" unless file?
|
62
|
+
@name ||= self.basename.to_s.downcase.strip.split('.')[0]
|
63
|
+
end
|
64
|
+
|
65
|
+
# A parent layout for this current layout file. If no layout is specified for this
|
66
|
+
# layout's parent, then nil is returned. If there is a parent layout for this layout,
|
67
|
+
# any pages using it will be rendered using this layout first, then sent to the parent
|
68
|
+
# for further rendering.
|
69
|
+
def parent
|
70
|
+
return @parent if @parent
|
71
|
+
|
72
|
+
if self.meta[:layout]
|
73
|
+
@parent = self.site.find_layout(self.meta[:layout])
|
74
|
+
else
|
75
|
+
@parent = nil
|
76
|
+
end
|
77
|
+
|
78
|
+
@parent
|
79
|
+
end
|
80
|
+
|
81
|
+
def relative_file
|
82
|
+
@relative_file ||= self.site.relative_path(self.file)
|
83
|
+
end
|
84
|
+
|
85
|
+
def reload!
|
86
|
+
@template = nil
|
87
|
+
@loaded = false
|
88
|
+
@name = nil
|
89
|
+
@engine = nil
|
90
|
+
end
|
91
|
+
|
92
|
+
# Render the given content against the current layout template.
|
93
|
+
def render(content, page = nil, view = nil)
|
94
|
+
if self.template
|
95
|
+
view ||= View.new(self.site, page)
|
96
|
+
result = self.template.render(view) { content }
|
97
|
+
|
98
|
+
if self.parent
|
99
|
+
result = self.parent.render(result, page, view)
|
100
|
+
end
|
101
|
+
|
102
|
+
view = nil
|
103
|
+
|
104
|
+
result
|
105
|
+
else
|
106
|
+
content.respond_to?(:rendered_content) ? content.rendered_content : content.to_s
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# The render template to use for this layout. A template is only used if the
|
111
|
+
# file extension for the layout is a valid layout extension from the current
|
112
|
+
# site.
|
113
|
+
def template
|
114
|
+
return @template if @template
|
115
|
+
|
116
|
+
if template?
|
117
|
+
@template = self.engine.new() { self.content }
|
118
|
+
else
|
119
|
+
nil
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# Does this file have the ability to be used as a template?
|
124
|
+
#
|
125
|
+
# This currently only works if the layout is a .erb file. Otherwise anything that
|
126
|
+
# calls this layout just returns the text it is given.
|
127
|
+
def template?
|
128
|
+
self.site.page_engine_extensions.include?(self.extension)
|
129
|
+
end
|
130
|
+
|
131
|
+
# Is this layout equal to another page being sent?
|
132
|
+
def ==(other)
|
133
|
+
other = other.relative_file if other.respond_to?(:relative_file)
|
134
|
+
self.id == other or self.relative_file == other
|
135
|
+
end
|
136
|
+
|
137
|
+
protected
|
138
|
+
# Read the file and store it in @content
|
139
|
+
def read_file!
|
140
|
+
self.content = file? ? File.read(self.file) : nil
|
141
|
+
end
|
142
|
+
|
143
|
+
# Reads all content from a layouts's meta data. At this time, the layout only supports
|
144
|
+
# loading a parent layout. All other meta data is unused.
|
145
|
+
#
|
146
|
+
# Meta data is stored in YAML format within the head of a page after the -- declaration like so:
|
147
|
+
#
|
148
|
+
# ---
|
149
|
+
# layout: default
|
150
|
+
#
|
151
|
+
# # Start of layout content
|
152
|
+
def read_metadata!
|
153
|
+
return unless self.content
|
154
|
+
|
155
|
+
begin
|
156
|
+
if matches = /^(---\n)(.*?)^\s*?$/m.match(self.content)
|
157
|
+
if matches.size == 3
|
158
|
+
self.content = matches.post_match.strip
|
159
|
+
self.meta = YAML.load(matches[2])
|
160
|
+
self.meta.symbolize_keys!
|
161
|
+
end
|
162
|
+
end
|
163
|
+
rescue Exception => e
|
164
|
+
self.meta = {}
|
165
|
+
self.site.log(" ** Problem reading YAML for file #{relative_file} (#{e.message}). Meta data skipped")
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Plate
|
2
|
+
# Generic markdown template, currently uses RedCarpet2
|
3
|
+
class MarkdownTemplate < Tilt::Template
|
4
|
+
self.default_mime_type = 'text/html'
|
5
|
+
|
6
|
+
def self.engine_initialized?
|
7
|
+
defined? ::Redcarpet::Render
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize_engine
|
11
|
+
require_template_library 'redcarpet'
|
12
|
+
end
|
13
|
+
|
14
|
+
def prepare
|
15
|
+
@engine = ::Redcarpet::Markdown.new(Redcarpet::Render::HTML, options)
|
16
|
+
end
|
17
|
+
|
18
|
+
def evaluate(scope, locals, &block)
|
19
|
+
@output ||= @engine.render(data)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/plate/page.rb
ADDED
@@ -0,0 +1,280 @@
|
|
1
|
+
require 'digest'
|
2
|
+
|
3
|
+
module Plate
|
4
|
+
class Page
|
5
|
+
include Callbacks
|
6
|
+
|
7
|
+
attr_accessor :body, :content, :file, :meta, :site
|
8
|
+
|
9
|
+
def initialize(site, file = nil, load_on_initialize = true)
|
10
|
+
self.site = site
|
11
|
+
self.file = file
|
12
|
+
self.meta = {}
|
13
|
+
self.content = ""
|
14
|
+
|
15
|
+
load! if load_on_initialize and file?
|
16
|
+
end
|
17
|
+
|
18
|
+
# Setup some shortcut getters for meta attributes
|
19
|
+
%w( title description tags category ).each do |meta_attribute|
|
20
|
+
class_eval <<-META
|
21
|
+
def #{meta_attribute} # def title
|
22
|
+
self.meta[:#{meta_attribute}] # self.meta[:title]
|
23
|
+
end # end
|
24
|
+
|
25
|
+
def #{meta_attribute}=(value) # def title=(value)
|
26
|
+
self.meta[:#{meta_attribute}] = value # self.meta[:title] = value
|
27
|
+
end # end
|
28
|
+
META
|
29
|
+
end
|
30
|
+
|
31
|
+
# The name of the file, without any path data
|
32
|
+
def basename
|
33
|
+
File.basename(self.file)
|
34
|
+
end
|
35
|
+
alias_method :name, :basename
|
36
|
+
|
37
|
+
# The directory this page is located in, relative to the site root.
|
38
|
+
def directory
|
39
|
+
return @directory if @directory
|
40
|
+
|
41
|
+
base = Pathname.new(File.join(self.site.source, 'content'))
|
42
|
+
current = Pathname.new(self.file)
|
43
|
+
|
44
|
+
dirs = current.relative_path_from(base).to_s.split('/')
|
45
|
+
|
46
|
+
if dirs.size > 1
|
47
|
+
dirs.pop
|
48
|
+
@directory = "/#{dirs.join('/')}"
|
49
|
+
else
|
50
|
+
@directory = "/"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def engines
|
55
|
+
@engines ||= self.extensions.reverse.collect { |e| self.site.registered_page_engines[e.gsub(/\./, '').to_sym] }.reject { |e| !e }
|
56
|
+
end
|
57
|
+
|
58
|
+
def extensions
|
59
|
+
@extensions ||= self.basename.scan(/\.[^.]+/)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Does the file exist or not.
|
63
|
+
def file?
|
64
|
+
return false if self.file.nil?
|
65
|
+
File.exists?(self.file)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Returns just the file name, no extension.
|
69
|
+
def file_name
|
70
|
+
File.basename(self.file, '.*')
|
71
|
+
end
|
72
|
+
|
73
|
+
# The full file path of where this file will be written to. (Relative to site root)
|
74
|
+
def file_path
|
75
|
+
return self.meta[:path] if self.meta.has_key?(:path)
|
76
|
+
return @file_path if @file_path
|
77
|
+
|
78
|
+
result = directory
|
79
|
+
result << '/' unless result =~ /\/$/
|
80
|
+
result << slug unless slug == "index"
|
81
|
+
|
82
|
+
# Remove any double slashes
|
83
|
+
result.gsub!(/\/\//, '/')
|
84
|
+
|
85
|
+
# Remove file extensions, and cleanup URL
|
86
|
+
result = result.split('/').reject{ |segment| segment =~ /^\.+$/ }.join('/')
|
87
|
+
|
88
|
+
# Add a trailing slash
|
89
|
+
result << '/' unless result =~ /\/$/
|
90
|
+
|
91
|
+
# Tack on index.html for the folder
|
92
|
+
result << "index.html"
|
93
|
+
@file_path = result
|
94
|
+
end
|
95
|
+
|
96
|
+
def format_extension
|
97
|
+
format = self.extensions.reverse.detect() { |e| !self.site.page_engine_extensions.include?(e) }
|
98
|
+
format = ".html" if format.nil?
|
99
|
+
format
|
100
|
+
end
|
101
|
+
|
102
|
+
# A unique ID for this page.
|
103
|
+
def id
|
104
|
+
@id ||= Digest::MD5.hexdigest(relative_file)
|
105
|
+
end
|
106
|
+
|
107
|
+
def inspect
|
108
|
+
"#<#{self.class}:0x#{object_id.to_s(16)} name=#{name.to_s.inspect}>"
|
109
|
+
end
|
110
|
+
|
111
|
+
# Utility method to sanitize keywords output. Keywords are returned as an array.
|
112
|
+
def keywords
|
113
|
+
@keywords ||= (Array === self.meta[:keywords] ? self.meta[:keywords] : self.meta[:keywords].to_s.strip.split(',').collect(&:strip))
|
114
|
+
end
|
115
|
+
|
116
|
+
# The layout to use when rendering this page. Returns nil if no default layout is available,
|
117
|
+
# or the layout has specifically been turned off within the config.
|
118
|
+
def layout
|
119
|
+
return nil if self.meta[:layout] == false
|
120
|
+
return @layout if @layout
|
121
|
+
|
122
|
+
if self.meta[:layout]
|
123
|
+
@layout = self.site.find_layout(self.meta[:layout])
|
124
|
+
else
|
125
|
+
@layout = self.site.default_layout
|
126
|
+
end
|
127
|
+
|
128
|
+
@layout
|
129
|
+
end
|
130
|
+
|
131
|
+
# Has this page been loaded from file?
|
132
|
+
def loaded?
|
133
|
+
!!@loaded
|
134
|
+
end
|
135
|
+
|
136
|
+
# Read the file data for this page
|
137
|
+
def load!
|
138
|
+
return if @loaded
|
139
|
+
raise FileNotFound unless file?
|
140
|
+
|
141
|
+
read_file!
|
142
|
+
read_metadata!
|
143
|
+
|
144
|
+
@loaded = true
|
145
|
+
end
|
146
|
+
|
147
|
+
def path
|
148
|
+
return '/' if self.file_path == '/index.html'
|
149
|
+
@path ||= self.file_path.sub(/(.*?)\/index\.html$/i, '\1')
|
150
|
+
end
|
151
|
+
|
152
|
+
# The file's source path, relative to site root.
|
153
|
+
def relative_file
|
154
|
+
@relative_file ||= self.site.relative_path(self.file)
|
155
|
+
end
|
156
|
+
|
157
|
+
def reload!
|
158
|
+
@layout = nil
|
159
|
+
@loaded = false
|
160
|
+
@content = nil
|
161
|
+
@meta = {}
|
162
|
+
@keywords = nil
|
163
|
+
@rendered_content = nil
|
164
|
+
@body = nil
|
165
|
+
|
166
|
+
load!
|
167
|
+
end
|
168
|
+
|
169
|
+
# Returns the rendered body of this page, without the layout.
|
170
|
+
def rendered_body
|
171
|
+
return @body if @body
|
172
|
+
|
173
|
+
result = ""
|
174
|
+
|
175
|
+
around_callback :render do
|
176
|
+
result = self.content
|
177
|
+
|
178
|
+
view = View.new(self.site, self)
|
179
|
+
|
180
|
+
self.engines.each do |engine|
|
181
|
+
template = engine.new(self.file) { result }
|
182
|
+
result = template.render(view, {})
|
183
|
+
end
|
184
|
+
|
185
|
+
view = nil
|
186
|
+
|
187
|
+
@body = result
|
188
|
+
end
|
189
|
+
|
190
|
+
@body
|
191
|
+
end
|
192
|
+
|
193
|
+
def rendered_content
|
194
|
+
@rendered_content ||= self.apply_layout_to(rendered_body)
|
195
|
+
end
|
196
|
+
|
197
|
+
# Name of the file to be saved. Just takes the current file name and removes any extensions.
|
198
|
+
def slug
|
199
|
+
self.basename.to_s.downcase.split('.')[0].dasherize.parameterize
|
200
|
+
end
|
201
|
+
|
202
|
+
# The title from this page's meta data, turned into a parameter for use in a url.
|
203
|
+
def title_for_url
|
204
|
+
self.title.to_s.dasherize.parameterize
|
205
|
+
end
|
206
|
+
|
207
|
+
# Returns this page's content
|
208
|
+
def to_s
|
209
|
+
self.inspect
|
210
|
+
end
|
211
|
+
|
212
|
+
# The full URL of this page. Depends on the site's URL attribute and a config option of `:base_url`
|
213
|
+
def url
|
214
|
+
@url ||= "#{site.url}#{path}"
|
215
|
+
end
|
216
|
+
|
217
|
+
# Write this compiled page to its destination file.
|
218
|
+
def write!
|
219
|
+
path = File.join(site.build_destination, file_path)
|
220
|
+
|
221
|
+
FileUtils.mkdir_p(File.dirname(path))
|
222
|
+
|
223
|
+
File.open(path, 'w') do |f|
|
224
|
+
f.write(self.rendered_content)
|
225
|
+
end
|
226
|
+
|
227
|
+
path
|
228
|
+
end
|
229
|
+
|
230
|
+
# Is this page equal to another page being sent?
|
231
|
+
def ==(other)
|
232
|
+
other = other.relative_file if other.respond_to?(:relative_file)
|
233
|
+
self.id == other or self.relative_file == other
|
234
|
+
end
|
235
|
+
|
236
|
+
protected
|
237
|
+
def apply_layout_to(content)
|
238
|
+
return content unless Layout === self.layout
|
239
|
+
self.layout.render(content, self)
|
240
|
+
end
|
241
|
+
|
242
|
+
# Reading page details
|
243
|
+
# #####################################################################
|
244
|
+
|
245
|
+
# Read the file and store it in @content
|
246
|
+
def read_file!
|
247
|
+
self.content = file? ? File.read(self.file) : nil
|
248
|
+
end
|
249
|
+
|
250
|
+
# Reads all content from a page's meta data
|
251
|
+
#
|
252
|
+
# Meta data is stored in YAML format within the head of a page after the -- declaration like so:
|
253
|
+
#
|
254
|
+
# ---
|
255
|
+
# title: "Hello"
|
256
|
+
# description: "This is some meta data"
|
257
|
+
# keywords: [ blah, blah ]
|
258
|
+
# tags: [ blah, blah ]
|
259
|
+
# category: Test
|
260
|
+
# layout: default
|
261
|
+
#
|
262
|
+
# # Start of actual content
|
263
|
+
def read_metadata!
|
264
|
+
return unless self.content
|
265
|
+
|
266
|
+
begin
|
267
|
+
if matches = /^(---\n)(.*?)^\s*?$/m.match(self.content)
|
268
|
+
if matches.size == 3
|
269
|
+
self.content = matches.post_match.strip
|
270
|
+
self.meta = YAML.load(matches[2])
|
271
|
+
self.meta.symbolize_keys!
|
272
|
+
end
|
273
|
+
end
|
274
|
+
rescue Exception => e
|
275
|
+
self.meta = {}
|
276
|
+
self.site.log(" ** Problem reading YAML for file #{relative_file} (#{e.message}). Meta data skipped")
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|