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,265 @@
1
+ require 'webrick'
2
+
3
+ module Nanoc::Extra
4
+
5
+ # Nanoc::Extra::AutoCompiler is a web server that will automatically compile
6
+ # pages as they are requested. It also serves static files such as
7
+ # stylesheets and images.
8
+ class AutoCompiler
9
+
10
+ # Error that is raised when the autocompiler is started if the specified
11
+ # handler cannot be found.
12
+ class UnknownHandlerError < Nanoc::Error ; end
13
+
14
+ HANDLER_NAMES = [ :thin, :mongrel, :webrick, :ebb, :cgi, :fastcgi, :lsws, :scgi ]
15
+
16
+ ERROR_404 = <<END
17
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
18
+ <html>
19
+ <head>
20
+ <title>404 File Not Found</title>
21
+ <style type="text/css">
22
+ body { padding: 10px; border: 10px solid #f00; margin: 10px; font-family: Helvetica, Arial, sans-serif; }
23
+ </style>
24
+ </head>
25
+ <body>
26
+ <h1>404 File Not Found</h1>
27
+ <p>The file you requested, <i><%=h path %></i>, was not found on this server.</p>
28
+ </body>
29
+ </html>
30
+ END
31
+
32
+ ERROR_500 = <<END
33
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
34
+ <html>
35
+ <head>
36
+ <title>500 Server Error</title>
37
+ <style type="text/css">
38
+ body { padding: 10px; border: 10px solid #f00; margin: 10px; font-family: Helvetica, Arial, sans-serif; }
39
+ </style>
40
+ </head>
41
+ <body>
42
+ <h1>500 Server Error</h1>
43
+ <p>An error occurred while compiling the page you requested, <i><%=h path %></i>.</p>
44
+ <p>If you think this is a bug in nanoc, please do <a href="http://nanoc.stoneship.org/trac/newticket">report it</a>&mdash;thanks!</p>
45
+ <p>Message:</p>
46
+ <blockquote><p><%=h message %></p></blockquote>
47
+ <p>Page compilation stack:</p>
48
+ <ol>
49
+ <% @site.compiler.stack.reverse.each do |item| %>
50
+ <% if item.is_a?(Nanoc::PageRep) # page rep %>
51
+ <li><strong>Page</strong> <%= item.page.path %> (rep <%= item.name %>)</li>
52
+ <% else # layout %>
53
+ <li><strong>Layout</strong> <%= item.path %></li>
54
+ <% end %>
55
+ <% end %>
56
+ </ol>
57
+ <p>Backtrace:</p>
58
+ <ol>
59
+ <% exception.backtrace.each do |line| %>
60
+ <li><%= line %></li>
61
+ <% end %>
62
+ </ol>
63
+ </body>
64
+ </html>
65
+ END
66
+
67
+ # Creates a new autocompiler for the given site.
68
+ def initialize(site, include_outdated=false)
69
+ # Set site
70
+ @site = site
71
+
72
+ # Set options
73
+ @include_outdated = include_outdated
74
+
75
+ # Create mutex to prevent parallel requests
76
+ @mutex = Mutex.new
77
+ end
78
+
79
+ # Starts the server on the given port.
80
+ #
81
+ # +port+:: The port the autocompiler web server should be started on. Can
82
+ # be nil; in this case the server will be started on port 3000.
83
+ #
84
+ # +handler_name+:: A symbol containing the name of the handler to use. See
85
+ # HANDLER_NAMES for a list of supported handlers. Can be
86
+ # set to nil; in this case the best handler will be
87
+ # picked.
88
+ def start(port, handler_name)
89
+ require 'mime/types'
90
+ require 'rack'
91
+
92
+ # Determine handler
93
+ if handler_name.nil?
94
+ handler = preferred_handler
95
+ else
96
+ handler = handler_named(handler_name.to_sym)
97
+ raise UnknownHandlerError.new(handler_name) if handler.nil?
98
+ end
99
+
100
+ # Build Rack app
101
+ app = lambda { |env| handle_request(env['PATH_INFO']) }
102
+
103
+ # Run Rack app
104
+ port ||= 3000
105
+ handler.run(app, :Port => port, :port => port) do |server|
106
+ trap(:INT) { server.stop }
107
+ end
108
+ end
109
+
110
+ private
111
+
112
+ def preferred_handler
113
+ return @preferred_handler unless @preferred_handler.nil?
114
+
115
+ HANDLER_NAMES.each do |handler_name|
116
+ # Get handler
117
+ @preferred_handler = handler_named(handler_name)
118
+
119
+ # Make sure we have one
120
+ break unless @preferred_handler.nil?
121
+ end
122
+
123
+ @preferred_handler
124
+ end
125
+
126
+ def handler_named(handler_name)
127
+ # Build list of handlers
128
+ @handlers ||= {
129
+ :cgi => {
130
+ :proc => lambda { Rack::Handler::CGI }
131
+ },
132
+ :fastcgi => { # FIXME buggy
133
+ :proc => lambda { Rack::Handler::FastCGI }
134
+ },
135
+ :lsws => { # FIXME test
136
+ :proc => lambda { Rack::Handler::LSWS }
137
+ },
138
+ :mongrel => {
139
+ :proc => lambda { Rack::Handler::Mongrel }
140
+ },
141
+ :scgi => { # FIXME buggy
142
+ :proc => lambda { Rack::Handler::SCGI }
143
+ },
144
+ :webrick => {
145
+ :proc => lambda { Rack::Handler::WEBrick }
146
+ },
147
+ :thin => {
148
+ :proc => lambda { Rack::Handler::Thin },
149
+ :requires => [ 'thin' ]
150
+ },
151
+ :ebb => {
152
+ :proc => lambda { Rack::Handler::Ebb },
153
+ :requires => [ 'ebb' ]
154
+ }
155
+ }
156
+
157
+ begin
158
+ # Lookup handler
159
+ handler = @handlers[handler_name]
160
+
161
+ # Load requirements
162
+ (handler[:requires] || []).each { |r| require r }
163
+
164
+ # Get handler class
165
+ handler[:proc].call
166
+ rescue NameError, LoadError
167
+ nil
168
+ end
169
+ end
170
+
171
+ def handle_request(path)
172
+ @mutex.synchronize do
173
+ # Reload site data
174
+ @site.load_data(true)
175
+
176
+ # Get page or file
177
+ page_reps = @site.pages.map { |p| p.reps }.flatten
178
+ page_rep = page_reps.find { |p| p.web_path == path.cleaned_path }
179
+ file_path = @site.config[:output_dir] + path
180
+
181
+ if page_rep.nil?
182
+ # Serve file
183
+ if File.file?(file_path)
184
+ serve_file(file_path)
185
+ else
186
+ serve_404(path)
187
+ end
188
+ else
189
+ # Serve page rep
190
+ serve_page_rep(page_rep)
191
+ end
192
+ end
193
+ end
194
+
195
+ def h(s)
196
+ ERB::Util.html_escape(s)
197
+ end
198
+
199
+ def mime_type_of(path, fallback)
200
+ mime_type = MIME::Types.of(path).first
201
+ mime_type = mime_type.nil? ? fallback : mime_type.simplified
202
+ end
203
+
204
+ def serve_404(path)
205
+ # Build response
206
+ [
207
+ 404,
208
+ { 'Content-Type' => 'text/html' },
209
+ [ ERB.new(ERROR_404).result(binding) ]
210
+ ]
211
+ end
212
+
213
+ def serve_500(path, exception)
214
+ # Build message
215
+ case exception
216
+ when Nanoc::Errors::UnknownLayoutError
217
+ message = "Unknown layout: #{exception.message}"
218
+ when Nanoc::Errors::UnknownFilterError
219
+ message = "Unknown filter: #{exception.message}"
220
+ when Nanoc::Errors::CannotDetermineFilterError
221
+ message = "Cannot determine filter for layout: #{exception.message}"
222
+ when Nanoc::Errors::RecursiveCompilationError
223
+ message = "Recursive call to page content. Page stack:"
224
+ when Nanoc::Errors::NoLongerSupportedError
225
+ message = "No longer supported: #{exception.message}"
226
+ else
227
+ message = "Unknown error: #{exception.message}"
228
+ end
229
+
230
+ # Build response
231
+ [
232
+ 500,
233
+ { 'Content-Type' => 'text/html' },
234
+ [ ERB.new(ERROR_500).result(binding) ]
235
+ ]
236
+ end
237
+
238
+ def serve_file(path)
239
+ # Build response
240
+ [
241
+ 200,
242
+ { 'Content-Type' => mime_type_of(path, 'application/octet-stream') },
243
+ [ File.read(path) ]
244
+ ]
245
+ end
246
+
247
+ def serve_page_rep(page_rep)
248
+ # Recompile page rep
249
+ begin
250
+ @site.compiler.run([ page_rep.page ], :even_when_not_outdated => @include_outdated)
251
+ rescue Exception => exception
252
+ return serve_500(page_rep.web_path, exception)
253
+ end
254
+
255
+ # Build response
256
+ [
257
+ 200,
258
+ { 'Content-Type' => mime_type_of(page_rep.disk_path, 'text/html') },
259
+ [ page_rep.content(:post) ]
260
+ ]
261
+ end
262
+
263
+ end
264
+
265
+ end
@@ -0,0 +1,22 @@
1
+ module Nanoc::Extra
2
+
3
+ # Nanoc::Extra::Context provides a context and a Binding for use in various
4
+ # filters, such as the ERB and Haml one.
5
+ class Context
6
+
7
+ # Creates a new context based off the contents of the hash. Each pair in
8
+ # the hash will be converted to an instance variable. For example, passing
9
+ # the hash { :foo => 'bar' } will cause @foo to have the value "bar".
10
+ def initialize(hash)
11
+ hash.each_pair do |key, value|
12
+ instance_variable_set('@' + key.to_s, value)
13
+ end
14
+ end
15
+
16
+ # Returns a binding for this context.
17
+ def get_binding
18
+ binding
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,54 @@
1
+ class Hash
2
+
3
+ # Converts this hash into YAML format, splitting the YAML output into a
4
+ # 'builtin' and a 'custom' section. A key that is present in
5
+ # Nanoc::Page::DEFAULTS will be considered a 'default' key; all other keys
6
+ # will be put in the 'Custom' section.
7
+ #
8
+ # For example, the hash:
9
+ #
10
+ # {
11
+ # :title => 'My Cool Page',
12
+ # :filters_pre => [ 'foo', 'bar' ]
13
+ # }
14
+ #
15
+ # will be converted into:
16
+ #
17
+ # # Built-in
18
+ # filters_pre: [ 'foo', 'bar' ]
19
+ #
20
+ # # Custom
21
+ # title: 'My Cool Page'
22
+ #
23
+ # as +filters_pre+ is considered a 'default' key while +title+ is not.
24
+ def to_split_yaml
25
+ # Skip irrelevant keys
26
+ hash = self.reject { |k,v| k == :file }
27
+
28
+ # Split keys
29
+ hashes = { :builtin => {}, :custom => {} }
30
+ hash.each_pair do |key, value|
31
+ kind = Nanoc::Page::DEFAULTS.include?(key) || Nanoc::Asset::DEFAULTS.include?(key) ? :builtin : :custom
32
+ hashes[kind][key] = value
33
+ end
34
+
35
+ # Dump and clean hashes
36
+ dumps = { :builtin => '', :custom => '' }
37
+ [ :builtin, :custom ].each do |kind|
38
+ if hashes[kind].keys.empty?
39
+ dumps[kind] = "\n"
40
+ else
41
+ raw_dump = YAML.dump(hashes[kind].stringify_keys)
42
+ dumps[kind] = raw_dump.split('---')[1].gsub("\n\n", "\n")
43
+ end
44
+ end
45
+
46
+ # Built composite YAML file
47
+ '# Built-in' +
48
+ dumps[:builtin] +
49
+ "\n" +
50
+ '# Custom' +
51
+ dumps[:custom]
52
+ end
53
+
54
+ end
@@ -0,0 +1,13 @@
1
+ class Time
2
+
3
+ # Returns a string with the time in an ISO-8601 date format.
4
+ def to_iso8601_date
5
+ self.strftime("%Y-%m-%d")
6
+ end
7
+
8
+ # Returns a string with the time in an ISO-8601 time format.
9
+ def to_iso8601_time
10
+ self.gmtime.strftime("%Y-%m-%dT%H:%M:%SZ")
11
+ end
12
+
13
+ end
@@ -0,0 +1,29 @@
1
+ module Nanoc::Extra
2
+
3
+ # A FileProxy is a proxy for a File object. It is used to prevent a File
4
+ # object from being created until it is actually necessary.
5
+ #
6
+ # For example, a site with a few thousand pages would fail to compile
7
+ # because the massive amount of file descriptors necessary, but the file
8
+ # proxy will make sure the File object is not created until it is used.
9
+ class FileProxy
10
+
11
+ instance_methods.each { |m| undef_method m unless m =~ /^__/ }
12
+
13
+ # Creates a new file proxy for the given path. This is similar to
14
+ # creating a File object with the same path, except that the File object
15
+ # will not be created until it is accessed.
16
+ def initialize(path)
17
+ @path = path
18
+ end
19
+
20
+ # Makes sure all method calls are relayed to a File object, which will
21
+ # be created right before the method call takes place and destroyed
22
+ # right after.
23
+ def method_missing(sym, *args, &block)
24
+ File.new(@path).__send__(sym, *args, &block)
25
+ end
26
+
27
+ end
28
+
29
+ end
@@ -0,0 +1,48 @@
1
+ module Nanoc::Extra
2
+
3
+ # Nanoc::Extra::VCS is a very simple representation of a version control
4
+ # system that abstracts the add, remove and move operations. It does not
5
+ # commit. This class is primarily used by data sources that store data as
6
+ # flat files on the disk.
7
+ #
8
+ # This is the abstract superclass for all VCSes. Subclasses should implement
9
+ # the indicated methods.
10
+ class VCS < Nanoc::Plugin
11
+
12
+ # Adds the file with the given filename to the working copy.
13
+ #
14
+ # Subclasses must implement this method.
15
+ def add(filename)
16
+ not_implemented('add')
17
+ end
18
+
19
+ # Removes the file with the given filename from the working copy. When
20
+ # this method is executed, the file should no longer be present on the
21
+ # disk.
22
+ #
23
+ # Subclasses must implement this method.
24
+ def remove(filename)
25
+ not_implemented('remove')
26
+ end
27
+
28
+ # Moves the file with the given filename to a new location. When this
29
+ # method is executed, the original file should no longer be present on the
30
+ # disk.
31
+ #
32
+ # Subclasses must implement this method.
33
+ def move(src, dst)
34
+ not_implemented('move')
35
+ end
36
+
37
+ private
38
+
39
+ def not_implemented(name)
40
+ raise NotImplementedError.new(
41
+ "#{self.class} does not override ##{name}, which is required for " +
42
+ "this data source to be used."
43
+ )
44
+ end
45
+
46
+ end
47
+
48
+ end
@@ -0,0 +1,21 @@
1
+ module Nanoc::Extra::VCSes
2
+
3
+ class Bazaar < Nanoc::Extra::VCS
4
+
5
+ identifiers :bazaar, :bzr
6
+
7
+ def add(filename)
8
+ system('bzr', 'add', filename)
9
+ end
10
+
11
+ def remove(filename)
12
+ system('bzr', 'rm', filename)
13
+ end
14
+
15
+ def move(src, dst)
16
+ system('bzr', 'mv', src, dst)
17
+ end
18
+
19
+ end
20
+
21
+ end