nanoc 1.6.2 → 2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/ChangeLog +27 -0
- data/Rakefile +34 -27
- data/bin/nanoc +153 -49
- data/lib/nanoc.rb +15 -33
- data/lib/nanoc/base/auto_compiler.rb +124 -0
- data/lib/nanoc/base/compiler.rb +55 -0
- data/lib/nanoc/base/core_ext/hash.rb +34 -0
- data/lib/nanoc/base/data_source.rb +53 -0
- data/lib/nanoc/base/enhancements.rb +89 -0
- data/lib/nanoc/base/filter.rb +16 -0
- data/lib/nanoc/base/layout_processor.rb +33 -0
- data/lib/nanoc/base/page.rb +155 -0
- data/lib/nanoc/base/page_proxy.rb +31 -0
- data/lib/nanoc/base/plugin.rb +19 -0
- data/lib/nanoc/base/plugin_manager.rb +33 -0
- data/lib/nanoc/base/site.rb +143 -0
- data/lib/nanoc/data_sources/database.rb +259 -0
- data/lib/nanoc/data_sources/filesystem.rb +308 -0
- data/lib/nanoc/data_sources/trivial.rb +145 -0
- data/lib/nanoc/filters/erb.rb +34 -0
- data/lib/nanoc/filters/haml.rb +16 -0
- data/lib/nanoc/filters/markaby.rb +15 -0
- data/lib/nanoc/filters/markdown.rb +13 -0
- data/lib/nanoc/filters/rdoc.rb +14 -0
- data/lib/nanoc/filters/smartypants.rb +13 -0
- data/lib/nanoc/filters/textile.rb +13 -0
- data/lib/nanoc/layout_processors/erb.rb +35 -0
- data/lib/nanoc/layout_processors/haml.rb +18 -0
- data/lib/nanoc/layout_processors/markaby.rb +16 -0
- metadata +37 -30
- data/lib/nanoc/compiler.rb +0 -145
- data/lib/nanoc/core_ext.rb +0 -1
- data/lib/nanoc/core_ext/array.rb +0 -17
- data/lib/nanoc/core_ext/hash.rb +0 -43
- data/lib/nanoc/core_ext/string.rb +0 -13
- data/lib/nanoc/core_ext/yaml.rb +0 -10
- data/lib/nanoc/creator.rb +0 -180
- data/lib/nanoc/enhancements.rb +0 -101
- data/lib/nanoc/filters.rb +0 -7
- data/lib/nanoc/filters/eruby_filter.rb +0 -39
- data/lib/nanoc/filters/haml_filter.rb +0 -18
- data/lib/nanoc/filters/liquid_filter.rb +0 -47
- data/lib/nanoc/filters/markaby_filter.rb +0 -15
- data/lib/nanoc/filters/markdown_filter.rb +0 -13
- data/lib/nanoc/filters/rdoc_filter.rb +0 -15
- data/lib/nanoc/filters/sass_filter.rb +0 -13
- data/lib/nanoc/filters/smartypants_filter.rb +0 -13
- data/lib/nanoc/filters/textile_filter.rb +0 -13
- data/lib/nanoc/page.rb +0 -171
- data/lib/nanoc/page_drop.rb +0 -18
- data/lib/nanoc/page_proxy.rb +0 -30
@@ -0,0 +1,55 @@
|
|
1
|
+
module Nanoc
|
2
|
+
class Compiler
|
3
|
+
|
4
|
+
attr_reader :stack
|
5
|
+
|
6
|
+
def initialize(site)
|
7
|
+
@site = site
|
8
|
+
end
|
9
|
+
|
10
|
+
def run(page=nil)
|
11
|
+
# Give feedback
|
12
|
+
log(:high, "Compiling #{page.nil? ? 'site' : 'page'}...")
|
13
|
+
time_before = Time.now
|
14
|
+
|
15
|
+
# Get the data we need
|
16
|
+
@site.load_data
|
17
|
+
eval(@site.code, $nanoc_binding)
|
18
|
+
|
19
|
+
# Create output directory if necessary
|
20
|
+
FileUtils.mkdir_p(@site.config[:output_dir])
|
21
|
+
|
22
|
+
# Compile
|
23
|
+
@stack = []
|
24
|
+
pages = (page.nil? ? @site.pages : [ page ])
|
25
|
+
pages.each do |current_page|
|
26
|
+
begin
|
27
|
+
current_page.compile
|
28
|
+
rescue => exception
|
29
|
+
handle_exception(exception, current_page, !page.nil?)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Give feedback
|
34
|
+
log(:high, "No pages were modified.") unless pages.any? { |page| page.modified? }
|
35
|
+
log(:high, "#{page.nil? ? 'Site' : 'Pages'} compiled in #{format('%.2f', Time.now - time_before)}s.")
|
36
|
+
end
|
37
|
+
|
38
|
+
def handle_exception(exception, page, single_page)
|
39
|
+
raise exception if single_page
|
40
|
+
|
41
|
+
log(:high, "ERROR: An exception occured while compiling page #{page.path}.", $stderr)
|
42
|
+
log(:high, "", $stderr)
|
43
|
+
log(:high, "If you think this is a bug in nanoc, please do report it at", $stderr)
|
44
|
+
log(:high, "<http://nanoc.stoneship.org/trac/newticket> -- thanks!", $stderr)
|
45
|
+
log(:high, "", $stderr)
|
46
|
+
log(:high, 'Message:', $stderr)
|
47
|
+
log(:high, ' ' + exception.message, $stderr)
|
48
|
+
log(:high, 'Backtrace:', $stderr)
|
49
|
+
log(:high, exception.backtrace.map { |t| ' - ' + t }.join("\n"), $stderr)
|
50
|
+
|
51
|
+
exit(1)
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'time'
|
2
|
+
|
3
|
+
class Hash
|
4
|
+
|
5
|
+
# Cleans up the hash and returns the result. It performs the following
|
6
|
+
# operations:
|
7
|
+
#
|
8
|
+
# * Values with keys ending in _at and _on are converted into Times and
|
9
|
+
# Dates, respectively
|
10
|
+
# * All keys are converted to symbols
|
11
|
+
# * Value strings 'true', 'false', and 'none' are converted into
|
12
|
+
# true, false, and nil, respectively
|
13
|
+
def clean
|
14
|
+
inject({}) do |hash, (key, value)|
|
15
|
+
real_key = key.to_s
|
16
|
+
if real_key =~ /_on$/
|
17
|
+
hash.merge(key.to_sym => Date.parse(value))
|
18
|
+
elsif real_key =~ /_at$/
|
19
|
+
hash.merge(key.to_sym => Time.parse(value))
|
20
|
+
elsif value == 'true'
|
21
|
+
hash.merge(key.to_sym => true)
|
22
|
+
elsif value == 'false'
|
23
|
+
hash.merge(key.to_sym => false)
|
24
|
+
elsif value == 'none'
|
25
|
+
hash.merge(key.to_sym => nil)
|
26
|
+
elsif value.is_a?(Hash)
|
27
|
+
hash.merge(key.to_sym => value.clean)
|
28
|
+
else
|
29
|
+
hash.merge(key.to_sym => value)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Nanoc
|
2
|
+
class DataSource < Plugin
|
3
|
+
|
4
|
+
attr_reader :config
|
5
|
+
|
6
|
+
def initialize(site)
|
7
|
+
@site = site
|
8
|
+
@references = 0
|
9
|
+
end
|
10
|
+
|
11
|
+
# Preparation
|
12
|
+
|
13
|
+
def loading
|
14
|
+
# Load if necessary
|
15
|
+
up if @references == 0
|
16
|
+
@references += 1
|
17
|
+
|
18
|
+
yield
|
19
|
+
ensure
|
20
|
+
# Unload if necessary
|
21
|
+
@references -= 1
|
22
|
+
down if @references == 0
|
23
|
+
end
|
24
|
+
|
25
|
+
def up ; end
|
26
|
+
def down ; end
|
27
|
+
|
28
|
+
def setup ; end
|
29
|
+
|
30
|
+
# Loading data
|
31
|
+
|
32
|
+
def pages ; error 'DataSource#pages must be overridden' ; end
|
33
|
+
def page_defaults ; error 'DataSource#page_defaults must be overridden' ; end
|
34
|
+
def layouts ; error 'DataSource#layouts must be overridden' ; end
|
35
|
+
def templates ; error 'DataSource#templates must be overridden' ; end
|
36
|
+
def code ; error 'DataSource#code must be overridden' ; end
|
37
|
+
|
38
|
+
# Creating data
|
39
|
+
|
40
|
+
def create_page(name, template)
|
41
|
+
error 'DataSource#create_page must be overridden'
|
42
|
+
end
|
43
|
+
|
44
|
+
def create_layout(name)
|
45
|
+
error 'DataSource#create_layout must be overridden'
|
46
|
+
end
|
47
|
+
|
48
|
+
def create_template(name)
|
49
|
+
error 'DataSource#create_template must be overridden'
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# Get requirements
|
2
|
+
begin ; require 'rubygems' ; rescue LoadError ; end
|
3
|
+
require 'yaml'
|
4
|
+
require 'fileutils'
|
5
|
+
|
6
|
+
# Logging (level can be :off, :high, :low)
|
7
|
+
$log_level = :high
|
8
|
+
def log(log_level, s, io=$stdout)
|
9
|
+
io.puts s if ($log_level == :low or $log_level == log_level) and $log_level != :off
|
10
|
+
end
|
11
|
+
|
12
|
+
# Convenience function for printing errors
|
13
|
+
def error(s, pre='ERROR')
|
14
|
+
log(:high, pre + ': ' + s, $stderr)
|
15
|
+
exit(1)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Convenience function for requiring libraries
|
19
|
+
def nanoc_require(x, message="'#{x}' is required to compile this site.")
|
20
|
+
require x
|
21
|
+
rescue LoadError
|
22
|
+
error(message)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Rendering sub-layouts
|
26
|
+
def render(name, other_assigns={})
|
27
|
+
layout = @site.layouts.find { |l| l[:name] == name }
|
28
|
+
layout_processor_class = Nanoc::PluginManager.layout_processor_for_extension(layout[:extension])
|
29
|
+
layout_processor = layout_processor_class.new(@page, @pages, @site.config, @site, other_assigns)
|
30
|
+
layout_processor.run(layout[:content])
|
31
|
+
end
|
32
|
+
|
33
|
+
# Convenience function for cd'ing in and out of a directory
|
34
|
+
def in_dir(path)
|
35
|
+
FileUtils.cd(File.join(path))
|
36
|
+
yield
|
37
|
+
ensure
|
38
|
+
FileUtils.cd(File.join(path.map { |n| '..' }))
|
39
|
+
end
|
40
|
+
|
41
|
+
class FileManager
|
42
|
+
|
43
|
+
ACTION_COLORS = {
|
44
|
+
:create => "\e[1m" + "\e[32m", # bold + green
|
45
|
+
:update => "\e[1m" + "\e[33m", # bold + yellow
|
46
|
+
:identical => "\e[1m" # bold
|
47
|
+
}
|
48
|
+
|
49
|
+
def self.file_log(log_level, action, path)
|
50
|
+
log(log_level, '%s%12s%s %s' % [ACTION_COLORS[action.to_sym], action, "\e[0m", path])
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.create_dir(name)
|
54
|
+
# Check whether directory exists
|
55
|
+
return if File.exist?(name)
|
56
|
+
|
57
|
+
# Create dir
|
58
|
+
FileUtils.mkdir_p(name)
|
59
|
+
log(:create, name)
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.create_file(path)
|
63
|
+
# Create parent directory if necessary
|
64
|
+
if path =~ /\//
|
65
|
+
parent_path = path.sub(/\/[^\/]+$/, '')
|
66
|
+
FileManager.create_dir(parent_path)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Get contents
|
70
|
+
content_old = File.exist?(path) ? File.read(path) : nil
|
71
|
+
content_new = yield
|
72
|
+
content_new = content_new.force_encoding(content_old.encoding) if content_old and String.method_defined?(:force_encoding)
|
73
|
+
modified = (content_old != content_new)
|
74
|
+
|
75
|
+
# Log
|
76
|
+
if File.exist?(path)
|
77
|
+
file_log(*(modified ? [ :high, :update, path ] : [ :low, :identical, path ]))
|
78
|
+
else
|
79
|
+
file_log(:high, :create, path)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Write
|
83
|
+
open(path, 'w') { |io| io.write(content_new) }
|
84
|
+
|
85
|
+
# Report back
|
86
|
+
modified
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Nanoc
|
2
|
+
class LayoutProcessor < Plugin
|
3
|
+
|
4
|
+
def initialize(page, pages, config, site, other_assigns={})
|
5
|
+
@page = page
|
6
|
+
@pages = pages
|
7
|
+
@config = config
|
8
|
+
@site = site
|
9
|
+
@other_assigns = other_assigns
|
10
|
+
end
|
11
|
+
|
12
|
+
def run(layout)
|
13
|
+
error 'LayoutProcessor#run must be overridden'
|
14
|
+
end
|
15
|
+
|
16
|
+
# Extensions
|
17
|
+
|
18
|
+
class << self
|
19
|
+
attr_accessor :_extensions
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.extensions(*extensions)
|
23
|
+
self._extensions = [] unless instance_variable_defined?(:@_extensions)
|
24
|
+
extensions.empty? ? self._extensions || [] : self._extensions = (self._extensions || []) + extensions
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.extension(extension=nil)
|
28
|
+
self._extensions = [] unless instance_variable_defined?(:@_extensions)
|
29
|
+
extension.nil? ? self.extensions.first : self.extensions(extension)
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
module Nanoc
|
2
|
+
class Page
|
3
|
+
|
4
|
+
PAGE_DEFAULTS = {
|
5
|
+
:custom_path => nil,
|
6
|
+
:extension => 'html',
|
7
|
+
:filename => 'index',
|
8
|
+
:filters_pre => [],
|
9
|
+
:filters_post => [],
|
10
|
+
:haml_options => {},
|
11
|
+
:is_draft => false,
|
12
|
+
:layout => 'default',
|
13
|
+
:path => nil,
|
14
|
+
:skip_output => false
|
15
|
+
}
|
16
|
+
|
17
|
+
attr_reader :attributes
|
18
|
+
attr_accessor :parent, :children
|
19
|
+
|
20
|
+
def initialize(hash, site)
|
21
|
+
@site = site
|
22
|
+
@compiler = site.compiler
|
23
|
+
|
24
|
+
@attributes = hash
|
25
|
+
@content = { :pre => attribute_named(:uncompiled_content), :post => nil }
|
26
|
+
|
27
|
+
@parent = nil
|
28
|
+
@children = []
|
29
|
+
|
30
|
+
@filtered_pre = false
|
31
|
+
@layouted = false
|
32
|
+
@filtered_post = false
|
33
|
+
@written = false
|
34
|
+
end
|
35
|
+
|
36
|
+
# Proxy support
|
37
|
+
|
38
|
+
def to_proxy
|
39
|
+
@proxy ||= PageProxy.new(self)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Accessors, kind of
|
43
|
+
|
44
|
+
def modified? ; @modified ; end
|
45
|
+
|
46
|
+
def attribute_named(name)
|
47
|
+
return @attributes[name] if @attributes.has_key?(name)
|
48
|
+
return @site.page_defaults[name] if @site.page_defaults.has_key?(name)
|
49
|
+
return PAGE_DEFAULTS[name]
|
50
|
+
end
|
51
|
+
|
52
|
+
def content
|
53
|
+
compile(false) unless @filtered_pre
|
54
|
+
@content[:pre]
|
55
|
+
end
|
56
|
+
|
57
|
+
def layouted_content
|
58
|
+
compile(true)
|
59
|
+
@content[:post]
|
60
|
+
end
|
61
|
+
|
62
|
+
def skip_output? ; attribute_named(:skip_output) ; end
|
63
|
+
def path ; attribute_named(:path) ; end
|
64
|
+
|
65
|
+
def path_on_filesystem
|
66
|
+
if attribute_named(:custom_path).nil?
|
67
|
+
@site.config[:output_dir] + attribute_named(:path) +
|
68
|
+
attribute_named(:filename) + '.' + attribute_named(:extension)
|
69
|
+
else
|
70
|
+
@site.config[:output_dir] + attribute_named(:custom_path)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Compiling
|
75
|
+
|
76
|
+
def compile(full=true)
|
77
|
+
@modified = false
|
78
|
+
|
79
|
+
# Check for recursive call
|
80
|
+
if @compiler.stack.include?(self)
|
81
|
+
log(:high, "\n" + 'ERROR: Recursive call to page content. Page filter stack:', $stderr)
|
82
|
+
log(:high, " - #{self.attribute_named(:path)}", $stderr)
|
83
|
+
@compiler.stack.each_with_index do |page, i|
|
84
|
+
log(:high, " - #{page.attribute_named(:path)}", $stderr)
|
85
|
+
end
|
86
|
+
exit(1)
|
87
|
+
end
|
88
|
+
|
89
|
+
@compiler.stack.push(self)
|
90
|
+
|
91
|
+
# Filter pre
|
92
|
+
unless @filtered_pre
|
93
|
+
filter(:pre)
|
94
|
+
@filtered_pre = true
|
95
|
+
end
|
96
|
+
|
97
|
+
# Layout
|
98
|
+
if !@layouted and full
|
99
|
+
layout
|
100
|
+
@layouted = true
|
101
|
+
end
|
102
|
+
|
103
|
+
# Filter post
|
104
|
+
if !@filtered_post and full
|
105
|
+
filter(:post)
|
106
|
+
@filtered_post = true
|
107
|
+
end
|
108
|
+
|
109
|
+
# Write
|
110
|
+
if !@written and full
|
111
|
+
@modified = FileManager.create_file(self.path_on_filesystem) { @content[:post] } unless skip_output?
|
112
|
+
@written = true
|
113
|
+
end
|
114
|
+
|
115
|
+
@compiler.stack.pop
|
116
|
+
end
|
117
|
+
|
118
|
+
def filter(stage)
|
119
|
+
# Get filters
|
120
|
+
error 'The `filters` property is no longer supported; please use `filters_pre` instead.' unless attribute_named(:filters).nil?
|
121
|
+
filters = attribute_named(stage == :pre ? :filters_pre : :filters_post)
|
122
|
+
|
123
|
+
filters.each do |filter_name|
|
124
|
+
# Create filter
|
125
|
+
filter_class = PluginManager.filter_named(filter_name)
|
126
|
+
error "Unknown filter: '#{filter_name}'" if filter_class.nil?
|
127
|
+
filter = filter_class.new(self.to_proxy, @site.pages.map { |p| p.to_proxy }, @site.config, @site)
|
128
|
+
|
129
|
+
# Run filter
|
130
|
+
@content[stage] = filter.run(@content[stage])
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def layout
|
135
|
+
# Don't layout if not necessary
|
136
|
+
if attribute_named(:layout).nil?
|
137
|
+
@content[:post] = @content[:pre]
|
138
|
+
return
|
139
|
+
end
|
140
|
+
|
141
|
+
# Find layout
|
142
|
+
layout = @site.layouts.find { |l| l[:name] == attribute_named(:layout) }
|
143
|
+
error 'Unknown layout: ' + attribute_named(:layout) if layout.nil?
|
144
|
+
|
145
|
+
# Find layout processor
|
146
|
+
layout_processor_class = PluginManager.layout_processor_for_extension(layout[:extension])
|
147
|
+
error "Unknown layout processor: '#{layout[:extension]}'" if layout_processor_class.nil?
|
148
|
+
layout_processor = layout_processor_class.new(self.to_proxy, @site.pages.map { |p| p.to_proxy }, @site.config, @site)
|
149
|
+
|
150
|
+
# Layout
|
151
|
+
@content[:post] = layout_processor.run(layout[:content])
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Nanoc
|
2
|
+
class PageProxy
|
3
|
+
|
4
|
+
def initialize(page)
|
5
|
+
@page = page
|
6
|
+
end
|
7
|
+
|
8
|
+
def [](key)
|
9
|
+
real_key = key.to_s.sub(/\?$/, '').to_sym
|
10
|
+
|
11
|
+
if real_key == :content
|
12
|
+
@page.content
|
13
|
+
elsif real_key == :parent
|
14
|
+
@page.parent.nil? ? nil : @page.parent.to_proxy
|
15
|
+
elsif real_key == :children
|
16
|
+
@page.children.map { |page| page.to_proxy }
|
17
|
+
else
|
18
|
+
@page.attribute_named(real_key)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def []=(key, value)
|
23
|
+
@page.attributes[key.to_sym] = value
|
24
|
+
end
|
25
|
+
|
26
|
+
def method_missing(method, *args)
|
27
|
+
self[method]
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|