nanoc2 2.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. data/ChangeLog +3 -0
  2. data/LICENSE +19 -0
  3. data/README +75 -0
  4. data/Rakefile +76 -0
  5. data/bin/nanoc2 +26 -0
  6. data/lib/nanoc2.rb +73 -0
  7. data/lib/nanoc2/base.rb +26 -0
  8. data/lib/nanoc2/base/asset.rb +117 -0
  9. data/lib/nanoc2/base/asset_defaults.rb +21 -0
  10. data/lib/nanoc2/base/asset_rep.rb +282 -0
  11. data/lib/nanoc2/base/binary_filter.rb +44 -0
  12. data/lib/nanoc2/base/code.rb +41 -0
  13. data/lib/nanoc2/base/compiler.rb +67 -0
  14. data/lib/nanoc2/base/core_ext.rb +2 -0
  15. data/lib/nanoc2/base/core_ext/hash.rb +78 -0
  16. data/lib/nanoc2/base/core_ext/string.rb +8 -0
  17. data/lib/nanoc2/base/data_source.rb +286 -0
  18. data/lib/nanoc2/base/defaults.rb +30 -0
  19. data/lib/nanoc2/base/filter.rb +93 -0
  20. data/lib/nanoc2/base/layout.rb +91 -0
  21. data/lib/nanoc2/base/notification_center.rb +66 -0
  22. data/lib/nanoc2/base/page.rb +132 -0
  23. data/lib/nanoc2/base/page_defaults.rb +20 -0
  24. data/lib/nanoc2/base/page_rep.rb +324 -0
  25. data/lib/nanoc2/base/plugin.rb +71 -0
  26. data/lib/nanoc2/base/proxies.rb +5 -0
  27. data/lib/nanoc2/base/proxies/asset_proxy.rb +29 -0
  28. data/lib/nanoc2/base/proxies/asset_rep_proxy.rb +26 -0
  29. data/lib/nanoc2/base/proxies/layout_proxy.rb +25 -0
  30. data/lib/nanoc2/base/proxies/page_proxy.rb +35 -0
  31. data/lib/nanoc2/base/proxies/page_rep_proxy.rb +28 -0
  32. data/lib/nanoc2/base/proxy.rb +37 -0
  33. data/lib/nanoc2/base/router.rb +72 -0
  34. data/lib/nanoc2/base/site.rb +274 -0
  35. data/lib/nanoc2/base/template.rb +64 -0
  36. data/lib/nanoc2/binary_filters.rb +1 -0
  37. data/lib/nanoc2/binary_filters/image_science_thumbnail.rb +28 -0
  38. data/lib/nanoc2/cli.rb +9 -0
  39. data/lib/nanoc2/cli/base.rb +132 -0
  40. data/lib/nanoc2/cli/commands.rb +10 -0
  41. data/lib/nanoc2/cli/commands/autocompile.rb +80 -0
  42. data/lib/nanoc2/cli/commands/compile.rb +312 -0
  43. data/lib/nanoc2/cli/commands/create_layout.rb +85 -0
  44. data/lib/nanoc2/cli/commands/create_page.rb +85 -0
  45. data/lib/nanoc2/cli/commands/create_site.rb +323 -0
  46. data/lib/nanoc2/cli/commands/create_template.rb +76 -0
  47. data/lib/nanoc2/cli/commands/help.rb +69 -0
  48. data/lib/nanoc2/cli/commands/info.rb +125 -0
  49. data/lib/nanoc2/cli/commands/switch.rb +141 -0
  50. data/lib/nanoc2/cli/commands/update.rb +91 -0
  51. data/lib/nanoc2/cli/logger.rb +72 -0
  52. data/lib/nanoc2/data_sources.rb +2 -0
  53. data/lib/nanoc2/data_sources/filesystem.rb +707 -0
  54. data/lib/nanoc2/data_sources/filesystem_combined.rb +495 -0
  55. data/lib/nanoc2/extra.rb +6 -0
  56. data/lib/nanoc2/extra/auto_compiler.rb +285 -0
  57. data/lib/nanoc2/extra/context.rb +22 -0
  58. data/lib/nanoc2/extra/core_ext.rb +2 -0
  59. data/lib/nanoc2/extra/core_ext/hash.rb +54 -0
  60. data/lib/nanoc2/extra/core_ext/time.rb +13 -0
  61. data/lib/nanoc2/extra/file_proxy.rb +29 -0
  62. data/lib/nanoc2/extra/vcs.rb +48 -0
  63. data/lib/nanoc2/extra/vcses.rb +5 -0
  64. data/lib/nanoc2/extra/vcses/bazaar.rb +21 -0
  65. data/lib/nanoc2/extra/vcses/dummy.rb +20 -0
  66. data/lib/nanoc2/extra/vcses/git.rb +21 -0
  67. data/lib/nanoc2/extra/vcses/mercurial.rb +21 -0
  68. data/lib/nanoc2/extra/vcses/subversion.rb +21 -0
  69. data/lib/nanoc2/filters.rb +16 -0
  70. data/lib/nanoc2/filters/bluecloth.rb +13 -0
  71. data/lib/nanoc2/filters/erb.rb +19 -0
  72. data/lib/nanoc2/filters/erubis.rb +14 -0
  73. data/lib/nanoc2/filters/haml.rb +21 -0
  74. data/lib/nanoc2/filters/markaby.rb +14 -0
  75. data/lib/nanoc2/filters/maruku.rb +14 -0
  76. data/lib/nanoc2/filters/old.rb +19 -0
  77. data/lib/nanoc2/filters/rainpress.rb +13 -0
  78. data/lib/nanoc2/filters/rdiscount.rb +13 -0
  79. data/lib/nanoc2/filters/rdoc.rb +23 -0
  80. data/lib/nanoc2/filters/redcloth.rb +14 -0
  81. data/lib/nanoc2/filters/relativize_paths.rb +16 -0
  82. data/lib/nanoc2/filters/relativize_paths_in_css.rb +16 -0
  83. data/lib/nanoc2/filters/relativize_paths_in_html.rb +16 -0
  84. data/lib/nanoc2/filters/rubypants.rb +14 -0
  85. data/lib/nanoc2/filters/sass.rb +18 -0
  86. data/lib/nanoc2/helpers.rb +9 -0
  87. data/lib/nanoc2/helpers/blogging.rb +217 -0
  88. data/lib/nanoc2/helpers/capturing.rb +63 -0
  89. data/lib/nanoc2/helpers/filtering.rb +54 -0
  90. data/lib/nanoc2/helpers/html_escape.rb +25 -0
  91. data/lib/nanoc2/helpers/link_to.rb +113 -0
  92. data/lib/nanoc2/helpers/render.rb +49 -0
  93. data/lib/nanoc2/helpers/tagging.rb +56 -0
  94. data/lib/nanoc2/helpers/text.rb +38 -0
  95. data/lib/nanoc2/helpers/xml_sitemap.rb +63 -0
  96. data/lib/nanoc2/routers.rb +3 -0
  97. data/lib/nanoc2/routers/default.rb +54 -0
  98. data/lib/nanoc2/routers/no_dirs.rb +66 -0
  99. data/lib/nanoc2/routers/versioned.rb +79 -0
  100. metadata +185 -0
@@ -0,0 +1,6 @@
1
+ require 'nanoc2/extra/auto_compiler'
2
+ require 'nanoc2/extra/context'
3
+ require 'nanoc2/extra/core_ext'
4
+ require 'nanoc2/extra/file_proxy'
5
+ require 'nanoc2/extra/vcs'
6
+ require 'nanoc2/extra/vcses'
@@ -0,0 +1,285 @@
1
+ require 'webrick'
2
+
3
+ module Nanoc2::Extra
4
+
5
+ # Nanoc2::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 < Nanoc2::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?(Nanoc2::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 do |env|
102
+ begin
103
+ handle_request(env['PATH_INFO'])
104
+ rescue Exception => exception
105
+ return serve_500(nil, exception)
106
+ end
107
+ end
108
+
109
+ # Run Rack app
110
+ port ||= 3000
111
+ handler.run(app, :Port => port, :port => port) do |server|
112
+ trap(:INT) { server.stop }
113
+ end
114
+ end
115
+
116
+ private
117
+
118
+ def preferred_handler
119
+ return @preferred_handler unless @preferred_handler.nil?
120
+
121
+ HANDLER_NAMES.each do |handler_name|
122
+ # Get handler
123
+ @preferred_handler = handler_named(handler_name)
124
+
125
+ # Make sure we have one
126
+ break unless @preferred_handler.nil?
127
+ end
128
+
129
+ @preferred_handler
130
+ end
131
+
132
+ def handler_named(handler_name)
133
+ # Build list of handlers
134
+ @handlers ||= {
135
+ :cgi => {
136
+ :proc => lambda { Rack::Handler::CGI }
137
+ },
138
+ :fastcgi => { # FIXME buggy
139
+ :proc => lambda { Rack::Handler::FastCGI }
140
+ },
141
+ :lsws => { # FIXME test
142
+ :proc => lambda { Rack::Handler::LSWS }
143
+ },
144
+ :mongrel => {
145
+ :proc => lambda { Rack::Handler::Mongrel }
146
+ },
147
+ :scgi => { # FIXME buggy
148
+ :proc => lambda { Rack::Handler::SCGI }
149
+ },
150
+ :webrick => {
151
+ :proc => lambda { Rack::Handler::WEBrick }
152
+ },
153
+ :thin => {
154
+ :proc => lambda { Rack::Handler::Thin },
155
+ :requires => [ 'thin' ]
156
+ },
157
+ :ebb => {
158
+ :proc => lambda { Rack::Handler::Ebb },
159
+ :requires => [ 'ebb' ]
160
+ }
161
+ }
162
+
163
+ begin
164
+ # Lookup handler
165
+ handler = @handlers[handler_name]
166
+
167
+ # Load requirements
168
+ (handler[:requires] || []).each { |r| require r }
169
+
170
+ # Get handler class
171
+ handler[:proc].call
172
+ rescue NameError, LoadError
173
+ nil
174
+ end
175
+ end
176
+
177
+ def handle_request(path)
178
+ @mutex.synchronize do
179
+ # Reload site data
180
+ @site.load_data(true)
181
+
182
+ # Get file path
183
+ file_path = @site.config[:output_dir] + path
184
+
185
+ # Find rep
186
+ objs = @site.pages + @site.assets
187
+ reps = objs.map { |o| o.reps }.flatten
188
+ rep = reps.find { |r| r.web_path == path }
189
+
190
+ if rep.nil?
191
+ # Get list of possible filenames
192
+ if file_path =~ /\/$/
193
+ all_file_paths = @site.config[:index_filenames].map { |f| file_path + f }
194
+ else
195
+ all_file_paths = [ file_path ]
196
+ end
197
+ good_file_path = all_file_paths.find { |f| File.file?(f) }
198
+
199
+ # Serve file
200
+ if good_file_path
201
+ serve_file(good_file_path)
202
+ else
203
+ serve_404(path)
204
+ end
205
+ else
206
+ # Serve rep
207
+ serve_rep(rep)
208
+ end
209
+ end
210
+ end
211
+
212
+ def h(s)
213
+ ERB::Util.html_escape(s)
214
+ end
215
+
216
+ def mime_type_of(path, fallback)
217
+ mime_type = MIME::Types.of(path).first
218
+ mime_type = mime_type.nil? ? fallback : mime_type.simplified
219
+ end
220
+
221
+ def serve_404(path)
222
+ # Build response
223
+ [
224
+ 404,
225
+ { 'Content-Type' => 'text/html' },
226
+ [ ERB.new(ERROR_404).result(binding) ]
227
+ ]
228
+ end
229
+
230
+ def serve_500(path, exception)
231
+ # Build message
232
+ case exception
233
+ when Nanoc2::Errors::UnknownLayoutError
234
+ message = "Unknown layout: #{exception.message}"
235
+ when Nanoc2::Errors::UnknownFilterError
236
+ message = "Unknown filter: #{exception.message}"
237
+ when Nanoc2::Errors::CannotDetermineFilterError
238
+ message = "Cannot determine filter for layout: #{exception.message}"
239
+ when Nanoc2::Errors::RecursiveCompilationError
240
+ message = "Recursive call to page content. Page stack:"
241
+ when Nanoc2::Errors::NoLongerSupportedError
242
+ message = "No longer supported: #{exception.message}"
243
+ else
244
+ message = "Unknown error: #{exception.message}"
245
+ end
246
+
247
+ # Build response
248
+ [
249
+ 500,
250
+ { 'Content-Type' => 'text/html' },
251
+ [ ERB.new(ERROR_500).result(binding) ]
252
+ ]
253
+ end
254
+
255
+ def serve_file(path)
256
+ # Build response
257
+ [
258
+ 200,
259
+ { 'Content-Type' => mime_type_of(path, 'application/octet-stream') },
260
+ [ File.open(path, 'rb') { |io| io.read } ]
261
+ ]
262
+ end
263
+
264
+ def serve_rep(rep)
265
+ # Recompile rep
266
+ begin
267
+ @site.compiler.run(
268
+ [ rep.respond_to?(:page) ? rep.page : rep.asset ],
269
+ :even_when_not_outdated => @include_outdated
270
+ )
271
+ rescue Exception => exception
272
+ return serve_500(rep.web_path, exception)
273
+ end
274
+
275
+ # Build response
276
+ [
277
+ 200,
278
+ { 'Content-Type' => mime_type_of(rep.disk_path, 'text/html') },
279
+ [ File.read(rep.disk_path) ]
280
+ ]
281
+ end
282
+
283
+ end
284
+
285
+ end
@@ -0,0 +1,22 @@
1
+ module Nanoc2::Extra
2
+
3
+ # Nanoc2::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,2 @@
1
+ require 'nanoc2/extra/core_ext/hash'
2
+ require 'nanoc2/extra/core_ext/time'
@@ -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
+ # Nanoc2::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 = Nanoc2::Page::DEFAULTS.include?(key) || Nanoc2::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 Nanoc2::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 =~ /^__/ || m.to_s == 'object_id' }
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 Nanoc2::Extra
2
+
3
+ # Nanoc2::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 < Nanoc2::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