nanoc 1.6.2 → 2.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 +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
|