nanoc3 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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