nanoc 2.0.4 → 2.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/ChangeLog +31 -1
- data/LICENSE +1 -1
- data/README +63 -3
- data/Rakefile +59 -12
- data/bin/nanoc +7 -199
- data/lib/nanoc.rb +83 -12
- data/lib/nanoc/base/asset.rb +113 -0
- data/lib/nanoc/base/asset_defaults.rb +21 -0
- data/lib/nanoc/base/asset_rep.rb +277 -0
- data/lib/nanoc/base/binary_filter.rb +44 -0
- data/lib/nanoc/base/code.rb +41 -0
- data/lib/nanoc/base/compiler.rb +46 -34
- data/lib/nanoc/base/core_ext/hash.rb +51 -7
- data/lib/nanoc/base/core_ext/string.rb +8 -0
- data/lib/nanoc/base/data_source.rb +253 -20
- data/lib/nanoc/base/defaults.rb +30 -0
- data/lib/nanoc/base/enhancements.rb +9 -84
- data/lib/nanoc/base/filter.rb +109 -6
- data/lib/nanoc/base/layout.rb +91 -0
- data/lib/nanoc/base/notification_center.rb +66 -0
- data/lib/nanoc/base/page.rb +94 -126
- data/lib/nanoc/base/page_defaults.rb +20 -0
- data/lib/nanoc/base/page_rep.rb +318 -0
- data/lib/nanoc/base/plugin.rb +57 -9
- data/lib/nanoc/base/proxies/asset_proxy.rb +29 -0
- data/lib/nanoc/base/proxies/asset_rep_proxy.rb +26 -0
- data/lib/nanoc/base/proxies/layout_proxy.rb +25 -0
- data/lib/nanoc/base/proxies/page_proxy.rb +35 -0
- data/lib/nanoc/base/proxies/page_rep_proxy.rb +28 -0
- data/lib/nanoc/base/proxy.rb +37 -0
- data/lib/nanoc/base/router.rb +72 -0
- data/lib/nanoc/base/site.rb +219 -88
- data/lib/nanoc/base/template.rb +64 -0
- data/lib/nanoc/binary_filters/image_science_thumbnail.rb +28 -0
- data/lib/nanoc/cli.rb +1 -0
- data/lib/nanoc/cli/base.rb +219 -0
- data/lib/nanoc/cli/cli.rb +16 -0
- data/lib/nanoc/cli/command.rb +105 -0
- data/lib/nanoc/cli/commands/autocompile.rb +80 -0
- data/lib/nanoc/cli/commands/compile.rb +273 -0
- data/lib/nanoc/cli/commands/create_layout.rb +85 -0
- data/lib/nanoc/cli/commands/create_page.rb +85 -0
- data/lib/nanoc/cli/commands/create_site.rb +327 -0
- data/lib/nanoc/cli/commands/create_template.rb +76 -0
- data/lib/nanoc/cli/commands/help.rb +69 -0
- data/lib/nanoc/cli/commands/info.rb +114 -0
- data/lib/nanoc/cli/commands/switch.rb +141 -0
- data/lib/nanoc/cli/commands/update.rb +91 -0
- data/lib/nanoc/cli/ext.rb +37 -0
- data/lib/nanoc/cli/logger.rb +66 -0
- data/lib/nanoc/cli/option_parser.rb +168 -0
- data/lib/nanoc/data_sources/filesystem.rb +645 -224
- data/lib/nanoc/data_sources/filesystem_combined.rb +495 -0
- data/lib/nanoc/extra/auto_compiler.rb +265 -0
- data/lib/nanoc/extra/context.rb +22 -0
- data/lib/nanoc/extra/core_ext/hash.rb +54 -0
- data/lib/nanoc/extra/core_ext/time.rb +13 -0
- data/lib/nanoc/extra/file_proxy.rb +29 -0
- data/lib/nanoc/extra/vcs.rb +48 -0
- data/lib/nanoc/extra/vcses/bazaar.rb +21 -0
- data/lib/nanoc/extra/vcses/dummy.rb +20 -0
- data/lib/nanoc/extra/vcses/git.rb +21 -0
- data/lib/nanoc/extra/vcses/mercurial.rb +21 -0
- data/lib/nanoc/extra/vcses/subversion.rb +21 -0
- data/lib/nanoc/filters/bluecloth.rb +13 -0
- data/lib/nanoc/filters/erb.rb +6 -22
- data/lib/nanoc/filters/erubis.rb +14 -0
- data/lib/nanoc/filters/haml.rb +7 -23
- data/lib/nanoc/filters/markaby.rb +5 -5
- data/lib/nanoc/filters/maruku.rb +14 -0
- data/lib/nanoc/filters/old.rb +19 -0
- data/lib/nanoc/filters/rdiscount.rb +13 -0
- data/lib/nanoc/filters/rdoc.rb +5 -4
- data/lib/nanoc/filters/redcloth.rb +14 -0
- data/lib/nanoc/filters/rubypants.rb +14 -0
- data/lib/nanoc/filters/sass.rb +13 -0
- data/lib/nanoc/helpers/blogging.rb +170 -0
- data/lib/nanoc/helpers/capturing.rb +59 -0
- data/lib/nanoc/helpers/html_escape.rb +23 -0
- data/lib/nanoc/helpers/link_to.rb +69 -0
- data/lib/nanoc/helpers/render.rb +47 -0
- data/lib/nanoc/helpers/tagging.rb +52 -0
- data/lib/nanoc/helpers/xml_sitemap.rb +58 -0
- data/lib/nanoc/routers/default.rb +54 -0
- data/lib/nanoc/routers/no_dirs.rb +66 -0
- data/lib/nanoc/routers/versioned.rb +79 -0
- metadata +112 -22
- data/lib/nanoc/base/auto_compiler.rb +0 -132
- data/lib/nanoc/base/layout_processor.rb +0 -33
- data/lib/nanoc/base/page_proxy.rb +0 -31
- data/lib/nanoc/base/plugin_manager.rb +0 -33
- data/lib/nanoc/data_sources/database.rb +0 -259
- data/lib/nanoc/data_sources/trivial.rb +0 -145
- data/lib/nanoc/filters/markdown.rb +0 -13
- data/lib/nanoc/filters/smartypants.rb +0 -13
- data/lib/nanoc/filters/textile.rb +0 -13
- data/lib/nanoc/layout_processors/erb.rb +0 -35
- data/lib/nanoc/layout_processors/haml.rb +0 -38
- data/lib/nanoc/layout_processors/markaby.rb +0 -16
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module Nanoc
|
|
2
|
+
|
|
3
|
+
# Nanoc::Defaults represent the default attributes for a given set of
|
|
4
|
+
# objects in the site. It is basically a hash with an optional modification
|
|
5
|
+
# time.
|
|
6
|
+
class Defaults
|
|
7
|
+
|
|
8
|
+
# Th site where this set of defaults belongs to.
|
|
9
|
+
attr_accessor :site
|
|
10
|
+
|
|
11
|
+
# A hash containing the default attributes.
|
|
12
|
+
attr_reader :attributes
|
|
13
|
+
|
|
14
|
+
# The time when this set of defaults was last modified.
|
|
15
|
+
attr_reader :mtime
|
|
16
|
+
|
|
17
|
+
# Creates a new set of defaults.
|
|
18
|
+
#
|
|
19
|
+
# +attributes+:: The hash containing the metadata that individual objects
|
|
20
|
+
# will override.
|
|
21
|
+
#
|
|
22
|
+
# +mtime+:: The time when the defaults were last modified (optional).
|
|
23
|
+
def initialize(attributes, mtime=nil)
|
|
24
|
+
@attributes = attributes.clean
|
|
25
|
+
@mtime = mtime
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
end
|
|
@@ -1,89 +1,14 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
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
|
|
1
|
+
# Convenience function for printing warnings
|
|
2
|
+
def warn(s, pre='WARNING')
|
|
3
|
+
$stderr.puts "#{pre}: #{s}"
|
|
10
4
|
end
|
|
11
5
|
|
|
12
|
-
|
|
13
|
-
def error(s, pre='ERROR')
|
|
14
|
-
log(:high, pre + ': ' + s, $stderr)
|
|
15
|
-
exit(1)
|
|
16
|
-
end
|
|
6
|
+
############################# OLD AND DEPRECATED #############################
|
|
17
7
|
|
|
18
|
-
|
|
19
|
-
|
|
8
|
+
def nanoc_require(x)
|
|
9
|
+
warn(
|
|
10
|
+
"'nanoc_require' is deprecated and will be removed in a future version. Please use 'require' instead.",
|
|
11
|
+
'DEPRECATION WARNING'
|
|
12
|
+
)
|
|
20
13
|
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
14
|
end
|
data/lib/nanoc/base/filter.rb
CHANGED
|
@@ -1,16 +1,119 @@
|
|
|
1
1
|
module Nanoc
|
|
2
|
+
|
|
3
|
+
# Nanoc::Filter is responsible for filtering pages and textual assets
|
|
4
|
+
# (binary assets are filtered using Nanoc::BinaryFilter). It is the
|
|
5
|
+
# (abstract) superclass for all textual filters. Subclasses should override
|
|
6
|
+
# the +run+ method.
|
|
2
7
|
class Filter < Plugin
|
|
3
8
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
# Deprecated
|
|
10
|
+
EXTENSIONS_MAP = {}
|
|
11
|
+
|
|
12
|
+
# Creates a new filter for the given object (page or asset) and site.
|
|
13
|
+
#
|
|
14
|
+
# +kind+:: The kind of object that is passed. Can be either +:page+ or
|
|
15
|
+
# +:asset+.
|
|
16
|
+
#
|
|
17
|
+
# +obj_rep+:: A proxy for the page or asset representation (Nanoc::PageRep
|
|
18
|
+
# or Nanoc::AssetRep) that should be compiled by this filter.
|
|
19
|
+
#
|
|
20
|
+
# +obj+:: A proxy for the page or asset's page (Nanoc::Page or
|
|
21
|
+
# Nanoc::Asset).
|
|
22
|
+
#
|
|
23
|
+
# +site+:: The site (Nanoc::Site) this filter belongs to.
|
|
24
|
+
#
|
|
25
|
+
# +other_assigns+:: A hash containing other variables that should be made
|
|
26
|
+
# available during filtering.
|
|
27
|
+
def initialize(obj_rep, other_assigns={})
|
|
28
|
+
# Determine kind
|
|
29
|
+
@kind = obj_rep.is_a?(Nanoc::PageRep) ? :page : :asset
|
|
30
|
+
|
|
31
|
+
# Set object
|
|
32
|
+
@obj_rep = obj_rep
|
|
33
|
+
@obj = (@kind == :page ? @obj_rep.page : @obj_rep.asset)
|
|
34
|
+
|
|
35
|
+
# Set page/asset and page/asset reps
|
|
36
|
+
if @kind == :page
|
|
37
|
+
@page = @obj
|
|
38
|
+
@page_rep = @obj_rep
|
|
39
|
+
else
|
|
40
|
+
@asset = @obj
|
|
41
|
+
@asset_rep = @obj_rep
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Set site
|
|
45
|
+
@site = @obj.site
|
|
46
|
+
|
|
47
|
+
# Set other assigns
|
|
48
|
+
@other_assigns = other_assigns
|
|
9
49
|
end
|
|
10
50
|
|
|
51
|
+
# Runs the filter. This method returns the filtered content.
|
|
52
|
+
#
|
|
53
|
+
# +content+:: The unprocessed content that should be filtered.
|
|
54
|
+
#
|
|
55
|
+
# Subclasses must implement this method.
|
|
11
56
|
def run(content)
|
|
12
|
-
|
|
57
|
+
raise NotImplementedError.new("Nanoc::Filter subclasses must implement #run")
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Returns a hash with data that should be available.
|
|
61
|
+
def assigns
|
|
62
|
+
@assigns ||= @other_assigns.merge({
|
|
63
|
+
:_obj_rep => @obj_rep,
|
|
64
|
+
:_obj => @obj,
|
|
65
|
+
:page_rep => @kind == :page ? @page_rep.to_proxy : nil,
|
|
66
|
+
:page => @kind == :page ? @page.to_proxy : nil,
|
|
67
|
+
:asset_rep => @kind == :asset ? @asset_rep.to_proxy : nil,
|
|
68
|
+
:asset => @kind == :asset ? @asset.to_proxy : nil,
|
|
69
|
+
:pages => @site.pages.map { |obj| obj.to_proxy },
|
|
70
|
+
:assets => @site.assets.map { |obj| obj.to_proxy },
|
|
71
|
+
:layouts => @site.layouts.map { |obj| obj.to_proxy },
|
|
72
|
+
:config => @site.config,
|
|
73
|
+
:site => @site
|
|
74
|
+
})
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
class << self
|
|
78
|
+
|
|
79
|
+
# Deprecated
|
|
80
|
+
def extensions(*extensions) # :nodoc:
|
|
81
|
+
# Initialize
|
|
82
|
+
@extensions = [] unless instance_variables.include?('@extensions')
|
|
83
|
+
|
|
84
|
+
if extensions.empty?
|
|
85
|
+
@extensions
|
|
86
|
+
else
|
|
87
|
+
@extensions = extensions
|
|
88
|
+
@extensions.each { |e| register_extension(e, self) }
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Deprecated
|
|
93
|
+
def extension(extension=nil) # :nodoc:
|
|
94
|
+
# Initialize
|
|
95
|
+
@extensions = [] unless instance_variables.include?('@extensions')
|
|
96
|
+
|
|
97
|
+
if extension.nil?
|
|
98
|
+
@extensions.first
|
|
99
|
+
else
|
|
100
|
+
@extensions = [ extension ]
|
|
101
|
+
register_extension(extension, self)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Deprecated
|
|
106
|
+
def register_extension(extension, klass)
|
|
107
|
+
EXTENSIONS_MAP[extension] = klass
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Deprecated
|
|
111
|
+
def with_extension(extension)
|
|
112
|
+
EXTENSIONS_MAP[extension]
|
|
113
|
+
end
|
|
114
|
+
|
|
13
115
|
end
|
|
14
116
|
|
|
15
117
|
end
|
|
118
|
+
|
|
16
119
|
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
module Nanoc
|
|
2
|
+
|
|
3
|
+
# A Nanoc::Layout represents a layout in a nanoc site. It has content,
|
|
4
|
+
# attributes (for determining which filter to use for laying out a page), a
|
|
5
|
+
# path (because layouts are organised hierarchically), and a modification
|
|
6
|
+
# time (to speed up compilation).
|
|
7
|
+
class Layout
|
|
8
|
+
|
|
9
|
+
# Default values for layouts.
|
|
10
|
+
DEFAULTS = {
|
|
11
|
+
:filter => 'erb'
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
# The Nanoc::Site this layout belongs to.
|
|
15
|
+
attr_accessor :site
|
|
16
|
+
|
|
17
|
+
# The raw content of this layout.
|
|
18
|
+
attr_reader :content
|
|
19
|
+
|
|
20
|
+
# A hash containing this layout's attributes.
|
|
21
|
+
attr_reader :attributes
|
|
22
|
+
|
|
23
|
+
# This layout's path, starting and ending with a slash.
|
|
24
|
+
attr_reader :path
|
|
25
|
+
|
|
26
|
+
# The time when this layout was last modified.
|
|
27
|
+
attr_reader :mtime
|
|
28
|
+
|
|
29
|
+
# Creates a new layout.
|
|
30
|
+
#
|
|
31
|
+
# +content+:: The raw content of this layout.
|
|
32
|
+
#
|
|
33
|
+
# +attributes+:: A hash containing this layout's attributes.
|
|
34
|
+
#
|
|
35
|
+
# +path+:: This layout's path, starting and ending with a slash.
|
|
36
|
+
#
|
|
37
|
+
# +mtime+:: The time when this layout was last modified.
|
|
38
|
+
def initialize(content, attributes, path, mtime=nil)
|
|
39
|
+
@content = content
|
|
40
|
+
@attributes = attributes.clean
|
|
41
|
+
@path = path.cleaned_path
|
|
42
|
+
@mtime = mtime
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Returns a proxy (Nanoc::LayoutProxy) for this layout.
|
|
46
|
+
def to_proxy
|
|
47
|
+
@proxy ||= LayoutProxy.new(self)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Returns the attribute with the given name.
|
|
51
|
+
def attribute_named(name)
|
|
52
|
+
return @attributes[name] if @attributes.has_key?(name)
|
|
53
|
+
return DEFAULTS[name]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Returns the filter class needed for this layout.
|
|
57
|
+
def filter_class
|
|
58
|
+
if attribute_named(:extension).nil?
|
|
59
|
+
Nanoc::Filter.named(attribute_named(:filter))
|
|
60
|
+
else
|
|
61
|
+
Nanoc::Filter.with_extension(attribute_named(:extension))
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Saves the layout in the database, creating it if it doesn't exist yet or
|
|
66
|
+
# updating it if it already exists. Tells the site's data source to save
|
|
67
|
+
# the layout.
|
|
68
|
+
def save
|
|
69
|
+
@site.data_source.loading do
|
|
70
|
+
@site.data_source.save_layout(self)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Moves the layout to a new path. Tells the site's data source to move the
|
|
75
|
+
# layout.
|
|
76
|
+
def move_to(new_path)
|
|
77
|
+
@site.data_source.loading do
|
|
78
|
+
@site.data_source.move_layout(self, new_path)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Deletes the layout. Tells the site's data source to delete the layout.
|
|
83
|
+
def delete
|
|
84
|
+
@site.data_source.loading do
|
|
85
|
+
@site.data_source.delete_layout(self)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
module Nanoc
|
|
2
|
+
|
|
3
|
+
# Nanoc::NotificationCenter provides a way to send notifications between
|
|
4
|
+
# objects. It allows blocks associated with a certain notification name to
|
|
5
|
+
# be registered; these blocks will be called when the notification with the
|
|
6
|
+
# given name is posted.
|
|
7
|
+
#
|
|
8
|
+
# It is a slightly different implementation of the Observer pattern; the
|
|
9
|
+
# table of subscribers is not stored in the observable object itself, but in
|
|
10
|
+
# the notification center.
|
|
11
|
+
class NotificationCenter
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
|
|
15
|
+
# Adds the given block to the list of blocks that should be called when
|
|
16
|
+
# the notification with the given name is received.
|
|
17
|
+
#
|
|
18
|
+
# +name+:: The name of the notification that will be posted.
|
|
19
|
+
#
|
|
20
|
+
# +id+:: An identifier for the block. This is only used to be able to
|
|
21
|
+
# remove the block (using the remove method) later. Defaults to
|
|
22
|
+
# nil.
|
|
23
|
+
def on(name, id=nil, &block)
|
|
24
|
+
initialize_if_necessary(name)
|
|
25
|
+
|
|
26
|
+
# Add observer
|
|
27
|
+
@notifications[name] << { :id => id, :block => block }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Posts a notification with the given name. All arguments wil be passed
|
|
31
|
+
# to the blocks handling the notification.
|
|
32
|
+
def post(name, *args)
|
|
33
|
+
initialize_if_necessary(name)
|
|
34
|
+
|
|
35
|
+
# Notify all observers
|
|
36
|
+
@notifications[name].each do |observer|
|
|
37
|
+
observer[:block].call(*args)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Removes the block with the given identifier from the list of blocks
|
|
42
|
+
# that should be called when the notification with the given name is
|
|
43
|
+
# posted.
|
|
44
|
+
#
|
|
45
|
+
# +name+:: The name of the notification that will be posted.
|
|
46
|
+
#
|
|
47
|
+
# +id+:: The identifier of the block that should be removed.
|
|
48
|
+
def remove(name, id)
|
|
49
|
+
initialize_if_necessary(name)
|
|
50
|
+
|
|
51
|
+
# Remove relevant observers
|
|
52
|
+
@notifications[name].reject! { |i| i[:id] == id }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def initialize_if_necessary(name)
|
|
58
|
+
@notifications ||= {} # name => observers dictionary
|
|
59
|
+
@notifications[name] ||= [] # list of observers
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
end
|
data/lib/nanoc/base/page.rb
CHANGED
|
@@ -1,160 +1,128 @@
|
|
|
1
1
|
module Nanoc
|
|
2
|
+
|
|
3
|
+
# A Nanoc::Page represents a page in a nanoc site. It has content and
|
|
4
|
+
# attributes, as well as a path. It can also store the modification time to
|
|
5
|
+
# speed up compilation.
|
|
6
|
+
#
|
|
7
|
+
# Each page has a list of page representations or reps (Nanoc::PageRep);
|
|
8
|
+
# compiling a page actually compiles all of its representations.
|
|
2
9
|
class Page
|
|
3
10
|
|
|
4
|
-
|
|
11
|
+
# Default values for pages.
|
|
12
|
+
DEFAULTS = {
|
|
5
13
|
:custom_path => nil,
|
|
6
14
|
:extension => 'html',
|
|
7
15
|
:filename => 'index',
|
|
8
16
|
:filters_pre => [],
|
|
9
17
|
:filters_post => [],
|
|
10
|
-
:haml_options => {},
|
|
11
|
-
:is_draft => false,
|
|
12
18
|
:layout => 'default',
|
|
13
|
-
:path => nil,
|
|
14
19
|
:skip_output => false
|
|
15
20
|
}
|
|
16
21
|
|
|
17
|
-
|
|
18
|
-
attr_accessor :
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
# The Nanoc::Site this page belongs to.
|
|
23
|
+
attr_accessor :site
|
|
24
|
+
|
|
25
|
+
# The parent page of this page. This can be nil even for non-root pages.
|
|
26
|
+
attr_accessor :parent
|
|
27
|
+
|
|
28
|
+
# The child pages of this page.
|
|
29
|
+
attr_accessor :children
|
|
30
|
+
|
|
31
|
+
# This page's raw, uncompiled content.
|
|
32
|
+
attr_reader :content
|
|
33
|
+
|
|
34
|
+
# A hash containing this page's attributes.
|
|
35
|
+
attr_accessor :attributes
|
|
36
|
+
|
|
37
|
+
# This page's path.
|
|
38
|
+
attr_reader :path
|
|
39
|
+
|
|
40
|
+
# The time when this page was last modified.
|
|
41
|
+
attr_reader :mtime
|
|
42
|
+
|
|
43
|
+
# This page's list of page representations.
|
|
44
|
+
attr_reader :reps
|
|
45
|
+
|
|
46
|
+
# Creates a new page.
|
|
47
|
+
#
|
|
48
|
+
# +content+:: This page's unprocessed content.
|
|
49
|
+
#
|
|
50
|
+
# +attributes+:: A hash containing this page's attributes.
|
|
51
|
+
#
|
|
52
|
+
# +path+:: This page's path.
|
|
53
|
+
#
|
|
54
|
+
# +mtime+:: The time when this page was last modified.
|
|
55
|
+
def initialize(content, attributes, path, mtime=nil)
|
|
56
|
+
# Set primary attributes
|
|
57
|
+
@attributes = attributes.clean
|
|
58
|
+
@content = content
|
|
59
|
+
@path = path.cleaned_path
|
|
60
|
+
@mtime = mtime
|
|
61
|
+
|
|
62
|
+
# Start disconnected
|
|
63
|
+
@parent = nil
|
|
64
|
+
@children = []
|
|
65
|
+
@reps = []
|
|
66
|
+
end
|
|
26
67
|
|
|
27
|
-
|
|
28
|
-
|
|
68
|
+
# Builds the individual page representations (Nanoc::PageRep) for this
|
|
69
|
+
# page.
|
|
70
|
+
def build_reps
|
|
71
|
+
# Get list of rep names
|
|
72
|
+
rep_names_default = (@site.page_defaults.attributes[:reps] || {}).keys
|
|
73
|
+
rep_names_this = (@attributes[:reps] || {}).keys + [ :default ]
|
|
74
|
+
rep_names = rep_names_default | rep_names_this
|
|
75
|
+
|
|
76
|
+
# Get list of reps
|
|
77
|
+
reps = rep_names.inject({}) do |memo, rep_name|
|
|
78
|
+
rep = (@attributes[:reps] || {})[rep_name]
|
|
79
|
+
is_bad = (@attributes[:reps] || {}).has_key?(rep_name) && rep.nil?
|
|
80
|
+
is_bad ? memo : memo.merge(rep_name => rep || {})
|
|
81
|
+
end
|
|
29
82
|
|
|
30
|
-
|
|
31
|
-
@
|
|
32
|
-
|
|
33
|
-
|
|
83
|
+
# Build reps
|
|
84
|
+
@reps = []
|
|
85
|
+
reps.each_pair do |name, attrs|
|
|
86
|
+
@reps << PageRep.new(self, attrs, name)
|
|
87
|
+
end
|
|
34
88
|
end
|
|
35
89
|
|
|
36
|
-
#
|
|
37
|
-
|
|
90
|
+
# Returns a proxy (Nanoc::PageProxy) for this page.
|
|
38
91
|
def to_proxy
|
|
39
92
|
@proxy ||= PageProxy.new(self)
|
|
40
93
|
end
|
|
41
94
|
|
|
42
|
-
#
|
|
43
|
-
|
|
44
|
-
def modified? ; @modified ; end
|
|
45
|
-
|
|
95
|
+
# Returns the attribute with the given name.
|
|
46
96
|
def attribute_named(name)
|
|
47
|
-
return @attributes[name]
|
|
48
|
-
return @site.page_defaults[name] if @site.page_defaults.has_key?(name)
|
|
49
|
-
return
|
|
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?
|
|
63
|
-
attribute_named(:skip_output)
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def path
|
|
67
|
-
attribute_named(:custom_path) || attribute_named(:path)
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def path_on_filesystem
|
|
71
|
-
if attribute_named(:custom_path).nil?
|
|
72
|
-
@site.config[:output_dir] + attribute_named(:path) +
|
|
73
|
-
attribute_named(:filename) + '.' + attribute_named(:extension)
|
|
74
|
-
else
|
|
75
|
-
@site.config[:output_dir] + attribute_named(:custom_path)
|
|
76
|
-
end
|
|
97
|
+
return @attributes[name] if @attributes.has_key?(name)
|
|
98
|
+
return @site.page_defaults.attributes[name] if @site.page_defaults.attributes.has_key?(name)
|
|
99
|
+
return DEFAULTS[name]
|
|
77
100
|
end
|
|
78
101
|
|
|
79
|
-
#
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
if @compiler.stack.include?(self)
|
|
86
|
-
log(:high, "\n" + 'ERROR: Recursive call to page content. Page filter stack:', $stderr)
|
|
87
|
-
log(:high, " - #{self.attribute_named(:path)}", $stderr)
|
|
88
|
-
@compiler.stack.each_with_index do |page, i|
|
|
89
|
-
log(:high, " - #{page.attribute_named(:path)}", $stderr)
|
|
90
|
-
end
|
|
91
|
-
exit(1)
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
@compiler.stack.push(self)
|
|
95
|
-
|
|
96
|
-
# Filter pre
|
|
97
|
-
unless @filtered_pre
|
|
98
|
-
filter(:pre)
|
|
99
|
-
@filtered_pre = true
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
# Layout
|
|
103
|
-
if !@layouted and full
|
|
104
|
-
layout
|
|
105
|
-
@layouted = true
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
# Filter post
|
|
109
|
-
if !@filtered_post and full
|
|
110
|
-
filter(:post)
|
|
111
|
-
@filtered_post = true
|
|
102
|
+
# Saves the page in the database, creating it if it doesn't exist yet or
|
|
103
|
+
# updating it if it already exists. Tells the site's data source to save
|
|
104
|
+
# the page.
|
|
105
|
+
def save
|
|
106
|
+
@site.data_source.loading do
|
|
107
|
+
@site.data_source.save_page(self)
|
|
112
108
|
end
|
|
113
|
-
|
|
114
|
-
# Write
|
|
115
|
-
if !@written and full
|
|
116
|
-
@modified = FileManager.create_file(self.path_on_filesystem) { @content[:post] } unless skip_output?
|
|
117
|
-
@written = true
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
@compiler.stack.pop
|
|
121
109
|
end
|
|
122
110
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
filters.each do |filter_name|
|
|
129
|
-
# Create filter
|
|
130
|
-
filter_class = PluginManager.filter_named(filter_name)
|
|
131
|
-
error "Unknown filter: '#{filter_name}'" if filter_class.nil?
|
|
132
|
-
filter = filter_class.new(self.to_proxy, @site.pages.map { |p| p.to_proxy }, @site.config, @site)
|
|
133
|
-
|
|
134
|
-
# Run filter
|
|
135
|
-
@content[stage] = filter.run(@content[stage])
|
|
111
|
+
# Moves the page to a new path. Tells the site's data source to move the
|
|
112
|
+
# page.
|
|
113
|
+
def move_to(new_path)
|
|
114
|
+
@site.data_source.loading do
|
|
115
|
+
@site.data_source.move_page(self, new_path)
|
|
136
116
|
end
|
|
137
117
|
end
|
|
138
118
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
@
|
|
143
|
-
return
|
|
119
|
+
# Deletes the page. Tells the site's data source to delete the page.
|
|
120
|
+
def delete
|
|
121
|
+
@site.data_source.loading do
|
|
122
|
+
@site.data_source.delete_page(self)
|
|
144
123
|
end
|
|
145
|
-
|
|
146
|
-
# Find layout
|
|
147
|
-
layout = @site.layouts.find { |l| l[:name] == attribute_named(:layout) }
|
|
148
|
-
error 'Unknown layout: ' + attribute_named(:layout) if layout.nil?
|
|
149
|
-
|
|
150
|
-
# Find layout processor
|
|
151
|
-
layout_processor_class = PluginManager.layout_processor_for_extension(layout[:extension])
|
|
152
|
-
error "Unknown layout processor: '#{layout[:extension]}'" if layout_processor_class.nil?
|
|
153
|
-
layout_processor = layout_processor_class.new(self.to_proxy, @site.pages.map { |p| p.to_proxy }, @site.config, @site)
|
|
154
|
-
|
|
155
|
-
# Layout
|
|
156
|
-
@content[:post] = layout_processor.run(layout[:content])
|
|
157
124
|
end
|
|
158
125
|
|
|
159
126
|
end
|
|
127
|
+
|
|
160
128
|
end
|