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,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