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.
Files changed (116) hide show
  1. data/ChangeLog +3 -0
  2. data/LICENSE +19 -0
  3. data/NEWS.rdoc +262 -0
  4. data/README.rdoc +80 -0
  5. data/Rakefile +11 -0
  6. data/bin/nanoc3 +16 -0
  7. data/lib/nanoc3/base/code_snippet.rb +42 -0
  8. data/lib/nanoc3/base/compiler.rb +225 -0
  9. data/lib/nanoc3/base/compiler_dsl.rb +110 -0
  10. data/lib/nanoc3/base/core_ext/array.rb +21 -0
  11. data/lib/nanoc3/base/core_ext/hash.rb +23 -0
  12. data/lib/nanoc3/base/core_ext/string.rb +14 -0
  13. data/lib/nanoc3/base/core_ext.rb +5 -0
  14. data/lib/nanoc3/base/data_source.rb +197 -0
  15. data/lib/nanoc3/base/dependency_tracker.rb +291 -0
  16. data/lib/nanoc3/base/errors.rb +95 -0
  17. data/lib/nanoc3/base/filter.rb +60 -0
  18. data/lib/nanoc3/base/item.rb +87 -0
  19. data/lib/nanoc3/base/item_rep.rb +236 -0
  20. data/lib/nanoc3/base/layout.rb +53 -0
  21. data/lib/nanoc3/base/notification_center.rb +68 -0
  22. data/lib/nanoc3/base/plugin.rb +88 -0
  23. data/lib/nanoc3/base/preprocessor_context.rb +37 -0
  24. data/lib/nanoc3/base/rule.rb +37 -0
  25. data/lib/nanoc3/base/rule_context.rb +68 -0
  26. data/lib/nanoc3/base/site.rb +334 -0
  27. data/lib/nanoc3/base.rb +25 -0
  28. data/lib/nanoc3/cli/base.rb +151 -0
  29. data/lib/nanoc3/cli/commands/autocompile.rb +89 -0
  30. data/lib/nanoc3/cli/commands/compile.rb +279 -0
  31. data/lib/nanoc3/cli/commands/create_item.rb +79 -0
  32. data/lib/nanoc3/cli/commands/create_layout.rb +94 -0
  33. data/lib/nanoc3/cli/commands/create_site.rb +320 -0
  34. data/lib/nanoc3/cli/commands/help.rb +71 -0
  35. data/lib/nanoc3/cli/commands/info.rb +114 -0
  36. data/lib/nanoc3/cli/commands/update.rb +96 -0
  37. data/lib/nanoc3/cli/commands.rb +13 -0
  38. data/lib/nanoc3/cli/logger.rb +73 -0
  39. data/lib/nanoc3/cli.rb +16 -0
  40. data/lib/nanoc3/data_sources/delicious.rb +66 -0
  41. data/lib/nanoc3/data_sources/filesystem.rb +231 -0
  42. data/lib/nanoc3/data_sources/filesystem_combined.rb +202 -0
  43. data/lib/nanoc3/data_sources/filesystem_common.rb +22 -0
  44. data/lib/nanoc3/data_sources/filesystem_compact.rb +232 -0
  45. data/lib/nanoc3/data_sources/last_fm.rb +103 -0
  46. data/lib/nanoc3/data_sources/twitter.rb +53 -0
  47. data/lib/nanoc3/data_sources.rb +20 -0
  48. data/lib/nanoc3/extra/auto_compiler.rb +97 -0
  49. data/lib/nanoc3/extra/chick.rb +119 -0
  50. data/lib/nanoc3/extra/context.rb +24 -0
  51. data/lib/nanoc3/extra/core_ext/time.rb +19 -0
  52. data/lib/nanoc3/extra/core_ext.rb +3 -0
  53. data/lib/nanoc3/extra/deployers/rsync.rb +64 -0
  54. data/lib/nanoc3/extra/deployers.rb +12 -0
  55. data/lib/nanoc3/extra/file_proxy.rb +31 -0
  56. data/lib/nanoc3/extra/validators/links.rb +0 -0
  57. data/lib/nanoc3/extra/validators/w3c.rb +71 -0
  58. data/lib/nanoc3/extra/validators.rb +12 -0
  59. data/lib/nanoc3/extra/vcs.rb +65 -0
  60. data/lib/nanoc3/extra/vcses/bazaar.rb +21 -0
  61. data/lib/nanoc3/extra/vcses/dummy.rb +20 -0
  62. data/lib/nanoc3/extra/vcses/git.rb +21 -0
  63. data/lib/nanoc3/extra/vcses/mercurial.rb +21 -0
  64. data/lib/nanoc3/extra/vcses/subversion.rb +21 -0
  65. data/lib/nanoc3/extra/vcses.rb +17 -0
  66. data/lib/nanoc3/extra.rb +16 -0
  67. data/lib/nanoc3/filters/bluecloth.rb +13 -0
  68. data/lib/nanoc3/filters/coderay.rb +17 -0
  69. data/lib/nanoc3/filters/erb.rb +19 -0
  70. data/lib/nanoc3/filters/erubis.rb +17 -0
  71. data/lib/nanoc3/filters/haml.rb +20 -0
  72. data/lib/nanoc3/filters/less.rb +13 -0
  73. data/lib/nanoc3/filters/markaby.rb +14 -0
  74. data/lib/nanoc3/filters/maruku.rb +14 -0
  75. data/lib/nanoc3/filters/rainpress.rb +13 -0
  76. data/lib/nanoc3/filters/rdiscount.rb +13 -0
  77. data/lib/nanoc3/filters/rdoc.rb +23 -0
  78. data/lib/nanoc3/filters/redcloth.rb +14 -0
  79. data/lib/nanoc3/filters/relativize_paths.rb +32 -0
  80. data/lib/nanoc3/filters/rubypants.rb +14 -0
  81. data/lib/nanoc3/filters/sass.rb +17 -0
  82. data/lib/nanoc3/filters.rb +37 -0
  83. data/lib/nanoc3/helpers/blogging.rb +226 -0
  84. data/lib/nanoc3/helpers/breadcrumbs.rb +25 -0
  85. data/lib/nanoc3/helpers/capturing.rb +71 -0
  86. data/lib/nanoc3/helpers/filtering.rb +46 -0
  87. data/lib/nanoc3/helpers/html_escape.rb +22 -0
  88. data/lib/nanoc3/helpers/link_to.rb +120 -0
  89. data/lib/nanoc3/helpers/rendering.rb +76 -0
  90. data/lib/nanoc3/helpers/tagging.rb +58 -0
  91. data/lib/nanoc3/helpers/text.rb +40 -0
  92. data/lib/nanoc3/helpers/xml_sitemap.rb +69 -0
  93. data/lib/nanoc3/helpers.rb +16 -0
  94. data/lib/nanoc3/package.rb +106 -0
  95. data/lib/nanoc3/tasks/clean.rake +16 -0
  96. data/lib/nanoc3/tasks/clean.rb +33 -0
  97. data/lib/nanoc3/tasks/deploy/rsync.rake +11 -0
  98. data/lib/nanoc3/tasks/validate.rake +35 -0
  99. data/lib/nanoc3/tasks.rb +9 -0
  100. data/lib/nanoc3.rb +19 -0
  101. data/vendor/cri/ChangeLog +0 -0
  102. data/vendor/cri/LICENSE +19 -0
  103. data/vendor/cri/NEWS +0 -0
  104. data/vendor/cri/README +4 -0
  105. data/vendor/cri/Rakefile +25 -0
  106. data/vendor/cri/lib/cri/base.rb +153 -0
  107. data/vendor/cri/lib/cri/command.rb +105 -0
  108. data/vendor/cri/lib/cri/core_ext/string.rb +41 -0
  109. data/vendor/cri/lib/cri/core_ext.rb +8 -0
  110. data/vendor/cri/lib/cri/option_parser.rb +186 -0
  111. data/vendor/cri/lib/cri.rb +12 -0
  112. data/vendor/cri/test/test_base.rb +6 -0
  113. data/vendor/cri/test/test_command.rb +6 -0
  114. data/vendor/cri/test/test_core_ext.rb +21 -0
  115. data/vendor/cri/test/test_option_parser.rb +279 -0
  116. 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