nanoc3 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/ChangeLog +3 -0
- data/LICENSE +19 -0
- data/NEWS.rdoc +262 -0
- data/README.rdoc +80 -0
- data/Rakefile +11 -0
- data/bin/nanoc3 +16 -0
- data/lib/nanoc3/base/code_snippet.rb +42 -0
- data/lib/nanoc3/base/compiler.rb +225 -0
- data/lib/nanoc3/base/compiler_dsl.rb +110 -0
- data/lib/nanoc3/base/core_ext/array.rb +21 -0
- data/lib/nanoc3/base/core_ext/hash.rb +23 -0
- data/lib/nanoc3/base/core_ext/string.rb +14 -0
- data/lib/nanoc3/base/core_ext.rb +5 -0
- data/lib/nanoc3/base/data_source.rb +197 -0
- data/lib/nanoc3/base/dependency_tracker.rb +291 -0
- data/lib/nanoc3/base/errors.rb +95 -0
- data/lib/nanoc3/base/filter.rb +60 -0
- data/lib/nanoc3/base/item.rb +87 -0
- data/lib/nanoc3/base/item_rep.rb +236 -0
- data/lib/nanoc3/base/layout.rb +53 -0
- data/lib/nanoc3/base/notification_center.rb +68 -0
- data/lib/nanoc3/base/plugin.rb +88 -0
- data/lib/nanoc3/base/preprocessor_context.rb +37 -0
- data/lib/nanoc3/base/rule.rb +37 -0
- data/lib/nanoc3/base/rule_context.rb +68 -0
- data/lib/nanoc3/base/site.rb +334 -0
- data/lib/nanoc3/base.rb +25 -0
- data/lib/nanoc3/cli/base.rb +151 -0
- data/lib/nanoc3/cli/commands/autocompile.rb +89 -0
- data/lib/nanoc3/cli/commands/compile.rb +279 -0
- data/lib/nanoc3/cli/commands/create_item.rb +79 -0
- data/lib/nanoc3/cli/commands/create_layout.rb +94 -0
- data/lib/nanoc3/cli/commands/create_site.rb +320 -0
- data/lib/nanoc3/cli/commands/help.rb +71 -0
- data/lib/nanoc3/cli/commands/info.rb +114 -0
- data/lib/nanoc3/cli/commands/update.rb +96 -0
- data/lib/nanoc3/cli/commands.rb +13 -0
- data/lib/nanoc3/cli/logger.rb +73 -0
- data/lib/nanoc3/cli.rb +16 -0
- data/lib/nanoc3/data_sources/delicious.rb +66 -0
- data/lib/nanoc3/data_sources/filesystem.rb +231 -0
- data/lib/nanoc3/data_sources/filesystem_combined.rb +202 -0
- data/lib/nanoc3/data_sources/filesystem_common.rb +22 -0
- data/lib/nanoc3/data_sources/filesystem_compact.rb +232 -0
- data/lib/nanoc3/data_sources/last_fm.rb +103 -0
- data/lib/nanoc3/data_sources/twitter.rb +53 -0
- data/lib/nanoc3/data_sources.rb +20 -0
- data/lib/nanoc3/extra/auto_compiler.rb +97 -0
- data/lib/nanoc3/extra/chick.rb +119 -0
- data/lib/nanoc3/extra/context.rb +24 -0
- data/lib/nanoc3/extra/core_ext/time.rb +19 -0
- data/lib/nanoc3/extra/core_ext.rb +3 -0
- data/lib/nanoc3/extra/deployers/rsync.rb +64 -0
- data/lib/nanoc3/extra/deployers.rb +12 -0
- data/lib/nanoc3/extra/file_proxy.rb +31 -0
- data/lib/nanoc3/extra/validators/links.rb +0 -0
- data/lib/nanoc3/extra/validators/w3c.rb +71 -0
- data/lib/nanoc3/extra/validators.rb +12 -0
- data/lib/nanoc3/extra/vcs.rb +65 -0
- data/lib/nanoc3/extra/vcses/bazaar.rb +21 -0
- data/lib/nanoc3/extra/vcses/dummy.rb +20 -0
- data/lib/nanoc3/extra/vcses/git.rb +21 -0
- data/lib/nanoc3/extra/vcses/mercurial.rb +21 -0
- data/lib/nanoc3/extra/vcses/subversion.rb +21 -0
- data/lib/nanoc3/extra/vcses.rb +17 -0
- data/lib/nanoc3/extra.rb +16 -0
- data/lib/nanoc3/filters/bluecloth.rb +13 -0
- data/lib/nanoc3/filters/coderay.rb +17 -0
- data/lib/nanoc3/filters/erb.rb +19 -0
- data/lib/nanoc3/filters/erubis.rb +17 -0
- data/lib/nanoc3/filters/haml.rb +20 -0
- data/lib/nanoc3/filters/less.rb +13 -0
- data/lib/nanoc3/filters/markaby.rb +14 -0
- data/lib/nanoc3/filters/maruku.rb +14 -0
- data/lib/nanoc3/filters/rainpress.rb +13 -0
- data/lib/nanoc3/filters/rdiscount.rb +13 -0
- data/lib/nanoc3/filters/rdoc.rb +23 -0
- data/lib/nanoc3/filters/redcloth.rb +14 -0
- data/lib/nanoc3/filters/relativize_paths.rb +32 -0
- data/lib/nanoc3/filters/rubypants.rb +14 -0
- data/lib/nanoc3/filters/sass.rb +17 -0
- data/lib/nanoc3/filters.rb +37 -0
- data/lib/nanoc3/helpers/blogging.rb +226 -0
- data/lib/nanoc3/helpers/breadcrumbs.rb +25 -0
- data/lib/nanoc3/helpers/capturing.rb +71 -0
- data/lib/nanoc3/helpers/filtering.rb +46 -0
- data/lib/nanoc3/helpers/html_escape.rb +22 -0
- data/lib/nanoc3/helpers/link_to.rb +120 -0
- data/lib/nanoc3/helpers/rendering.rb +76 -0
- data/lib/nanoc3/helpers/tagging.rb +58 -0
- data/lib/nanoc3/helpers/text.rb +40 -0
- data/lib/nanoc3/helpers/xml_sitemap.rb +69 -0
- data/lib/nanoc3/helpers.rb +16 -0
- data/lib/nanoc3/package.rb +106 -0
- data/lib/nanoc3/tasks/clean.rake +16 -0
- data/lib/nanoc3/tasks/clean.rb +33 -0
- data/lib/nanoc3/tasks/deploy/rsync.rake +11 -0
- data/lib/nanoc3/tasks/validate.rake +35 -0
- data/lib/nanoc3/tasks.rb +9 -0
- data/lib/nanoc3.rb +19 -0
- data/vendor/cri/ChangeLog +0 -0
- data/vendor/cri/LICENSE +19 -0
- data/vendor/cri/NEWS +0 -0
- data/vendor/cri/README +4 -0
- data/vendor/cri/Rakefile +25 -0
- data/vendor/cri/lib/cri/base.rb +153 -0
- data/vendor/cri/lib/cri/command.rb +105 -0
- data/vendor/cri/lib/cri/core_ext/string.rb +41 -0
- data/vendor/cri/lib/cri/core_ext.rb +8 -0
- data/vendor/cri/lib/cri/option_parser.rb +186 -0
- data/vendor/cri/lib/cri.rb +12 -0
- data/vendor/cri/test/test_base.rb +6 -0
- data/vendor/cri/test/test_command.rb +6 -0
- data/vendor/cri/test/test_core_ext.rb +21 -0
- data/vendor/cri/test/test_option_parser.rb +279 -0
- metadata +225 -0
@@ -0,0 +1,95 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Nanoc3
|
4
|
+
|
5
|
+
module Errors
|
6
|
+
|
7
|
+
# Generic error. Superclass for all nanoc-specific errors.
|
8
|
+
class Generic < ::StandardError
|
9
|
+
end
|
10
|
+
|
11
|
+
# Error that is raised when a site is loaded that uses a data source with
|
12
|
+
# an unknown identifier.
|
13
|
+
class UnknownDataSource < Generic
|
14
|
+
def initialize(data_source_name)
|
15
|
+
super("The data source specified in the site's configuration file, #{data_source_name}, does not exist.")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Error that is raised during site compilation when an item uses a layout
|
20
|
+
# that is not present in the site.
|
21
|
+
class UnknownLayout < Generic
|
22
|
+
def initialize(layout_identifier)
|
23
|
+
super("The site does not have a layout with identifier '#{layout_identifier}'.")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Error that is raised during site compilation when an item uses a filter
|
28
|
+
# that is not known.
|
29
|
+
class UnknownFilter < Generic
|
30
|
+
def initialize(filter_name)
|
31
|
+
super("The requested filter, #{filter_name}, does not exist.")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Error that is raised during site compilation when a layout is compiled
|
36
|
+
# for which the filter cannot be determined. This is similar to the
|
37
|
+
# UnknownFilterError, but specific for filters for layouts.
|
38
|
+
class CannotDetermineFilter < Generic
|
39
|
+
def initialize(layout_identifier)
|
40
|
+
super("The filter to be used for the '#{layout_identifier}' could not be determined. Make sure the layout does have a filter.")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Error that is raised when data is requested when the data is not yet
|
45
|
+
# available (possibly due to a missing Nanoc::Site#load_data).
|
46
|
+
class DataNotYetAvailable < Generic
|
47
|
+
def initialize(type, plural)
|
48
|
+
super("#{type} #{plural ? 'are' : 'is'} not available yet. You may be missing a Nanoc::Site#load_data call.")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Error that is raised during site compilation when an item (directly or
|
53
|
+
# indirectly) includes its own item content, leading to endless recursion.
|
54
|
+
class RecursiveCompilation < Generic
|
55
|
+
def initialize(reps)
|
56
|
+
super("The site cannot be compiled because the following items mutually depend on each other: #{reps.inspect}.")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Error that is raised when no rules file can be found in the current
|
61
|
+
# working directory.
|
62
|
+
class NoRulesFileFound < Generic
|
63
|
+
def initialize
|
64
|
+
super("This site does not have a rules file, which is required for nanoc sites.")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Error that is raised when no compilation rule that can be applied to the
|
69
|
+
# current item can be found.
|
70
|
+
class NoMatchingCompilationRuleFound < Generic
|
71
|
+
def initialize(rep)
|
72
|
+
super("No compilation rules were found for the '#{rep.item.identifier}' item (rep '#{rep.name}').")
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Error that is raised when no routing rule that can be applied to the
|
77
|
+
# current item can be found.
|
78
|
+
class NoMatchingRoutingRuleFound < Generic
|
79
|
+
def initialize(rep)
|
80
|
+
super("No routing rules were found for the '#{rep.item.identifier}' item (rep '#{rep.name}').")
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Error that is raised when an rep cannot be compiled because it depends on other representations.
|
85
|
+
class UnmetDependency < Generic
|
86
|
+
attr_reader :rep
|
87
|
+
def initialize(rep)
|
88
|
+
@rep = rep
|
89
|
+
super("The '#{rep.item.identifier}' item (rep '#{rep.name}') cannot currently be compiled yet due to an unmet dependency.")
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Nanoc3
|
4
|
+
|
5
|
+
# Nanoc3::Filter is responsible for filtering items. It is
|
6
|
+
# the (abstract) superclass for all textual filters. Subclasses should
|
7
|
+
# override the +run+ method.
|
8
|
+
class Filter < Plugin
|
9
|
+
|
10
|
+
# A hash containing variables that will be made available during
|
11
|
+
# filtering.
|
12
|
+
attr_reader :assigns
|
13
|
+
|
14
|
+
# Creates a new filter with the given assigns.
|
15
|
+
#
|
16
|
+
# +a_assigns+:: A hash containing variables that should be made available
|
17
|
+
# during filtering.
|
18
|
+
def initialize(a_assigns={})
|
19
|
+
@assigns = a_assigns
|
20
|
+
end
|
21
|
+
|
22
|
+
# Sets the identifiers for this filter.
|
23
|
+
def self.identifiers(*identifiers)
|
24
|
+
Nanoc3::Filter.register(self, *identifiers)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Sets the identifier for this filter.
|
28
|
+
def self.identifier(identifier)
|
29
|
+
Nanoc3::Filter.register(self, identifier)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Registers the given class as a filter with the given identifier.
|
33
|
+
def self.register(class_or_name, *identifiers)
|
34
|
+
Nanoc3::Plugin.register(Nanoc3::Filter, class_or_name, *identifiers)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Runs the filter. This method returns the filtered content.
|
38
|
+
#
|
39
|
+
# +content+:: The unprocessed content that should be filtered.
|
40
|
+
#
|
41
|
+
# Subclasses must implement this method.
|
42
|
+
def run(content, params={})
|
43
|
+
raise NotImplementedError.new("Nanoc3::Filter subclasses must implement #run")
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns the filename associated with the item that is being filtered.
|
47
|
+
# The returned filename is in the format "item <identifier> (rep <name>)".
|
48
|
+
def filename
|
49
|
+
if assigns[:layout]
|
50
|
+
"layout #{assigns[:layout].identifier}"
|
51
|
+
elsif assigns[:item]
|
52
|
+
"item #{assigns[:item].identifier} (rep #{assigns[:item_rep].name})"
|
53
|
+
else
|
54
|
+
'?'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Nanoc3
|
4
|
+
|
5
|
+
# Nanoc3::Item is represents all compileable items in a site. It has content
|
6
|
+
# and attributes, as well as an identifier. It can also store the
|
7
|
+
# modification time to speed up compilation.
|
8
|
+
class Item
|
9
|
+
|
10
|
+
# The Nanoc3::Site this item belongs to.
|
11
|
+
attr_accessor :site
|
12
|
+
|
13
|
+
# A hash containing this item's attributes.
|
14
|
+
attr_accessor :attributes
|
15
|
+
|
16
|
+
# This item's identifier.
|
17
|
+
attr_accessor :identifier
|
18
|
+
|
19
|
+
# The time when this item was last modified.
|
20
|
+
attr_reader :mtime
|
21
|
+
|
22
|
+
# This item's list of item representations.
|
23
|
+
attr_reader :reps
|
24
|
+
|
25
|
+
# This item's raw, uncompiled content.
|
26
|
+
attr_reader :raw_content
|
27
|
+
|
28
|
+
# The parent item of this item. This can be nil even for non-root items.
|
29
|
+
attr_accessor :parent
|
30
|
+
|
31
|
+
# The child items of this item.
|
32
|
+
attr_accessor :children
|
33
|
+
|
34
|
+
# A boolean indicating whether or not this item is outdated because of its dependencies are outdated.
|
35
|
+
attr_accessor :dependencies_outdated
|
36
|
+
|
37
|
+
# Creates a new item.
|
38
|
+
#
|
39
|
+
# +raw_content+:: The uncompiled item content.
|
40
|
+
#
|
41
|
+
# +attributes+:: A hash containing this item's attributes.
|
42
|
+
#
|
43
|
+
# +identifier+:: This item's identifier.
|
44
|
+
#
|
45
|
+
# +mtime+:: The time when this item was last modified.
|
46
|
+
def initialize(raw_content, attributes, identifier, mtime=nil)
|
47
|
+
@raw_content = raw_content
|
48
|
+
@attributes = attributes.symbolize_keys
|
49
|
+
@identifier = identifier.cleaned_identifier
|
50
|
+
@mtime = mtime
|
51
|
+
|
52
|
+
@parent = nil
|
53
|
+
@children = []
|
54
|
+
|
55
|
+
@reps = []
|
56
|
+
end
|
57
|
+
|
58
|
+
# Requests the attribute with the given key.
|
59
|
+
def [](key)
|
60
|
+
Nanoc3::NotificationCenter.post(:visit_started, self)
|
61
|
+
Nanoc3::NotificationCenter.post(:visit_ended, self)
|
62
|
+
|
63
|
+
@attributes[key]
|
64
|
+
end
|
65
|
+
|
66
|
+
# Sets the attribute with the given key to the given value.
|
67
|
+
def []=(key, value)
|
68
|
+
@attributes[key] = value
|
69
|
+
end
|
70
|
+
|
71
|
+
# True if any reps are outdated; false otherwise.
|
72
|
+
def outdated?
|
73
|
+
@reps.any? { |r| r.outdated? }
|
74
|
+
end
|
75
|
+
|
76
|
+
# Alias for #dependencies_outdated.
|
77
|
+
def dependencies_outdated?
|
78
|
+
self.dependencies_outdated
|
79
|
+
end
|
80
|
+
|
81
|
+
def inspect
|
82
|
+
"<#{self.class}:0x#{self.object_id.to_s(16)} identifier=#{self.identifier}>"
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
@@ -0,0 +1,236 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Nanoc3
|
4
|
+
|
5
|
+
# A Nanoc3::ItemRep is a single representation (rep) of an item
|
6
|
+
# (Nanoc3::Item). An item can have multiple representations. A representation
|
7
|
+
# has its own output file. A single item can therefore have multiple output
|
8
|
+
# files, each run through a different set of filters with a different
|
9
|
+
# layout.
|
10
|
+
#
|
11
|
+
# An item representation is observable. The following events will be
|
12
|
+
# notified:
|
13
|
+
#
|
14
|
+
# * :compilation_started
|
15
|
+
# * :compilation_ended
|
16
|
+
# * :filtering_started
|
17
|
+
# * :filtering_ended
|
18
|
+
#
|
19
|
+
# The compilation-related events have one parameters (the item
|
20
|
+
# representation); the filtering-related events have two (the item
|
21
|
+
# representation, and a symbol containing the filter class name).
|
22
|
+
class ItemRep
|
23
|
+
|
24
|
+
# The item (Nanoc3::Item) to which this representation belongs.
|
25
|
+
attr_reader :item
|
26
|
+
|
27
|
+
# This item representation's unique name.
|
28
|
+
attr_reader :name
|
29
|
+
|
30
|
+
# Indicates whether this rep is forced to be dirty by the user.
|
31
|
+
attr_accessor :force_outdated
|
32
|
+
|
33
|
+
# Indicates whether this rep's output file has changed the last time it
|
34
|
+
# was compiled.
|
35
|
+
attr_accessor :modified
|
36
|
+
alias_method :modified?, :modified
|
37
|
+
|
38
|
+
# Indicates whether this rep's output file was created the last time it
|
39
|
+
# was compiled.
|
40
|
+
attr_accessor :created
|
41
|
+
alias_method :created?, :created
|
42
|
+
|
43
|
+
# Indicates whether this rep has already been compiled.
|
44
|
+
attr_accessor :compiled
|
45
|
+
alias_method :compiled?, :compiled
|
46
|
+
|
47
|
+
# Indicates whether this rep's compiled content has been written during
|
48
|
+
# the current or last compilation session.
|
49
|
+
attr_reader :written
|
50
|
+
alias_method :written?, :written
|
51
|
+
|
52
|
+
# The item rep's path, as used when being linked to. It starts with a
|
53
|
+
# slash and it is relative to the output directory. It does not include
|
54
|
+
# the path to the output directory. It will not include the filename if
|
55
|
+
# the filename is an index filename.
|
56
|
+
attr_accessor :path
|
57
|
+
|
58
|
+
# The item rep's raw path. It is relative to the current working directory
|
59
|
+
# and includes the path to the output directory. It also includes the
|
60
|
+
# filename, even if it is an index filename.
|
61
|
+
attr_accessor :raw_path
|
62
|
+
|
63
|
+
# Creates a new item representation for the given item.
|
64
|
+
#
|
65
|
+
# +item+:: The item (Nanoc3::Item) to which the new representation will
|
66
|
+
# belong.
|
67
|
+
#
|
68
|
+
# +name+:: The unique name for the new item representation.
|
69
|
+
def initialize(item, name)
|
70
|
+
# Set primary attributes
|
71
|
+
@item = item
|
72
|
+
@name = name
|
73
|
+
|
74
|
+
# Initialize content
|
75
|
+
@content = {
|
76
|
+
:raw => @item.raw_content,
|
77
|
+
:last => @item.raw_content,
|
78
|
+
:pre => @item.raw_content
|
79
|
+
}
|
80
|
+
|
81
|
+
# Reset flags
|
82
|
+
@compiled = false
|
83
|
+
@modified = false
|
84
|
+
@created = false
|
85
|
+
@written = false
|
86
|
+
@force_outdated = false
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns true if this item rep's output file is outdated and must be
|
90
|
+
# regenerated, false otherwise.
|
91
|
+
def outdated?
|
92
|
+
# Outdated if we don't know
|
93
|
+
return true if @item.mtime.nil?
|
94
|
+
|
95
|
+
# Outdated if the dependency tracker says so
|
96
|
+
return true if @force_outdated
|
97
|
+
|
98
|
+
# Outdated if compiled file doesn't exist
|
99
|
+
return true if self.raw_path.nil?
|
100
|
+
return true if !File.file?(self.raw_path)
|
101
|
+
|
102
|
+
# Get compiled mtime
|
103
|
+
compiled_mtime = File.stat(self.raw_path).mtime
|
104
|
+
|
105
|
+
# Outdated if file too old
|
106
|
+
return true if @item.mtime > compiled_mtime
|
107
|
+
|
108
|
+
# Outdated if layouts outdated
|
109
|
+
return true if @item.site.layouts.any? do |l|
|
110
|
+
l.mtime.nil? || l.mtime > compiled_mtime
|
111
|
+
end
|
112
|
+
|
113
|
+
# Outdated if code outdated
|
114
|
+
return true if @item.site.code_snippets.any? do |cs|
|
115
|
+
cs.mtime.nil? || cs.mtime > compiled_mtime
|
116
|
+
end
|
117
|
+
|
118
|
+
# Outdated if config outdated
|
119
|
+
return true if @item.site.config_mtime.nil?
|
120
|
+
return true if @item.site.config_mtime > compiled_mtime
|
121
|
+
|
122
|
+
# Outdated if rules outdated
|
123
|
+
return true if @item.site.rules_mtime.nil?
|
124
|
+
return true if @item.site.rules_mtime > compiled_mtime
|
125
|
+
|
126
|
+
return false
|
127
|
+
end
|
128
|
+
|
129
|
+
# Returns the assignments that should be available when compiling the content.
|
130
|
+
def assigns
|
131
|
+
{
|
132
|
+
:content => @content[:last],
|
133
|
+
:item => self.item,
|
134
|
+
:item_rep => self,
|
135
|
+
:items => self.item.site.items,
|
136
|
+
:layouts => self.item.site.layouts,
|
137
|
+
:config => self.item.site.config,
|
138
|
+
:site => self.item.site
|
139
|
+
}
|
140
|
+
end
|
141
|
+
|
142
|
+
# Returns the item representation content at the given snapshot.
|
143
|
+
#
|
144
|
+
# +snapshot+:: The snapshot from which the content should be fetched. To
|
145
|
+
# get the raw, uncompiled content, use +:raw+.
|
146
|
+
def content_at_snapshot(snapshot=:pre)
|
147
|
+
Nanoc3::NotificationCenter.post(:visit_started, self.item)
|
148
|
+
Nanoc3::NotificationCenter.post(:visit_ended, self.item)
|
149
|
+
|
150
|
+
puts "*** Attempting to fetch content for #{self.inspect}" if $DEBUG
|
151
|
+
|
152
|
+
raise Nanoc3::Errors::UnmetDependency.new(self) unless compiled?
|
153
|
+
|
154
|
+
@content[snapshot]
|
155
|
+
end
|
156
|
+
|
157
|
+
# Runs the item content through the given filter with the given arguments.
|
158
|
+
def filter(filter_name, filter_args={})
|
159
|
+
# Create filter
|
160
|
+
klass = Nanoc3::Filter.named(filter_name)
|
161
|
+
raise Nanoc3::Errors::UnknownFilter.new(filter_name) if klass.nil?
|
162
|
+
filter = klass.new(assigns)
|
163
|
+
|
164
|
+
# Run filter
|
165
|
+
Nanoc3::NotificationCenter.post(:filtering_started, self, filter_name)
|
166
|
+
@content[:last] = filter.run(@content[:last], filter_args)
|
167
|
+
Nanoc3::NotificationCenter.post(:filtering_ended, self, filter_name)
|
168
|
+
|
169
|
+
# Create snapshot
|
170
|
+
snapshot(@content[:post] ? :post : :pre)
|
171
|
+
end
|
172
|
+
|
173
|
+
# Lays out the item using the given layout.
|
174
|
+
def layout(layout_identifier)
|
175
|
+
# Get layout
|
176
|
+
layout ||= @item.site.layouts.find { |l| l.identifier == layout_identifier.cleaned_identifier }
|
177
|
+
raise Nanoc3::Errors::UnknownLayout.new(layout_identifier) if layout.nil?
|
178
|
+
|
179
|
+
# Get filter
|
180
|
+
filter_name, filter_args = @item.site.compiler.filter_for_layout(layout)
|
181
|
+
raise Nanoc3::Errors::CannotDetermineFilter.new(layout_identifier) if filter_name.nil?
|
182
|
+
|
183
|
+
# Get filter class
|
184
|
+
filter_class = Nanoc3::Filter.named(filter_name)
|
185
|
+
raise Nanoc3::Errors::UnknownFilter.new(filter_name) if filter_class.nil?
|
186
|
+
|
187
|
+
# Create filter
|
188
|
+
filter = filter_class.new(assigns.merge({ :layout => layout }))
|
189
|
+
|
190
|
+
# Create "pre" snapshot
|
191
|
+
snapshot(:pre)
|
192
|
+
|
193
|
+
# Layout
|
194
|
+
@item.site.compiler.stack.push(layout)
|
195
|
+
Nanoc3::NotificationCenter.post(:filtering_started, self, filter_name)
|
196
|
+
@content[:last] = filter.run(layout.raw_content, filter_args)
|
197
|
+
Nanoc3::NotificationCenter.post(:filtering_ended, self, filter_name)
|
198
|
+
@item.site.compiler.stack.pop
|
199
|
+
|
200
|
+
# Create "post" snapshot
|
201
|
+
snapshot(:post)
|
202
|
+
end
|
203
|
+
|
204
|
+
# Creates a snapshot of the current compiled item content.
|
205
|
+
def snapshot(snapshot_name)
|
206
|
+
@content[snapshot_name] = @content[:last]
|
207
|
+
end
|
208
|
+
|
209
|
+
# Writes the item rep's compiled content to the rep's output file.
|
210
|
+
def write
|
211
|
+
# Create parent directory
|
212
|
+
FileUtils.mkdir_p(File.dirname(self.raw_path))
|
213
|
+
|
214
|
+
# Check if file will be created
|
215
|
+
@created = !File.file?(self.raw_path)
|
216
|
+
|
217
|
+
# Remember old content
|
218
|
+
if File.file?(self.raw_path)
|
219
|
+
old_content = File.read(self.raw_path)
|
220
|
+
end
|
221
|
+
|
222
|
+
# Write
|
223
|
+
File.open(self.raw_path, 'w') { |io| io.write(@content[:last]) }
|
224
|
+
@written = true
|
225
|
+
|
226
|
+
# Check if file was modified
|
227
|
+
@modified = File.read(self.raw_path) != old_content
|
228
|
+
end
|
229
|
+
|
230
|
+
def inspect
|
231
|
+
"<#{self.class}:0x#{self.object_id.to_s(16)} name=#{self.name} item.identifier=#{self.item.identifier}>"
|
232
|
+
end
|
233
|
+
|
234
|
+
end
|
235
|
+
|
236
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Nanoc3
|
4
|
+
|
5
|
+
# A Nanoc3::Layout represents a layout in a nanoc site. It has content,
|
6
|
+
# attributes (for determining which filter to use for laying out an item),
|
7
|
+
# an identifier (because layouts are organised hierarchically), and a
|
8
|
+
# modification time (to speed up compilation).
|
9
|
+
class Layout
|
10
|
+
|
11
|
+
# The Nanoc3::Site this layout belongs to.
|
12
|
+
attr_accessor :site
|
13
|
+
|
14
|
+
# The raw content of this layout.
|
15
|
+
attr_reader :raw_content
|
16
|
+
|
17
|
+
# A hash containing this layout's attributes.
|
18
|
+
attr_reader :attributes
|
19
|
+
|
20
|
+
# This layout's identifier, starting and ending with a slash.
|
21
|
+
attr_accessor :identifier
|
22
|
+
|
23
|
+
# The time when this layout was last modified.
|
24
|
+
attr_reader :mtime
|
25
|
+
|
26
|
+
# Creates a new layout.
|
27
|
+
#
|
28
|
+
# +content+:: The raw content of this layout.
|
29
|
+
#
|
30
|
+
# +attributes+:: A hash containing this layout's attributes.
|
31
|
+
#
|
32
|
+
# +identifier+:: This layout's identifier.
|
33
|
+
#
|
34
|
+
# +mtime+:: The time when this layout was last modified.
|
35
|
+
def initialize(raw_content, attributes, identifier, mtime=nil)
|
36
|
+
@raw_content = raw_content
|
37
|
+
@attributes = attributes.symbolize_keys
|
38
|
+
@identifier = identifier.cleaned_identifier
|
39
|
+
@mtime = mtime
|
40
|
+
end
|
41
|
+
|
42
|
+
# Requests the attribute with the given key.
|
43
|
+
def [](key)
|
44
|
+
@attributes[key]
|
45
|
+
end
|
46
|
+
|
47
|
+
def inspect
|
48
|
+
"<#{self.class}:0x#{self.object_id.to_s(16)} identifier=#{self.identifier}>"
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Nanoc3
|
4
|
+
|
5
|
+
# Nanoc3::NotificationCenter provides a way to send notifications between
|
6
|
+
# objects. It allows blocks associated with a certain notification name to
|
7
|
+
# be registered; these blocks will be called when the notification with the
|
8
|
+
# given name is posted.
|
9
|
+
#
|
10
|
+
# It is a slightly different implementation of the Observer pattern; the
|
11
|
+
# table of subscribers is not stored in the observable object itself, but in
|
12
|
+
# the notification center.
|
13
|
+
class NotificationCenter
|
14
|
+
|
15
|
+
class << self
|
16
|
+
|
17
|
+
# Adds the given block to the list of blocks that should be called when
|
18
|
+
# the notification with the given name is received.
|
19
|
+
#
|
20
|
+
# +name+:: The name of the notification that will be posted.
|
21
|
+
#
|
22
|
+
# +id+:: An identifier for the block. This is only used to be able to
|
23
|
+
# remove the block (using the remove method) later. Defaults to
|
24
|
+
# nil.
|
25
|
+
def on(name, id=nil, &block)
|
26
|
+
initialize_if_necessary(name)
|
27
|
+
|
28
|
+
# Add observer
|
29
|
+
@notifications[name] << { :id => id, :block => block }
|
30
|
+
end
|
31
|
+
|
32
|
+
# Posts a notification with the given name. All arguments wil be passed
|
33
|
+
# to the blocks handling the notification.
|
34
|
+
def post(name, *args)
|
35
|
+
initialize_if_necessary(name)
|
36
|
+
|
37
|
+
# Notify all observers
|
38
|
+
@notifications[name].each do |observer|
|
39
|
+
observer[:block].call(*args)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Removes the block with the given identifier from the list of blocks
|
44
|
+
# that should be called when the notification with the given name is
|
45
|
+
# posted.
|
46
|
+
#
|
47
|
+
# +name+:: The name of the notification that will be posted.
|
48
|
+
#
|
49
|
+
# +id+:: The identifier of the block that should be removed.
|
50
|
+
def remove(name, id)
|
51
|
+
initialize_if_necessary(name)
|
52
|
+
|
53
|
+
# Remove relevant observers
|
54
|
+
@notifications[name].reject! { |i| i[:id] == id }
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def initialize_if_necessary(name)
|
60
|
+
@notifications ||= {} # name => observers dictionary
|
61
|
+
@notifications[name] ||= [] # list of observers
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Nanoc3
|
4
|
+
|
5
|
+
# Nanoc3::Plugin is the superclass for all plugins, such as filters
|
6
|
+
# (Nanoc3::Filter), data sources (Nanoc3::DataSource) and VCSes
|
7
|
+
# (Nanoc3::Extra::VCS). Each plugin has one or more unique identifiers,
|
8
|
+
# and several methods in this class provides functionality for finding
|
9
|
+
# plugins with given identifiers.
|
10
|
+
class Plugin
|
11
|
+
|
12
|
+
MAP = {}
|
13
|
+
|
14
|
+
class << self
|
15
|
+
|
16
|
+
# Registers the given class as a plugin.
|
17
|
+
#
|
18
|
+
# +superclass+:: The superclass of the plugin. For example:
|
19
|
+
# Nanoc::Filter, Nanoc::VCS.
|
20
|
+
#
|
21
|
+
# +class_or_name+:: The class to register. This can be a string, in
|
22
|
+
# which case it will be automatically converted to a
|
23
|
+
# proper class at lookup. For example:
|
24
|
+
# 'Nanoc::Filters::ERB', Nanoc::Filters::Haml.
|
25
|
+
#
|
26
|
+
# +identifiers+:: One or more symbols identifying the class. For
|
27
|
+
# example: :haml, :erb.
|
28
|
+
def register(superclass, class_or_name, *identifiers)
|
29
|
+
MAP[superclass] ||= {}
|
30
|
+
|
31
|
+
identifiers.each do |identifier|
|
32
|
+
MAP[superclass][identifier.to_sym] = class_or_name
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns the the plugin with the given name. Only subclasses of this
|
37
|
+
# class will be searched. For example, calling this method on
|
38
|
+
# Nanoc3::Filter will cause only Nanoc3::Filter subclasses to be searched.
|
39
|
+
def named(name)
|
40
|
+
# Initialize
|
41
|
+
MAP[self] ||= {}
|
42
|
+
|
43
|
+
# Lookup
|
44
|
+
class_or_name = MAP[self][name.to_sym]
|
45
|
+
|
46
|
+
# Get class
|
47
|
+
if class_or_name.is_a?(String)
|
48
|
+
class_or_name.scan(/\w+/).inject(self) { |memo, part| memo.const_get(part) }
|
49
|
+
else
|
50
|
+
class_or_name
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns a list of all plugins in the following format:
|
55
|
+
#
|
56
|
+
# { :class => ..., :superclass => ..., :identifiers => ... }
|
57
|
+
def all
|
58
|
+
plugins = []
|
59
|
+
MAP.each_pair do |superclass, submap|
|
60
|
+
submap.each_pair do |identifier, klass|
|
61
|
+
# Find existing plugin
|
62
|
+
existing_plugin = plugins.find do |p|
|
63
|
+
p[:class] == klass && p[:superclass] == superclass
|
64
|
+
end
|
65
|
+
|
66
|
+
if existing_plugin
|
67
|
+
# Add identifier to existing plugin
|
68
|
+
existing_plugin[:identifiers] << identifier
|
69
|
+
existing_plugin[:identifiers] = existing_plugin[:identifiers].sort_by { |s| s.to_s }
|
70
|
+
else
|
71
|
+
# Create new plugin
|
72
|
+
plugins << {
|
73
|
+
:class => klass,
|
74
|
+
:superclass => superclass,
|
75
|
+
:identifiers => [ identifier ]
|
76
|
+
}
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
plugins
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|