plate 0.5.4 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +15 -0
- data/README.md +21 -19
- data/Rakefile +9 -5
- data/lib/plate.rb +16 -11
- data/lib/plate/asset.rb +86 -23
- data/lib/plate/builder.rb +286 -150
- data/lib/plate/callbacks.rb +20 -4
- data/lib/plate/cli.rb +65 -53
- data/lib/plate/dsl.rb +68 -0
- data/lib/plate/errors.rb +7 -4
- data/lib/plate/hash_proxy.rb +27 -0
- data/lib/plate/helpers/url_helper.rb +19 -0
- data/lib/plate/layout.rb +41 -36
- data/lib/plate/less_template.rb +34 -0
- data/lib/plate/page.rb +108 -84
- data/lib/plate/sass_template.rb +15 -12
- data/lib/plate/site.rb +31 -1
- data/lib/plate/version.rb +1 -1
- data/lib/plate/view.rb +30 -11
- data/lib/templates/bootstrap.min.css +241 -0
- data/lib/templates/callbacks.rb +37 -0
- data/lib/templates/config.yml +9 -1
- data/lib/templates/index.html +37 -0
- data/lib/templates/layout.html.erb +41 -0
- data/lib/templates/rss.erb +32 -0
- metadata +53 -17
- data/lib/templates/index.md +0 -6
- data/lib/templates/layout.erb +0 -12
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,18 @@
|
|
1
|
+
## Plate 0.6.0 (Unreleased)
|
2
|
+
|
3
|
+
* Meta data can be read from the config file and used to store common site data such as title, description, author, etc.
|
4
|
+
* Modified new site generator to use meta data structure
|
5
|
+
* Added RSS feed as part of new site generation
|
6
|
+
* Added helper for making relative links absolute for use within RSS feeds
|
7
|
+
* Documentation updates
|
8
|
+
* Added a new DSL for callbacks and other extensions. Files within `./lib` will now be evaluated using the DSL instead of just being required.
|
9
|
+
* Helpers have been moved to a separate directory in the site source root named `./helpers`.
|
10
|
+
* Added new site template with some base CSS styles and helpful information to get started.
|
11
|
+
* Allow a build destination to be set within the config.yml file.
|
12
|
+
* Allow customization of Sass output style with config variable :sass_style
|
13
|
+
* Added DSL methods to register asset and template engines.
|
14
|
+
* Added engine for Less CSS processing. Install the less gem to use this engine.
|
15
|
+
|
1
16
|
## Plate 0.5.4
|
2
17
|
|
3
18
|
* Added config option to command line utility to load a specific config file.
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
[![Build Status](https://secure.travis-ci.org/jdtornow/plate.png)](http://travis-ci.org/jdtornow/plate) [![Dependency Status](https://gemnasium.com/jdtornow/plate.png?travis)](https://gemnasium.com/jdtornow/plate)
|
4
4
|
|
5
|
-
Plate is a super simple static site generator and blog engine. At its core, it takes a folder full of Markdown files and turns it into a static HTML site that you can host anywhere.
|
5
|
+
Plate is a super simple static site generator and blog engine. At its core, it takes a folder full of Markdown files and turns it into a static HTML site that you can host anywhere.
|
6
6
|
|
7
7
|
In addition to basic formatting with Markdown, Plate also supports generating more dynamic files with ERB or HAML, and compiling asset files with CoffeeScript, Sass and others.
|
8
8
|
|
@@ -25,39 +25,35 @@ Plate is designed to be a command-line utility. Here is a rundown of some of the
|
|
25
25
|
To generate a new site with plate, run the following command:
|
26
26
|
|
27
27
|
platify .
|
28
|
-
|
28
|
+
|
29
29
|
Or, with the normal `plate` command:
|
30
30
|
|
31
31
|
plate new .
|
32
|
-
|
32
|
+
|
33
33
|
### Building a site
|
34
|
-
|
34
|
+
|
35
35
|
To build your site, run:
|
36
36
|
|
37
37
|
plate build
|
38
|
-
|
38
|
+
|
39
39
|
Or, just run `plate` without any options to build the site.
|
40
40
|
|
41
41
|
plate
|
42
|
-
|
42
|
+
|
43
43
|
To show details about the site build, enable verbose mode:
|
44
44
|
|
45
45
|
plate --verbose
|
46
|
-
|
47
|
-
When writing a post, it is sometimes helpful to watch the site for changes and just reload content after each file save. To enable this mode, run the build command with the `--watch` option. Every time a file is saved, the utility will rebuild that file so you can simply hit refresh in your browser to see the changes without rebuilding the entire site.
|
48
46
|
|
49
|
-
plate --watch
|
50
|
-
|
51
47
|
### Creating a new post
|
52
|
-
|
48
|
+
|
53
49
|
To create a new blog post with the default options, run:
|
54
50
|
|
55
51
|
plate post "New Post Name"
|
56
|
-
|
52
|
+
|
57
53
|
You can also put default post options (such as a category or layout) into the command line:
|
58
54
|
|
59
55
|
plate post "New Post Name" --category Articles --layout post
|
60
|
-
|
56
|
+
|
61
57
|
Or, if you always use the same default category and/or layout, you can put those options into your config file instead like so:
|
62
58
|
|
63
59
|
# In config/plate.yml
|
@@ -73,14 +69,15 @@ Plate observes the following folder structure in your site:
|
|
73
69
|
|
74
70
|
* config/ - Put your global configuration settings here.
|
75
71
|
* content/ - All custom content for the site, besides blog posts. Everything in this folder will be copied over to the published site.
|
72
|
+
* helpers/ - Helpers are loaded into views automatically and can be used within dynamically rendered pages using a template language. (Such as Erb or Haml)
|
76
73
|
* layouts/ - Global layouts available for use on all content pages and posts.
|
77
|
-
* lib/ - Extend the basic functionality of Plate with plugins in this directory. All `.rb` files
|
74
|
+
* lib/ - Extend the basic functionality of Plate with plugins in this directory. All `.rb` files run against the Plate DSL.
|
78
75
|
* posts/ - All blog post content for the site. Posts can be organized into sub-directories if you like.
|
79
76
|
* public/ - This will be generated if it does not exist, contains the produced site. Set this as the web server root to your site for development mode.
|
80
77
|
|
81
78
|
## Extending Plate
|
82
79
|
|
83
|
-
Plate is meant to be extended easily. You might want to extend the basic functionality of Plate to add additional functionality for your site. To get started, create a directory named `lib` in the root of your site. Any Ruby files (ending in `.rb`) will be automatically loaded into the stack when Plate is run.
|
80
|
+
Plate is meant to be extended easily. You might want to extend the basic functionality of Plate to add additional functionality for your site. To get started, create a directory named `lib` in the root of your site. Any Ruby files (ending in `.rb`) will be automatically loaded into the stack when Plate is run.
|
84
81
|
|
85
82
|
### Callbacks
|
86
83
|
|
@@ -89,28 +86,33 @@ Callbacks are used to call certain blocks of code when an event happens in the l
|
|
89
86
|
The callbacks currently available are:
|
90
87
|
|
91
88
|
* Site - `before_render`, `after_render`
|
92
|
-
* Page/Post - `before_render`, `after_render`
|
89
|
+
* Page/Post - `before_render`, `after_render`, `before_write`, `after_write`
|
90
|
+
* Asset - `before_render`, `after_render`, `before_write`, `after_write`
|
93
91
|
|
94
92
|
Example of a callback to be run when a site completes the build:
|
95
93
|
|
96
|
-
|
94
|
+
register_callback :site, :after_render do |site|
|
97
95
|
puts "the site finished rendering!"
|
98
96
|
end
|
99
97
|
|
98
|
+
Put the above snippet in any file in the lib folder, and it will be registered when your site is compiled.
|
99
|
+
|
100
100
|
### Helpers
|
101
101
|
|
102
102
|
Helpers are modules that are automatically loaded into views. Any methods in the module will be available when you render a page.
|
103
103
|
|
104
|
-
An example of a helper file located in `
|
104
|
+
An example of a helper file located in `helpers/sample_helper.rb`
|
105
105
|
|
106
106
|
module SampleHelper
|
107
107
|
def sample_helper_method
|
108
108
|
"yes"
|
109
109
|
end
|
110
110
|
end
|
111
|
-
|
111
|
+
|
112
112
|
Then, in your `.erb` view you can call `sample_helper_method`.
|
113
113
|
|
114
|
+
All files in the `helpers/` directory are assumed to be helper modules and will be loaded automatically at runtime.
|
115
|
+
|
114
116
|
## Full Documentation
|
115
117
|
|
116
118
|
View the [full documentation on rdoc.info](http://rdoc.info/gems/plate/frames)
|
data/Rakefile
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'rake'
|
3
|
-
require 'yard'
|
4
3
|
|
5
4
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), *%w(lib)))
|
6
5
|
|
@@ -17,11 +16,16 @@ end
|
|
17
16
|
namespace :test do
|
18
17
|
desc "Build the sample site and leave its contents in test/sample/public"
|
19
18
|
task :sample do
|
20
|
-
sh %q(
|
19
|
+
sh %q(turn -I"lib:test" test/test_builder.rb -n /sample/)
|
21
20
|
end
|
22
21
|
end
|
23
22
|
|
24
|
-
|
25
|
-
|
23
|
+
begin
|
24
|
+
require 'yard'
|
25
|
+
|
26
|
+
YARD::Rake::YardocTask.new do |t|
|
27
|
+
end
|
26
28
|
|
27
|
-
task :doc => :yard
|
29
|
+
task :doc => :yard
|
30
|
+
rescue LoadError
|
31
|
+
end
|
data/lib/plate.rb
CHANGED
@@ -10,44 +10,49 @@ require 'plate/errors'
|
|
10
10
|
|
11
11
|
module Plate
|
12
12
|
autoload :CLI, 'plate/cli'
|
13
|
-
|
13
|
+
autoload :Dsl, 'plate/dsl'
|
14
|
+
|
14
15
|
autoload :Builder, 'plate/builder'
|
15
16
|
autoload :Callbacks, 'plate/callbacks'
|
16
17
|
autoload :Layout, 'plate/layout'
|
17
18
|
autoload :Site, 'plate/site'
|
18
|
-
|
19
|
+
|
19
20
|
autoload :BloggingHelper, 'plate/helpers/blogging_helper'
|
20
21
|
autoload :MetaHelper, 'plate/helpers/meta_helper'
|
21
22
|
autoload :URLHelper, 'plate/helpers/url_helper'
|
22
|
-
|
23
|
+
|
23
24
|
autoload :View, 'plate/view'
|
24
|
-
|
25
|
+
|
25
26
|
autoload :PostCollection, 'plate/post_collection'
|
26
|
-
|
27
|
+
autoload :HashProxy, 'plate/hash_proxy'
|
28
|
+
|
27
29
|
autoload :Asset, 'plate/asset'
|
28
30
|
autoload :DynamicPage, 'plate/dynamic_page'
|
29
31
|
autoload :Page, 'plate/page'
|
30
32
|
autoload :Post, 'plate/post'
|
31
33
|
autoload :StaticPage, 'plate/static_page'
|
32
|
-
|
33
|
-
autoload :Engine, 'plate/engine'
|
34
|
+
|
35
|
+
autoload :Engine, 'plate/engine'
|
36
|
+
|
34
37
|
autoload :HamlTemplate, 'plate/haml_template'
|
38
|
+
autoload :LessTemplate, 'plate/less_template'
|
35
39
|
autoload :MarkdownTemplate, 'plate/markdown_template'
|
36
40
|
autoload :SassTemplate, 'plate/sass_template'
|
37
41
|
autoload :ScssTemplate, 'plate/scss_template'
|
38
|
-
|
42
|
+
|
39
43
|
extend Engine
|
40
44
|
@engines ||= {}
|
41
|
-
|
45
|
+
|
42
46
|
# Set up the basic engines that are supported by Plate. Add your own this same way.
|
43
47
|
# Thanks to sprockets for the inspiration.
|
44
48
|
# https://github.com/sstephenson/sprockets
|
45
|
-
|
49
|
+
|
46
50
|
# Assets
|
47
51
|
register_asset_engine :coffee, Tilt::CoffeeScriptTemplate
|
52
|
+
register_asset_engine :less, LessTemplate
|
48
53
|
register_asset_engine :sass, SassTemplate
|
49
54
|
register_asset_engine :scss, ScssTemplate
|
50
|
-
|
55
|
+
|
51
56
|
# Layouts & Markup
|
52
57
|
register_template_engine :erb, Tilt::ERBTemplate
|
53
58
|
register_template_engine :haml, HamlTemplate
|
data/lib/plate/asset.rb
CHANGED
@@ -1,50 +1,113 @@
|
|
1
1
|
module Plate
|
2
|
-
# An asset is a
|
3
|
-
# to the destination.
|
2
|
+
# An asset is a dynamic stylesheet or javascript file that needs some processing before it is ready
|
3
|
+
# to be placed into the site's destination directory.
|
4
|
+
#
|
5
|
+
# Some examples of supported asset formats are [CoffeeScript](http://coffeescript.org)
|
6
|
+
# and [Sass](http://sass-lang.com).
|
7
|
+
#
|
8
|
+
# # Asset Engines
|
9
|
+
#
|
10
|
+
# Assets are compiled based on the file extensions of the source file, and the so-called "engines"
|
11
|
+
# that are available and registered to Plate. By default, Plate does not require any of these
|
12
|
+
# asset engines to run, so any external dependencies will need to be installed before attempting
|
13
|
+
# to use an asset engine.
|
14
|
+
#
|
15
|
+
# For example, in order to use the CoffeeScript engine (and thus a file with the extension of
|
16
|
+
# `.coffee`) the CoffeeScript gem will need to be installed. (`gem install coffee-script`).
|
17
|
+
#
|
18
|
+
# # File Naming
|
19
|
+
#
|
20
|
+
# For best results, name your source files exactly how you would like them to be displayed
|
21
|
+
# once compiled.
|
22
|
+
#
|
23
|
+
# To create a file in the destination named site.js that is compiled from a CoffeeScript source,
|
24
|
+
# name the file in the source directory site.js.coffee. The asset compilation engine will
|
25
|
+
# stop trying to render the file with an asset engine when it finds the first non-registered
|
26
|
+
# file extension. In this case, "js" is not recognized as an asset engine extension that needs
|
27
|
+
# compilation, so compilation stops and the file is written to the destination.
|
4
28
|
class Asset < Page
|
29
|
+
# The engines in use for this asset. Engines are determined by looping through
|
30
|
+
# the extensions of the base file and associating those with a registered engine.
|
31
|
+
#
|
32
|
+
# @return [Array] List of engines in use for this file.
|
5
33
|
def engines
|
6
34
|
@engines ||= self.extensions.reverse.collect { |e| self.site.registered_asset_engines[e.gsub(/\./, '').to_sym] }.reject { |e| !e }
|
7
35
|
end
|
8
|
-
|
36
|
+
|
37
|
+
# The file extensions for this asset's source file.
|
38
|
+
#
|
39
|
+
# @return [Array] List of extensions
|
9
40
|
def extensions
|
10
41
|
@extensions ||= self.basename.scan(/\.[^.]+/)
|
11
42
|
end
|
12
|
-
|
43
|
+
|
44
|
+
# The end result format for this file. This is the first file extension in the asset's
|
45
|
+
# source file name that is not a registered engine extension.
|
46
|
+
#
|
47
|
+
# For example, a file named `screen.css.css` will have a format extension of "css".
|
48
|
+
#
|
49
|
+
# @return [String]
|
13
50
|
def format_extension
|
14
51
|
self.extensions.reverse.detect { |e| !self.site.asset_engine_extensions.include?(e) }
|
15
52
|
end
|
16
|
-
|
17
|
-
|
18
|
-
|
53
|
+
|
54
|
+
# The destination file path for this asset.
|
55
|
+
#
|
56
|
+
# @return [String]
|
57
|
+
def file_path
|
58
|
+
[ directory, file_name ].join('/')
|
19
59
|
end
|
20
|
-
|
60
|
+
|
61
|
+
# Directory name for the source asset file
|
62
|
+
#
|
63
|
+
# @return [String]
|
21
64
|
def pathname
|
22
65
|
File.dirname(self.file)
|
23
66
|
end
|
24
|
-
|
25
|
-
#
|
67
|
+
|
68
|
+
# Generates the resulting content for this asset using the `engines` determined
|
69
|
+
# by the source file's name. Unlike a page, assets do not render content
|
70
|
+
# using a layout.
|
71
|
+
#
|
72
|
+
# @return [String]
|
26
73
|
def rendered_content
|
27
74
|
return @rendered_content if @rendered_content
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
75
|
+
|
76
|
+
around_callback :render do
|
77
|
+
result = File.read(file)
|
78
|
+
|
79
|
+
self.engines.each do |engine|
|
80
|
+
template = engine.new() { result }
|
81
|
+
result = template.render(self, :site => self.site)
|
82
|
+
end
|
83
|
+
|
84
|
+
@rendered_content = result
|
34
85
|
end
|
35
|
-
|
36
|
-
@rendered_content
|
86
|
+
|
87
|
+
@rendered_content
|
37
88
|
end
|
38
|
-
|
39
|
-
# Write this
|
40
|
-
# in
|
89
|
+
|
90
|
+
# Write this asset file to the destination. The content is written to disk using the path
|
91
|
+
# designated in {#file_path} and the content from {#rendered_content}.
|
92
|
+
#
|
93
|
+
# The callbacks `before_write` and `after_write` are included here. To perform
|
94
|
+
# custom actions before or after an asset file is written to disk, use these callback
|
95
|
+
# methods.
|
96
|
+
#
|
97
|
+
# See {Plate::Callbacks} for more information on setting up callbacks.
|
98
|
+
#
|
99
|
+
# @return [String] The file path that was written to.
|
41
100
|
def write!
|
42
101
|
path = File.join(site.build_destination, file_path)
|
43
102
|
FileUtils.mkdir_p(File.dirname(path))
|
44
103
|
|
45
|
-
|
46
|
-
|
104
|
+
around_callback :write do
|
105
|
+
File.open(path, 'w') do |f|
|
106
|
+
f.write(self.rendered_content)
|
107
|
+
end
|
47
108
|
end
|
109
|
+
|
110
|
+
path
|
48
111
|
end
|
49
112
|
end
|
50
113
|
end
|
data/lib/plate/builder.rb
CHANGED
@@ -1,257 +1,357 @@
|
|
1
1
|
require 'yaml'
|
2
2
|
require 'digest'
|
3
3
|
|
4
|
-
module Plate
|
5
|
-
#
|
4
|
+
module Plate
|
5
|
+
# The builder is used to compile create a site from a source directory and output it to a destination.
|
6
|
+
#
|
7
|
+
# In most cases, the Builder is called directly from the command line tools (see {Plate::CLI}) and does
|
8
|
+
# not need to be called directly.
|
6
9
|
class Builder
|
7
|
-
|
8
|
-
|
10
|
+
include Callbacks
|
11
|
+
|
12
|
+
# @return [String] The destination directory path where the site should be placed upon a completed build.
|
13
|
+
attr_accessor :destination
|
14
|
+
|
15
|
+
# @return [Boolean] Is logging enabled for this build? Set using the `--verbose` command line option.
|
16
|
+
attr_accessor :enable_logging
|
17
|
+
|
18
|
+
# @return [Hash] A hash of meta values loaded from the config file in the site's source.
|
19
|
+
attr_accessor :metadata
|
20
|
+
|
21
|
+
# @return [Hash] The options hash for this site build.
|
22
|
+
attr_accessor :options
|
23
|
+
|
24
|
+
# @return [Site] The site instance for this build.
|
25
|
+
attr_accessor :site
|
26
|
+
|
27
|
+
# @return [String] The source directory path where the site's content is loaded from.
|
28
|
+
attr_accessor :source
|
29
|
+
|
30
|
+
# @return [Array] A list of helper classes loaded from the `source/helpers` directory.
|
31
|
+
attr_reader :helpers
|
32
|
+
|
33
|
+
# Create a new instance of the builder for the given site source, destination
|
34
|
+
# and options.
|
35
|
+
#
|
36
|
+
# In most cases this should be initialized from the command line utilities.
|
9
37
|
def initialize(source, destination, options = {})
|
10
38
|
@source = source
|
11
39
|
@destination = destination
|
12
|
-
@
|
40
|
+
@metadata = {}
|
41
|
+
@options = Hash === options ? options.clone : {}
|
13
42
|
@options.symbolize_keys!
|
14
43
|
end
|
15
|
-
|
44
|
+
|
45
|
+
# The directory where the build details are cached. Caching is used to store
|
46
|
+
# temporary data, and as a temporary spot to build the site in before it is
|
47
|
+
# moved to the destination directory.
|
48
|
+
#
|
49
|
+
# Caching is probably a bad name for this since nothing is truly "cached", it is
|
50
|
+
# merely a temporary directory.
|
51
|
+
#
|
52
|
+
# The directory can be set in a site options, or uses the default which is in the user's
|
53
|
+
# current home directory under ~/.plate...
|
54
|
+
#
|
55
|
+
# @return [String]
|
16
56
|
def cache_location
|
17
57
|
return @cache_location if @cache_location
|
18
|
-
|
58
|
+
|
19
59
|
if self.options.has_key?(:cache_location)
|
20
60
|
@cache_location ||= File.expand_path(self.options[:cache_location])
|
21
61
|
else
|
22
62
|
@cache_location ||= File.expand_path("~/.plate/#{self.id}")
|
23
|
-
end
|
63
|
+
end
|
24
64
|
end
|
25
|
-
|
26
|
-
# Remove any caches from this site build
|
27
|
-
# temporary build folders so they can be reset
|
65
|
+
|
66
|
+
# Remove any caches and temporary data from this site build.
|
28
67
|
def clear_cache!
|
29
68
|
FileUtils.rm_rf(cache_location)
|
30
|
-
|
69
|
+
|
31
70
|
@cache_location = nil
|
32
71
|
@tmp_destination = nil
|
33
72
|
@loaded = false
|
34
73
|
end
|
35
|
-
|
36
|
-
#
|
74
|
+
|
75
|
+
# Loads the site, if not already loaded and returns the options listed.
|
76
|
+
#
|
77
|
+
# @return [Hash]
|
37
78
|
def config
|
38
|
-
|
79
|
+
load!
|
39
80
|
@options
|
40
81
|
end
|
41
|
-
|
42
|
-
# A unique id for this site, based off of the source directory
|
82
|
+
|
83
|
+
# A unique id for this site, based off of the source directory.
|
84
|
+
#
|
85
|
+
# @return [String]
|
43
86
|
def id
|
44
87
|
check_source!
|
45
|
-
|
46
88
|
@id ||= [ File.basename(source), Digest::MD5.hexdigest(source) ].collect { |s| s.to_s.downcase.parameterize }.join('-')
|
47
89
|
end
|
48
|
-
|
90
|
+
|
91
|
+
# Are there any items loaded within this site build? Returns false if there are no items.
|
92
|
+
#
|
93
|
+
# When builing a site from the source directory and there are no items persent, the builder
|
94
|
+
# will exit.
|
95
|
+
#
|
96
|
+
# @return [Boolean]
|
49
97
|
def items?
|
50
98
|
self.total_items > 0
|
51
99
|
end
|
52
|
-
|
100
|
+
|
101
|
+
# Read the source directory from the file system, configure the {Plate::Site} instance,
|
102
|
+
# and load any helpers and plugins.
|
103
|
+
#
|
104
|
+
# The loaded data is cached so this method can safely be called multiple times without
|
105
|
+
# having to read from the filesystem more than once.
|
106
|
+
#
|
107
|
+
# @return [Boolean] Was the source loaded?
|
53
108
|
def load!
|
54
109
|
unless @loaded
|
55
110
|
log('Site builder initialized.')
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
111
|
+
|
112
|
+
require_plugins!
|
113
|
+
load_config_file!
|
114
|
+
setup_site!
|
115
|
+
setup_tmp_directory!
|
116
|
+
|
62
117
|
@loaded = true
|
63
118
|
end
|
64
|
-
|
119
|
+
|
65
120
|
@loaded
|
66
121
|
end
|
67
|
-
|
122
|
+
|
123
|
+
# Utility method to grab the relative path of a specific file from the site's source.
|
124
|
+
#
|
125
|
+
# @return [String] File path relative to source directory.
|
68
126
|
def relative_path(file_or_directory)
|
69
127
|
file_or_directory.gsub(/^#{Regexp.quote(source)}(.*)$/, '\1')
|
70
128
|
end
|
71
|
-
|
129
|
+
|
130
|
+
# Rebuilds the entire site from source. Used when watching a directory for changes.
|
131
|
+
#
|
132
|
+
# @return [Boolean]
|
72
133
|
def rebuild!
|
73
134
|
log('Re-rendering site...')
|
74
|
-
|
135
|
+
|
75
136
|
clear_cache!
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
137
|
+
|
138
|
+
site.reload!
|
139
|
+
render_site!
|
140
|
+
copy_to_destination!
|
141
|
+
|
81
142
|
true
|
82
143
|
end
|
83
|
-
|
144
|
+
|
84
145
|
# When watching a directory for changes, allow reloading of site content based on modifications
|
85
146
|
# only in the content, layouts and posts folder. Changes to config or lib files will need to
|
86
147
|
# be reloaded manually.
|
148
|
+
#
|
149
|
+
# @return [Boolean]
|
87
150
|
def reloadable?(relative_file)
|
88
151
|
relative_file =~ /^\/?(content|layouts|posts)\/(.*?)/
|
89
152
|
end
|
90
|
-
|
91
|
-
#
|
153
|
+
|
154
|
+
# Start the rendering of the site based on the provided, source, destination and
|
155
|
+
# config options.
|
156
|
+
#
|
157
|
+
# The site will be loaded if it has not already been read from source, and rendered first to
|
158
|
+
# the temporary {#cache_location}
|
159
|
+
#
|
160
|
+
# @return [Boolean]
|
92
161
|
def render!
|
93
162
|
@start_time = Time.now
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
163
|
+
|
164
|
+
around_callback :render do
|
165
|
+
log("Building full site...")
|
166
|
+
|
167
|
+
load!
|
168
|
+
render_site!
|
169
|
+
copy_to_destination!
|
170
|
+
|
171
|
+
@end_time = Time.now
|
172
|
+
|
173
|
+
log("Site build completed in #{timer} seconds")
|
174
|
+
end
|
175
|
+
|
105
176
|
true
|
106
177
|
end
|
107
|
-
|
178
|
+
|
179
|
+
# Render a single file from the source to destination directories. This is used
|
180
|
+
# when watching a site for changes and recompiling a specific file on demand.
|
181
|
+
#
|
182
|
+
# For layout files, the entire site is reloaded. For all other files, only that file
|
183
|
+
# and corresponding destination file are reloaded.
|
184
|
+
#
|
185
|
+
# This may produce some unexpected behavior due to plugins and helpers not being reloaded and
|
186
|
+
# is still considered experimental.
|
187
|
+
#
|
188
|
+
# @return [Boolean]
|
108
189
|
def render_file!(relative_file_path)
|
109
|
-
|
110
|
-
|
111
|
-
page =
|
112
|
-
|
190
|
+
load!
|
191
|
+
|
192
|
+
page = site.find(relative_file_path)
|
193
|
+
|
194
|
+
# Ensure that the file path it is trying to reload actually exists
|
113
195
|
if page and page.file?
|
114
|
-
#
|
196
|
+
# If the file is a layout, rebuild all pages using it
|
115
197
|
if Layout === page
|
116
198
|
page.reload!
|
117
|
-
|
199
|
+
|
118
200
|
log("Building layout [#{page.relative_file}]")
|
119
|
-
|
120
|
-
|
201
|
+
|
202
|
+
# Rebuild all pages using that particular layout.
|
203
|
+
site.find_by_layout(page.relative_file).each do |layout_page|
|
121
204
|
self.render_file!(layout_page.relative_file)
|
122
205
|
end
|
123
206
|
else
|
124
207
|
log("Building file [#{page.relative_file}]")
|
125
|
-
|
126
|
-
#
|
208
|
+
|
209
|
+
# Does the file have an existing temporary file in the {#cache_location}
|
127
210
|
existing_tmp = File.join(tmp_destination, page.file_path)
|
128
|
-
|
211
|
+
|
129
212
|
if File.exists?(existing_tmp)
|
130
213
|
FileUtils.rm_rf(existing_tmp)
|
131
214
|
end
|
132
|
-
|
215
|
+
|
133
216
|
page.reload!
|
134
217
|
page.write!
|
135
|
-
|
218
|
+
|
136
219
|
# File should exist again, even though we just removed it since we re-wrote it.
|
137
220
|
if File.exists?(existing_tmp)
|
138
221
|
existing = File.join(destination, page.file_path)
|
139
222
|
|
140
|
-
if File.exists?(existing)
|
223
|
+
if File.exists?(existing)
|
141
224
|
log("Removing existing file [#{existing}]", :indent)
|
142
225
|
FileUtils.rm_rf(existing)
|
143
226
|
end
|
144
227
|
|
145
228
|
FileUtils.mkdir_p(File.dirname(existing))
|
146
229
|
FileUtils.cp(existing_tmp, existing)
|
147
|
-
|
230
|
+
|
148
231
|
log("File build complete.", :indent)
|
149
232
|
end
|
150
233
|
end
|
151
234
|
else
|
152
235
|
log("Cannot render file, it doesn't exist. [#{relative_file_path}]")
|
153
236
|
end
|
154
|
-
|
237
|
+
|
155
238
|
true
|
156
239
|
end
|
157
|
-
|
158
|
-
#
|
240
|
+
|
241
|
+
# The total number of all assets, layouts pages and posts in the source
|
242
|
+
# directory.
|
243
|
+
#
|
244
|
+
# @return [Integer]
|
159
245
|
def total_items
|
160
|
-
return 0 unless
|
161
|
-
@total_items ||=
|
246
|
+
return 0 unless site
|
247
|
+
@total_items ||= site.all_files.size
|
162
248
|
end
|
163
|
-
|
249
|
+
|
164
250
|
# Returns the time it took to run render! (in milliseconds)
|
251
|
+
#
|
252
|
+
# @return [Float] Milliseconds
|
165
253
|
def timer
|
166
|
-
return 0 unless @end_time and @start_time
|
167
|
-
(
|
254
|
+
return 0 unless @end_time and @start_time
|
255
|
+
(@end_time - @start_time).round
|
168
256
|
end
|
169
|
-
|
257
|
+
|
170
258
|
# The directory path of where to put the files while the site is being built.
|
171
259
|
#
|
172
260
|
# If this value is nil, no temporary directory is used and files are built
|
173
261
|
# directly in the normal destination folder.
|
262
|
+
#
|
263
|
+
# @return [String] Full file path
|
174
264
|
def tmp_destination
|
175
265
|
return @tmp_destination if @tmp_destination
|
176
|
-
|
266
|
+
|
177
267
|
result = ""
|
178
|
-
|
179
|
-
if
|
180
|
-
if
|
181
|
-
result = File.expand_path(
|
268
|
+
|
269
|
+
if options.has_key?(:tmp_destination)
|
270
|
+
if options[:tmp_destination]
|
271
|
+
result = File.expand_path(options[:tmp_destination])
|
182
272
|
end
|
183
273
|
else
|
184
274
|
result = File.join(cache_location, 'build-cache')
|
185
275
|
end
|
186
|
-
|
276
|
+
|
187
277
|
@tmp_destination = result
|
188
278
|
end
|
189
|
-
|
279
|
+
|
280
|
+
# Is this site using a temporary destination location?
|
281
|
+
#
|
282
|
+
# @return [Boolean]
|
190
283
|
def tmp_destination?
|
191
|
-
|
284
|
+
tmp_destination.to_s.size > 0
|
192
285
|
end
|
193
|
-
|
286
|
+
|
194
287
|
protected
|
195
288
|
# Allows process to continue if the source directory exists. If the source directory does not
|
196
289
|
# exist, raise a source does not exist error.
|
290
|
+
#
|
291
|
+
# @private
|
197
292
|
def check_source!
|
198
293
|
raise SourceNotFound unless directory_exists?(source)
|
199
294
|
end
|
200
|
-
|
295
|
+
|
201
296
|
# Copy all files from within the tmp/ build directory into the actual destination.
|
202
297
|
#
|
203
298
|
# Warning: This will overwrite any files already in the destination.
|
299
|
+
#
|
300
|
+
# @private
|
204
301
|
def copy_to_destination!
|
205
302
|
if items?
|
206
303
|
self.setup_destination!
|
207
|
-
|
304
|
+
|
208
305
|
if tmp_destination?
|
209
|
-
log("Copying content to destination directory")
|
306
|
+
log("Copying content to destination directory")
|
210
307
|
FileUtils.cp_r(Dir.glob("#{tmp_destination}**/*"), destination)
|
211
|
-
end
|
308
|
+
end
|
212
309
|
end
|
213
310
|
end
|
214
|
-
|
311
|
+
|
215
312
|
# Utility method for switching between ruby 1.8* and 1.9+
|
313
|
+
#
|
314
|
+
# @private
|
216
315
|
def directory_exists?(dir)
|
217
316
|
Dir.respond_to?(:exists?) ? Dir.exists?(dir) : File.directory?(dir)
|
218
317
|
end
|
219
|
-
|
318
|
+
|
220
319
|
# Loads the configuration options to use for rendering this site. By default, this information
|
221
320
|
# is loaded from a file located in config/plate.yml. If this file does not exist, no config
|
222
321
|
# data is loaded by default.
|
223
|
-
#
|
322
|
+
#
|
224
323
|
# You can specific additional options by passing them into the options block of this class:
|
225
324
|
#
|
226
325
|
# ## Custom Config File
|
227
|
-
#
|
228
|
-
# To load a different file, pass in the relative path of that file to the source root into the :config
|
326
|
+
#
|
327
|
+
# To load a different file, pass in the relative path of that file to the source root into the :config
|
229
328
|
# option:
|
230
329
|
#
|
231
330
|
# Builder.new(source, destination, :config => 'config/other-file.yml')
|
232
|
-
#
|
233
|
-
# On the command line when building a site, or creating a new post, you can specify the
|
331
|
+
#
|
332
|
+
# On the command line when building a site, or creating a new post, you can specify the
|
234
333
|
# custom config file as a command line option as well:
|
235
334
|
#
|
236
335
|
# plate build --config config/other-file.yml
|
237
|
-
#
|
336
|
+
#
|
337
|
+
# @return [Hash] Options hash. Also set to {#options}
|
238
338
|
def load_config_file!
|
239
339
|
config_file = 'config/plate.yml'
|
240
|
-
|
340
|
+
|
241
341
|
# Check for provided config options
|
242
342
|
if options.has_key?(:config)
|
243
343
|
# If config is false, just return without loading anything.
|
244
344
|
if options[:config] == false
|
245
|
-
log("Skipping config file load.")
|
246
|
-
config_file = false
|
345
|
+
log("Skipping config file load.")
|
346
|
+
config_file = false
|
247
347
|
# If something is provided for config set the config_file
|
248
348
|
else
|
249
349
|
config_file = options[:config]
|
250
350
|
end
|
251
351
|
end
|
252
|
-
|
352
|
+
|
253
353
|
if config_file
|
254
|
-
config_file_path = File.join(
|
354
|
+
config_file_path = File.join(source, config_file)
|
255
355
|
|
256
356
|
log("Checking for config file... [#{config_file_path}]")
|
257
357
|
|
@@ -267,72 +367,101 @@ module Plate
|
|
267
367
|
end
|
268
368
|
end
|
269
369
|
end
|
270
|
-
|
370
|
+
|
371
|
+
# If meta data was provided, add it to the site's meta data instance
|
372
|
+
if @options.has_key?(:meta) and Hash === @options[:meta]
|
373
|
+
@metadata = @options[:meta]
|
374
|
+
@metadata.symbolize_keys!
|
375
|
+
@options.delete(:meta)
|
376
|
+
end
|
377
|
+
|
378
|
+
# If a custom destination was provided in the config file, use it.
|
379
|
+
if @options.has_key?(:destination)
|
380
|
+
@destination = File.expand_path(@options[:destination])
|
381
|
+
end
|
382
|
+
|
271
383
|
# Make sure that the defaults are available.
|
272
384
|
@options.reverse_merge!({
|
273
385
|
:permalink => '/:category/:year/:month/:slug'
|
274
386
|
})
|
275
387
|
end
|
276
|
-
|
388
|
+
|
277
389
|
# Write to the log if enable_logging is enabled
|
390
|
+
#
|
391
|
+
# @private
|
278
392
|
def log(message, style = :arrow)
|
279
|
-
prefix = {
|
393
|
+
prefix = {
|
280
394
|
:arrow => ' -> ',
|
281
395
|
:indent => ' '
|
282
396
|
}[style] || style
|
283
|
-
|
397
|
+
|
284
398
|
puts "#{prefix}#{message}" if !!enable_logging
|
285
399
|
end
|
286
|
-
|
287
|
-
# Build out the site and store it in the destination directory
|
400
|
+
|
401
|
+
# Build out the site and store it in the destination directory.
|
402
|
+
#
|
403
|
+
# @private
|
288
404
|
def render_site!
|
289
405
|
if items?
|
290
406
|
log("Rendering site...")
|
291
|
-
|
407
|
+
|
292
408
|
paths = []
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
paths +=
|
297
|
-
paths +=
|
298
|
-
paths +=
|
299
|
-
|
409
|
+
|
410
|
+
site.run_callback(:before_render)
|
411
|
+
|
412
|
+
paths += site.assets.collect(&:write!)
|
413
|
+
paths += site.pages.collect(&:write!)
|
414
|
+
paths += site.posts.collect(&:write!)
|
415
|
+
|
300
416
|
@build_paths = paths
|
301
|
-
|
302
|
-
|
303
|
-
|
417
|
+
|
418
|
+
site.run_callback(:after_render)
|
419
|
+
|
304
420
|
log("Site rendered!", :indent)
|
305
421
|
else
|
306
422
|
log("No assets, posts or pages found. :(")
|
307
423
|
end
|
308
424
|
end
|
309
|
-
|
310
|
-
# Load any plugins
|
311
|
-
#
|
425
|
+
|
426
|
+
# Load any plugins in the ./lib folder and helper modules in the ./helpers folder.
|
427
|
+
#
|
428
|
+
# Any modules named with the format `SomethingHelper` will automatically be loaded
|
429
|
+
# into all views.
|
430
|
+
#
|
431
|
+
# @private
|
312
432
|
def require_plugins!
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
plugins = Dir.glob(File.join(source, "lib/**/*.rb"))
|
318
|
-
|
319
|
-
if plugins.length > 0
|
433
|
+
# For plugins, load all .rb files in the lib directory, regardless of name.
|
434
|
+
plugin_files = Dir.glob(File.join(source, 'lib/**/*.rb'))
|
435
|
+
|
436
|
+
if plugin_files.length > 0
|
320
437
|
log("Loading plugins...")
|
321
|
-
|
322
|
-
plugins.each do |file|
|
323
|
-
require file
|
324
438
|
|
439
|
+
plugin_files.each do |file|
|
440
|
+
Dsl.evaluate_plugin(file)
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
# For helpers, load all files that match `abc_helper.rb`, then check to make sure
|
445
|
+
# there is an appropriately named Module within that file.
|
446
|
+
@helpers = []
|
447
|
+
|
448
|
+
helper_files = Dir.glob(File.join(source, 'helpers/**/*.rb'))
|
449
|
+
matcher = /^#{Regexp.quote(File.join(source, 'helpers'))}\/?(.*).rb$/
|
450
|
+
|
451
|
+
if helper_files.length > 0
|
452
|
+
helper_files.each do |file|
|
325
453
|
underscore_name = file.sub(matcher, '\1')
|
326
454
|
|
327
|
-
# For helpers, make sure the module is defined, and add it to the helpers list
|
328
455
|
if underscore_name =~ /(.*?)_helper$/
|
456
|
+
require file
|
457
|
+
|
329
458
|
class_name = underscore_name.classify
|
330
459
|
|
331
460
|
if defined? class_name
|
332
461
|
log("Loaded helper [#{class_name}]", :indent)
|
333
462
|
|
334
|
-
klass = class_name.constantize
|
335
|
-
|
463
|
+
klass = class_name.constantize
|
464
|
+
@helpers << klass
|
336
465
|
|
337
466
|
View.send(:include, klass)
|
338
467
|
end
|
@@ -340,49 +469,56 @@ module Plate
|
|
340
469
|
end
|
341
470
|
end
|
342
471
|
end
|
343
|
-
|
344
|
-
# Clear out the destination directory, if it exists. Leave the root of the
|
472
|
+
|
473
|
+
# Clear out the destination directory, if it exists. Leave the root of the
|
345
474
|
# destination itself, but clear any files within it.
|
475
|
+
#
|
476
|
+
# @private
|
346
477
|
def setup_destination!
|
347
478
|
if directory_exists?(destination)
|
348
479
|
log("Clearing destination directory [#{destination}]")
|
349
|
-
|
480
|
+
|
350
481
|
FileUtils.rm_r(Dir.glob("#{destination}**/*"), :force => true)
|
351
482
|
elsif items?
|
352
483
|
log("Creating destination directory [#{destination}]")
|
353
|
-
|
354
|
-
FileUtils.mkdir_p(destination)
|
484
|
+
|
485
|
+
FileUtils.mkdir_p(destination)
|
355
486
|
end
|
356
487
|
end
|
357
|
-
|
488
|
+
|
358
489
|
# Setup the Site instance and prepare it for loading
|
490
|
+
#
|
491
|
+
# @private
|
359
492
|
def setup_site!
|
360
493
|
log("Setting up site instance")
|
361
|
-
|
362
|
-
self.site = Site.new(source, destination, options)
|
494
|
+
|
495
|
+
self.site = Site.new(source, destination, options)
|
363
496
|
self.site.logger = self
|
364
497
|
self.site.cache_location = self.cache_location
|
365
|
-
|
498
|
+
self.site.metadata = self.metadata
|
499
|
+
|
366
500
|
log("Site data loaded from source")
|
367
501
|
end
|
368
|
-
|
369
|
-
# Create a temporary folder to build everything in. Once the build was successful,
|
502
|
+
|
503
|
+
# Create a temporary folder to build everything in. Once the build was successful,
|
370
504
|
# all files will then be placed into the actual destination.
|
505
|
+
#
|
506
|
+
# @private
|
371
507
|
def setup_tmp_directory!
|
372
508
|
return unless tmp_destination?
|
373
|
-
|
509
|
+
|
374
510
|
log("Setting up tmp build directory [#{tmp_destination}]")
|
375
|
-
|
511
|
+
|
376
512
|
# Clear out any existing tmp folder contents
|
377
513
|
if directory_exists?(tmp_destination)
|
378
514
|
log("Clearing existing tmp directory content")
|
379
|
-
|
515
|
+
|
380
516
|
FileUtils.rm_rf(tmp_destination)
|
381
517
|
end
|
382
|
-
|
518
|
+
|
383
519
|
FileUtils.mkdir_p(tmp_destination)
|
384
|
-
|
385
|
-
|
520
|
+
|
521
|
+
site.build_destination = tmp_destination
|
386
522
|
end
|
387
523
|
end
|
388
524
|
end
|