nanoc 2.0.4 → 2.1

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