nanoc 2.0.4 → 2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. data/ChangeLog +31 -1
  2. data/LICENSE +1 -1
  3. data/README +63 -3
  4. data/Rakefile +59 -12
  5. data/bin/nanoc +7 -199
  6. data/lib/nanoc.rb +83 -12
  7. data/lib/nanoc/base/asset.rb +113 -0
  8. data/lib/nanoc/base/asset_defaults.rb +21 -0
  9. data/lib/nanoc/base/asset_rep.rb +277 -0
  10. data/lib/nanoc/base/binary_filter.rb +44 -0
  11. data/lib/nanoc/base/code.rb +41 -0
  12. data/lib/nanoc/base/compiler.rb +46 -34
  13. data/lib/nanoc/base/core_ext/hash.rb +51 -7
  14. data/lib/nanoc/base/core_ext/string.rb +8 -0
  15. data/lib/nanoc/base/data_source.rb +253 -20
  16. data/lib/nanoc/base/defaults.rb +30 -0
  17. data/lib/nanoc/base/enhancements.rb +9 -84
  18. data/lib/nanoc/base/filter.rb +109 -6
  19. data/lib/nanoc/base/layout.rb +91 -0
  20. data/lib/nanoc/base/notification_center.rb +66 -0
  21. data/lib/nanoc/base/page.rb +94 -126
  22. data/lib/nanoc/base/page_defaults.rb +20 -0
  23. data/lib/nanoc/base/page_rep.rb +318 -0
  24. data/lib/nanoc/base/plugin.rb +57 -9
  25. data/lib/nanoc/base/proxies/asset_proxy.rb +29 -0
  26. data/lib/nanoc/base/proxies/asset_rep_proxy.rb +26 -0
  27. data/lib/nanoc/base/proxies/layout_proxy.rb +25 -0
  28. data/lib/nanoc/base/proxies/page_proxy.rb +35 -0
  29. data/lib/nanoc/base/proxies/page_rep_proxy.rb +28 -0
  30. data/lib/nanoc/base/proxy.rb +37 -0
  31. data/lib/nanoc/base/router.rb +72 -0
  32. data/lib/nanoc/base/site.rb +219 -88
  33. data/lib/nanoc/base/template.rb +64 -0
  34. data/lib/nanoc/binary_filters/image_science_thumbnail.rb +28 -0
  35. data/lib/nanoc/cli.rb +1 -0
  36. data/lib/nanoc/cli/base.rb +219 -0
  37. data/lib/nanoc/cli/cli.rb +16 -0
  38. data/lib/nanoc/cli/command.rb +105 -0
  39. data/lib/nanoc/cli/commands/autocompile.rb +80 -0
  40. data/lib/nanoc/cli/commands/compile.rb +273 -0
  41. data/lib/nanoc/cli/commands/create_layout.rb +85 -0
  42. data/lib/nanoc/cli/commands/create_page.rb +85 -0
  43. data/lib/nanoc/cli/commands/create_site.rb +327 -0
  44. data/lib/nanoc/cli/commands/create_template.rb +76 -0
  45. data/lib/nanoc/cli/commands/help.rb +69 -0
  46. data/lib/nanoc/cli/commands/info.rb +114 -0
  47. data/lib/nanoc/cli/commands/switch.rb +141 -0
  48. data/lib/nanoc/cli/commands/update.rb +91 -0
  49. data/lib/nanoc/cli/ext.rb +37 -0
  50. data/lib/nanoc/cli/logger.rb +66 -0
  51. data/lib/nanoc/cli/option_parser.rb +168 -0
  52. data/lib/nanoc/data_sources/filesystem.rb +645 -224
  53. data/lib/nanoc/data_sources/filesystem_combined.rb +495 -0
  54. data/lib/nanoc/extra/auto_compiler.rb +265 -0
  55. data/lib/nanoc/extra/context.rb +22 -0
  56. data/lib/nanoc/extra/core_ext/hash.rb +54 -0
  57. data/lib/nanoc/extra/core_ext/time.rb +13 -0
  58. data/lib/nanoc/extra/file_proxy.rb +29 -0
  59. data/lib/nanoc/extra/vcs.rb +48 -0
  60. data/lib/nanoc/extra/vcses/bazaar.rb +21 -0
  61. data/lib/nanoc/extra/vcses/dummy.rb +20 -0
  62. data/lib/nanoc/extra/vcses/git.rb +21 -0
  63. data/lib/nanoc/extra/vcses/mercurial.rb +21 -0
  64. data/lib/nanoc/extra/vcses/subversion.rb +21 -0
  65. data/lib/nanoc/filters/bluecloth.rb +13 -0
  66. data/lib/nanoc/filters/erb.rb +6 -22
  67. data/lib/nanoc/filters/erubis.rb +14 -0
  68. data/lib/nanoc/filters/haml.rb +7 -23
  69. data/lib/nanoc/filters/markaby.rb +5 -5
  70. data/lib/nanoc/filters/maruku.rb +14 -0
  71. data/lib/nanoc/filters/old.rb +19 -0
  72. data/lib/nanoc/filters/rdiscount.rb +13 -0
  73. data/lib/nanoc/filters/rdoc.rb +5 -4
  74. data/lib/nanoc/filters/redcloth.rb +14 -0
  75. data/lib/nanoc/filters/rubypants.rb +14 -0
  76. data/lib/nanoc/filters/sass.rb +13 -0
  77. data/lib/nanoc/helpers/blogging.rb +170 -0
  78. data/lib/nanoc/helpers/capturing.rb +59 -0
  79. data/lib/nanoc/helpers/html_escape.rb +23 -0
  80. data/lib/nanoc/helpers/link_to.rb +69 -0
  81. data/lib/nanoc/helpers/render.rb +47 -0
  82. data/lib/nanoc/helpers/tagging.rb +52 -0
  83. data/lib/nanoc/helpers/xml_sitemap.rb +58 -0
  84. data/lib/nanoc/routers/default.rb +54 -0
  85. data/lib/nanoc/routers/no_dirs.rb +66 -0
  86. data/lib/nanoc/routers/versioned.rb +79 -0
  87. metadata +112 -22
  88. data/lib/nanoc/base/auto_compiler.rb +0 -132
  89. data/lib/nanoc/base/layout_processor.rb +0 -33
  90. data/lib/nanoc/base/page_proxy.rb +0 -31
  91. data/lib/nanoc/base/plugin_manager.rb +0 -33
  92. data/lib/nanoc/data_sources/database.rb +0 -259
  93. data/lib/nanoc/data_sources/trivial.rb +0 -145
  94. data/lib/nanoc/filters/markdown.rb +0 -13
  95. data/lib/nanoc/filters/smartypants.rb +0 -13
  96. data/lib/nanoc/filters/textile.rb +0 -13
  97. data/lib/nanoc/layout_processors/erb.rb +0 -35
  98. data/lib/nanoc/layout_processors/haml.rb +0 -38
  99. data/lib/nanoc/layout_processors/markaby.rb +0 -16
@@ -0,0 +1,20 @@
1
+ module Nanoc
2
+
3
+ # Nanoc::PageDefaults represent the default attributes for all pages in the
4
+ # site. If a specific page attribute is requested, but not found, then the
5
+ # page defaults will be queried for this attribute. (If the attribute
6
+ # doesn't even exist in the page defaults, hardcoded defaults will be used.)
7
+ class PageDefaults < Defaults
8
+
9
+ # Saves the page defaults in the database, creating it if it doesn't exist
10
+ # yet or updating it if it already exists. Tells the site's data source to
11
+ # save the page defaults.
12
+ def save
13
+ @site.data_source.loading do
14
+ @site.data_source.save_page_defaults(self)
15
+ end
16
+ end
17
+
18
+ end
19
+
20
+ end
@@ -0,0 +1,318 @@
1
+ module Nanoc
2
+
3
+ # A Nanoc::PageRep is a single representation (rep) of a page (Nanoc::Page).
4
+ # A page can have multiple representations. A representation has its own
5
+ # attributes and its own output file. A single page can therefore have
6
+ # multiple output files, each run through a different set of filters with a
7
+ # different layout.
8
+ #
9
+ # A page representation is observable. The following events will be
10
+ # notified:
11
+ #
12
+ # * :compilation_started
13
+ # * :compilation_ended
14
+ # * :filtering_started
15
+ # * :filtering_ended
16
+ #
17
+ # The compilation-related events have one parameters (the page
18
+ # representation); the filtering-related events have two (the page
19
+ # representation, and a symbol containing the filter class name).
20
+ class PageRep
21
+
22
+ # The page (Nanoc::Page) to which this representation belongs.
23
+ attr_reader :page
24
+
25
+ # A hash containing this page representation's attributes.
26
+ attr_accessor :attributes
27
+
28
+ # This page representation's unique name.
29
+ attr_reader :name
30
+
31
+ # Creates a new page representation for the given page and with the given
32
+ # attributes.
33
+ #
34
+ # +page+:: The page (Nanoc::Page) to which the new representation will
35
+ # belong.
36
+ #
37
+ # +attributes+:: A hash containing the new page representation's
38
+ # attributes. This hash must have been run through
39
+ # Hash#clean before using it here.
40
+ #
41
+ # +name+:: The unique name for the new page representation.
42
+ def initialize(page, attributes, name)
43
+ # Set primary attributes
44
+ @page = page
45
+ @attributes = attributes
46
+ @name = name
47
+
48
+ # Get page content from page
49
+ @content = { :pre => nil, :post => nil }
50
+
51
+ # Reset flags
52
+ @compiled = false
53
+ @modified = false
54
+ @created = false
55
+ end
56
+
57
+ # Returns a proxy (Nanoc::PageRepProxy) for this page representation.
58
+ def to_proxy
59
+ @proxy ||= PageRepProxy.new(self)
60
+ end
61
+
62
+ # Returns true if this page rep's output file was created during the last
63
+ # compilation session, or false if the output file did already exist.
64
+ def created?
65
+ @created
66
+ end
67
+
68
+ # Returns true if this page rep's output file was modified during the last
69
+ # compilation session, or false if the output file wasn't changed.
70
+ def modified?
71
+ @modified
72
+ end
73
+
74
+ # Returns true if this page rep has been compiled, false otherwise.
75
+ def compiled?
76
+ @compiled
77
+ end
78
+
79
+ # Returns true if this page rep's output file is outdated and must be
80
+ # regenerated, false otherwise.
81
+ def outdated?
82
+ # Outdated if we don't know
83
+ return true if @page.mtime.nil?
84
+
85
+ # Outdated if compiled file doesn't exist
86
+ return true if !File.file?(disk_path)
87
+
88
+ # Get compiled mtime
89
+ compiled_mtime = File.stat(disk_path).mtime
90
+
91
+ # Outdated if file too old
92
+ return true if @page.mtime > compiled_mtime
93
+
94
+ # Outdated if layouts outdated
95
+ return true if @page.site.layouts.any? do |l|
96
+ l.mtime.nil? or l.mtime > compiled_mtime
97
+ end
98
+
99
+ # Outdated if page defaults outdated
100
+ return true if @page.site.page_defaults.mtime.nil?
101
+ return true if @page.site.page_defaults.mtime > compiled_mtime
102
+
103
+ # Outdated if code outdated
104
+ return true if @page.site.code.mtime.nil?
105
+ return true if @page.site.code.mtime > compiled_mtime
106
+
107
+ return false
108
+ end
109
+
110
+ # Returns the path to the output file, including the path to the output
111
+ # directory specified in the site configuration, and including the
112
+ # filename and extension.
113
+ def disk_path
114
+ @disk_path ||= @page.site.router.disk_path_for(self)
115
+ end
116
+
117
+ # Returns the path to the output file as it would be used in a web
118
+ # browser: starting with a slash (representing the web root), and only
119
+ # including the filename and extension if they cannot be ignored (i.e.
120
+ # they are not in the site configuration's list of index files).
121
+ def web_path
122
+ @web_path ||= @page.site.router.web_path_for(self)
123
+ end
124
+
125
+ # Returns the attribute with the given name. This method will look in
126
+ # several places for the requested attribute:
127
+ #
128
+ # 1. This page representation's attributes;
129
+ # 2. The attributes of this page representation's page;
130
+ # 3. The page defaults' representation corresponding to this page
131
+ # representation;
132
+ # 4. The page defaults in general;
133
+ # 5. The hardcoded page defaults, if everything else fails.
134
+ def attribute_named(name)
135
+ # Check in here
136
+ return @attributes[name] if @attributes.has_key?(name)
137
+
138
+ # Check in page
139
+ return @page.attributes[name] if @page.attributes.has_key?(name)
140
+
141
+ # Check in page defaults' page rep
142
+ page_default_reps = @page.site.page_defaults.attributes[:reps] || {}
143
+ page_default_rep = page_default_reps[@name] || {}
144
+ return page_default_rep[name] if page_default_rep.has_key?(name)
145
+
146
+ # Check in site defaults (global)
147
+ page_defaults_attrs = @page.site.page_defaults.attributes
148
+ return page_defaults_attrs[name] if page_defaults_attrs.has_key?(name)
149
+
150
+ # Check in hardcoded defaults
151
+ return Nanoc::Page::DEFAULTS[name]
152
+ end
153
+
154
+ # Returns the page representation content at the given stage.
155
+ #
156
+ # +stage+:: The stage at which the content should be fetched. Can be
157
+ # either +:pre+ or +:post+. To get the raw, uncompiled content,
158
+ # use Nanoc::Page#content.
159
+ def content(stage=:pre)
160
+ compile(stage == :post, true, false)
161
+
162
+ @content[stage]
163
+ end
164
+
165
+ # Returns the layout used for this page representation.
166
+ def layout
167
+ # Check whether layout is present
168
+ return nil if attribute_named(:layout).nil?
169
+
170
+ # Find layout
171
+ @layout ||= @page.site.layouts.find { |l| l.path == attribute_named(:layout).cleaned_path }
172
+ raise Nanoc::Errors::UnknownLayoutError.new(attribute_named(:layout)) if @layout.nil?
173
+
174
+ @layout
175
+ end
176
+
177
+ # Compiles the page representation and writes the result to the disk. This
178
+ # method should not be called directly; please use Nanoc::Compiler#run
179
+ # instead, and pass this page representation's page as its first argument.
180
+ #
181
+ # The page representation will only be compiled if it wasn't compiled
182
+ # before yet. To force recompilation of the page rep, forgetting any
183
+ # progress, set +from_scratch+ to true.
184
+ #
185
+ # +also_layout+:: true if the page rep should also be laid out and
186
+ # post-filtered, false if the page rep should only be
187
+ # pre-filtered.
188
+ #
189
+ # +even_when_not_outdated+:: true if the page rep should be compiled even
190
+ # if it is not outdated, false if not.
191
+ #
192
+ # +from_scratch+:: true if all compilation stages (pre-filter, layout,
193
+ # post-filter) should be performed again even if they
194
+ # have already been performed, false otherwise.
195
+ def compile(also_layout, even_when_not_outdated, from_scratch)
196
+ # Don't compile if already compiled
197
+ return if @content[also_layout ? :post : :pre] and !from_scratch
198
+
199
+ # Skip unless outdated
200
+ unless outdated? or even_when_not_outdated
201
+ if also_layout
202
+ Nanoc::NotificationCenter.post(:compilation_started, self)
203
+ Nanoc::NotificationCenter.post(:compilation_ended, self)
204
+ end
205
+ return
206
+ end
207
+
208
+ # Reset flags
209
+ @compiled = false
210
+ @modified = false
211
+ @created = false
212
+
213
+ # Forget progress if requested
214
+ @content = { :pre => nil, :post => nil } if from_scratch
215
+
216
+ # Check for recursive call
217
+ if @page.site.compiler.stack.include?(self)
218
+ @page.site.compiler.stack.push(self)
219
+ raise Nanoc::Errors::RecursiveCompilationError.new
220
+ end
221
+
222
+ # Start
223
+ @page.site.compiler.stack.push(self)
224
+ Nanoc::NotificationCenter.post(:compilation_started, self) if also_layout
225
+
226
+ # Pre-filter if necesary
227
+ if @content[:pre].nil?
228
+ do_filter(:pre)
229
+ end
230
+
231
+ # Post-filter if necessary
232
+ if @content[:post].nil? and also_layout
233
+ do_layout
234
+ do_filter(:post)
235
+
236
+ # Update status
237
+ @compiled = true
238
+ unless attribute_named(:skip_output)
239
+ @created = !File.file?(self.disk_path)
240
+ @modified = @created ? true : File.read(self.disk_path) != @content[:post]
241
+ end
242
+
243
+ # Write if necessary
244
+ write unless attribute_named(:skip_output)
245
+ end
246
+
247
+ # Stop
248
+ Nanoc::NotificationCenter.post(:compilation_ended, self) if also_layout
249
+ @page.site.compiler.stack.pop
250
+ end
251
+
252
+ private
253
+
254
+ # Runs the content through the filters in the given stage.
255
+ def do_filter(stage)
256
+ # Get content if necessary
257
+ content = (stage == :pre ? @page.content : @content[:post])
258
+
259
+ # Get filters
260
+ check_for_outdated_filters
261
+ filters = attribute_named(stage == :pre ? :filters_pre : :filters_post)
262
+
263
+ # Run each filter
264
+ filters.each do |filter_name|
265
+ # Create filter
266
+ klass = Nanoc::Filter.named(filter_name)
267
+ raise Nanoc::Errors::UnknownFilterError.new(filter_name) if klass.nil?
268
+ filter = klass.new(self)
269
+
270
+ # Run filter
271
+ Nanoc::NotificationCenter.post(:filtering_started, self, klass.identifier)
272
+ content = filter.run(content)
273
+ Nanoc::NotificationCenter.post(:filtering_ended, self, klass.identifier)
274
+ end
275
+
276
+ # Set content
277
+ @content[stage] = content
278
+ end
279
+
280
+ # Runs the content through this rep's layout.
281
+ def do_layout
282
+ # Don't layout if not necessary
283
+ if attribute_named(:layout).nil?
284
+ @content[:post] = @content[:pre]
285
+ return
286
+ end
287
+
288
+ # Create filter
289
+ klass = layout.filter_class
290
+ raise Nanoc::Errors::CannotDetermineFilterError.new(layout.path) if klass.nil?
291
+ filter = klass.new(self)
292
+
293
+ # Layout
294
+ Nanoc::NotificationCenter.post(:filtering_started, self, klass.identifier)
295
+ @content[:post] = filter.run(layout.content)
296
+ Nanoc::NotificationCenter.post(:filtering_ended, self, klass.identifier)
297
+ end
298
+
299
+ # Writes the compiled content to the disk.
300
+ def write
301
+ # TODO add ruby 1.9 support
302
+ FileUtils.mkdir_p(File.dirname(self.disk_path))
303
+ File.open(self.disk_path, 'w') { |io| io.write(@content[:post]) }
304
+ end
305
+
306
+ # Raises an error when the outdated 'filters' attribute is used.
307
+ def check_for_outdated_filters
308
+ unless attribute_named(:filters).nil?
309
+ raise Nanoc::Errors::NoLongerSupportedError.new(
310
+ 'The `filters` property is no longer supported; please use ' +
311
+ '`filters_pre` instead.'
312
+ )
313
+ end
314
+ end
315
+
316
+ end
317
+
318
+ end
@@ -1,19 +1,67 @@
1
1
  module Nanoc
2
+
3
+ # Nanoc::Plugin is the superclass for all plugins, such as filters
4
+ # (Nanoc::Filter), binary filters (Nanoc::BinaryFilter), routers
5
+ # (Nanoc::Router), data sources (Nanoc::DataSource) and VCSes
6
+ # (Nanoc::Extra::VCS). Each plugin has one or more unique identifiers, and
7
+ # several methods in this class provides functionality for finding plugins
8
+ # with given identifiers.
2
9
  class Plugin
3
10
 
11
+ MAP = {}
12
+
4
13
  class << self
5
- attr_accessor :_identifiers
6
- end
7
14
 
8
- def self.identifiers(*identifiers)
9
- self._identifiers = [] unless instance_variables.include?('@_identifiers')
10
- identifiers.empty? ? self._identifiers || [] : self._identifiers = (self._identifiers || []) + identifiers
11
- end
15
+ # Sets or returns the identifiers for this plugin.
16
+ #
17
+ # When given a list of identifier symbols, sets the identifiers for
18
+ # this plugin. When given nothing, returns an array of identifier
19
+ # symbols for this plugin.
20
+ def identifiers(*identifiers)
21
+ # Initialize
22
+ @identifiers = [] unless instance_variables.include?('@identifiers')
23
+
24
+ if identifiers.empty?
25
+ @identifiers
26
+ else
27
+ @identifiers = identifiers
28
+ @identifiers.each { |i| register(i, self) }
29
+ end
30
+ end
31
+
32
+ # Sets or returns the identifier for this plugin.
33
+ #
34
+ # When given an identifier symbols, sets the identifier for this plugin.
35
+ # When given nothing, returns the identifier for this plugin.
36
+ def identifier(identifier=nil)
37
+ # Initialize
38
+ @identifiers = [] unless instance_variables.include?('@identifiers')
39
+
40
+ if identifier.nil?
41
+ @identifiers.first
42
+ else
43
+ @identifiers = [ identifier ]
44
+ register(identifier, self)
45
+ end
46
+ end
47
+
48
+ # Registers the given class +klass+ with the given name. This will allow
49
+ # the named method to find the class.
50
+ def register(name, klass)
51
+ MAP[klass.superclass] ||= {}
52
+ MAP[klass.superclass][name.to_sym] = klass
53
+ end
54
+
55
+ # Returns the the plugin with the given name. Only subclasses of this
56
+ # class will be searched. For example, calling this method on
57
+ # Nanoc::Filter will cause only Nanoc::Filter subclasses to be searched.
58
+ def named(name)
59
+ MAP[self] ||= {}
60
+ MAP[self][name.to_sym]
61
+ end
12
62
 
13
- def self.identifier(identifier=nil)
14
- self._identifiers = [] unless instance_variables.include?('@_identifiers')
15
- identifier.nil? ? self.identifiers.first : self.identifiers(identifier)
16
63
  end
17
64
 
18
65
  end
66
+
19
67
  end
@@ -0,0 +1,29 @@
1
+ module Nanoc
2
+
3
+ # Nanoc::AssetProxy is a proxy object for an asset (Nanoc::Asset).
4
+ class AssetProxy < Proxy
5
+
6
+ # Requests the asset attribute with the given name. +key+ can be a string
7
+ # or a symbol, and it can contain a trailing question mark (which will be
8
+ # stripped).
9
+ def [](key)
10
+ real_key = key.to_s.sub(/\?$/, '').to_sym
11
+
12
+ if real_key == :mtime
13
+ @obj.mtime
14
+ elsif real_key == :path # backward compatibility
15
+ @obj.reps.find { |r| r.name == :default }.web_path
16
+ else
17
+ super(key)
18
+ end
19
+ end
20
+
21
+ # Returns the asset representation with the given name.
22
+ def reps(name)
23
+ rep = @obj.reps.find { |r| r.name == name }
24
+ rep.nil? ? nil : rep.to_proxy
25
+ end
26
+
27
+ end
28
+
29
+ end
@@ -0,0 +1,26 @@
1
+ module Nanoc
2
+
3
+ # Nanoc::AssetRepProxy is a proxy object for an asset representation
4
+ # (Nanoc::AssetRep).
5
+ class AssetRepProxy < Proxy
6
+
7
+ # Requests the asset representation attribute with the given name. +key+
8
+ # can be a string or a symbol, and it can contain a trailing question mark
9
+ # (which will be stripped).
10
+ def [](key)
11
+ real_key = key.to_s.sub(/\?$/, '').to_sym
12
+
13
+ if real_key == :name
14
+ @obj.name
15
+ elsif real_key == :path
16
+ @obj.web_path
17
+ elsif real_key == :asset
18
+ @obj.asset.to_proxy
19
+ else
20
+ super(key)
21
+ end
22
+ end
23
+
24
+ end
25
+
26
+ end