nanoc3 3.0.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 +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
|