nanoc3 3.1.0b1 → 3.1.0b2

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,7 +3,7 @@
3
3
  module Nanoc3
4
4
 
5
5
  # The current nanoc version.
6
- VERSION = '3.1.0b1'
6
+ VERSION = '3.1.0b2'
7
7
 
8
8
  end
9
9
 
@@ -9,6 +9,24 @@ module Nanoc3::StringExtensions
9
9
  "/#{self}/".gsub(/^\/+|\/+$/, '/')
10
10
  end
11
11
 
12
+ # Replaces Unicode characters with their ASCII decompositions if the
13
+ # environment does not support Unicode.
14
+ #
15
+ # This method is not suited for general usage. If you need similar
16
+ # functionality, consider using the Iconv library instead.
17
+ #
18
+ # @return [String] The decomposed string
19
+ def make_compatible_with_env
20
+ # Check whether environment supports Unicode
21
+ # TODO this is ugly, and there most likely are better ways to do this
22
+ is_unicode_supported = %w( LC_ALL LC_CTYPE LANG ).any? { |e| ENV[e] =~ /UTF/ }
23
+ return self if is_unicode_supported
24
+
25
+ # Decompose if necessary
26
+ # TODO this decomposition is not generally usable
27
+ self.gsub(/“|”/, '"').gsub(/‘|’/, '\'').gsub('…', '...')
28
+ end
29
+
12
30
  end
13
31
 
14
32
  class String
@@ -27,6 +27,10 @@ module Nanoc3
27
27
  # stored
28
28
  attr_accessor :filename
29
29
 
30
+ # @return [Array<Nanoc3::Item>] The list of items that is being tracked
31
+ # by the dependency tracker
32
+ attr_reader :items
33
+
30
34
  # The version of the file format used to store dependencies.
31
35
  STORE_VERSION = 2
32
36
 
@@ -217,14 +221,14 @@ module Nanoc3
217
221
 
218
222
  # Mark successors of outdated items as outdated
219
223
  require 'set'
220
- processed = Set.new
221
224
  unprocessed = @items.select { |i| i.outdated? }
225
+ seen = Set.new(unprocessed)
222
226
  until unprocessed.empty?
223
227
  item = unprocessed.shift
224
- processed << item
225
228
 
226
229
  self.direct_successors_of(item).each do |successor|
227
- next if successor.outdated? || processed.include?(successor)
230
+ next if seen.include?(successor)
231
+ seen << successor
228
232
 
229
233
  successor.outdated_due_to_dependencies = true
230
234
  unprocessed << successor
@@ -241,9 +245,7 @@ module Nanoc3
241
245
  #
242
246
  # @return [void]
243
247
  def forget_dependencies_for(item)
244
- @graph.vertices.each do |v|
245
- @graph.remove_edge(v, item)
246
- end
248
+ @graph.delete_edges_to(item)
247
249
  end
248
250
 
249
251
  # Prints the dependency graph in human-readable form.
@@ -1,11 +1,11 @@
1
1
  # encoding: utf-8
2
2
 
3
+ require 'set'
4
+
3
5
  module Nanoc3
4
6
 
5
7
  # Represents a directed graph. It is used by the dependency tracker for
6
- # storing and querying dependencies between items. Internally, the graph
7
- # will be stored as an adjacency matrix. For this, the
8
- # {Nanoc3::DirectedGraph::SquareBooleanMatrix} class is used.
8
+ # storing and querying dependencies between items.
9
9
  #
10
10
  # @example Creating and using a directed graph
11
11
  #
@@ -33,82 +33,6 @@ module Nanoc3
33
33
  # # => %w( b c )
34
34
  class DirectedGraph
35
35
 
36
- # A square matrix that contains boolean values. It is used as an adjacency
37
- # matrix by the {Nanoc3::DirectedGraph} class.
38
- #
39
- # This class is a helper class, which means that it is not used directly
40
- # by nanoc. Future versions of nanoc may no longer contain this class. Do
41
- # not depend on this class to be available.
42
- class SquareBooleanMatrix
43
-
44
- # Creates a new matrix with the given number of rows/columns.
45
- #
46
- # @param [Number] size The number of elements along both sides of the
47
- # matrix (in other words, the square root of the number of elements)
48
- def initialize(size)
49
- @size = size
50
- end
51
-
52
- # Gets the value at the given x/y coordinates.
53
- #
54
- # @param [Number] x The X coordinate
55
- # @param [Number] y The Y coordinate
56
- #
57
- # @return The value at the given coordinates
58
- def [](x, y)
59
- @data ||= {}
60
- @data[x] ||= {}
61
- @data[x].has_key?(y) ? @data[x][y] : false
62
- end
63
-
64
- # Sets the value at the given x/y coordinates.
65
- #
66
- # @param [Number] x The X coordinate
67
- # @param [Number] y The Y coordinate
68
- # @param value The value to set at the given coordinates
69
- #
70
- # @return [void]
71
- def []=(x, y, value)
72
- @data ||= {}
73
- @data[x] ||= {}
74
- @data[x][y] = value
75
- end
76
-
77
- # Returns a string representing this matrix in ASCII art.
78
- #
79
- # @return [String] The string representation of this matrix
80
- def to_s
81
- s = ''
82
-
83
- # Calculate column width
84
- width = (@size-1).to_s.size
85
-
86
- # Add header
87
- s << ' ' + ' '*width + ' '
88
- @size.times { |i| s << '| ' + format("%#{width}i", i) + ' ' }
89
- s << "\n"
90
-
91
- # Add rows
92
- @size.times do |x|
93
- # Add line
94
- s << '-' + '-'*width + '-'
95
- @size.times { |i| s << '+-' + '-'*width + '-' }
96
- s << "\n"
97
-
98
- # Add actual row
99
- s << ' ' + format("%#{width}i", x)+ ' '
100
- @size.times do |y|
101
- s << '| ' + format("%#{width}s", self[x, y] ? '*' : ' ') + ' '
102
- end
103
- s << "\n"
104
- end
105
-
106
- # Done
107
- s
108
- end
109
-
110
- end
111
-
112
36
  # The list of vertices in this graph.
113
37
  #
114
38
  # @return [Array]
@@ -117,10 +41,16 @@ module Nanoc3
117
41
  # Creates a new directed graph with the given vertices.
118
42
  def initialize(vertices)
119
43
  @vertices = vertices
120
- generate_indices
121
- invalidate_successor_predecessor_cache
122
44
 
123
- @matrix = SquareBooleanMatrix.new(@vertices.size)
45
+ @from_graph = {}
46
+ @to_graph = {}
47
+
48
+ @vertice_indexes = {}
49
+ vertices.each_with_index do |v, i|
50
+ @vertice_indexes[v] = i
51
+ end
52
+
53
+ invalidate_caches
124
54
  end
125
55
 
126
56
  # Adds an edge from the first vertex to the second vertex.
@@ -130,9 +60,13 @@ module Nanoc3
130
60
  #
131
61
  # @return [void]
132
62
  def add_edge(from, to)
133
- from_index, to_index = indices_of(from, to)
134
- @matrix[from_index, to_index] = true
135
- invalidate_successor_predecessor_cache
63
+ @from_graph[from] ||= Set.new
64
+ @from_graph[from] << to
65
+
66
+ @to_graph[to] ||= Set.new
67
+ @to_graph[to] << from
68
+
69
+ invalidate_caches
136
70
  end
137
71
 
138
72
  # Removes the edge from the first vertex to the second vertex. If the
@@ -143,9 +77,26 @@ module Nanoc3
143
77
  #
144
78
  # @return [void]
145
79
  def remove_edge(from, to)
146
- from_index, to_index = indices_of(from, to)
147
- @matrix[from_index, to_index] = false
148
- invalidate_successor_predecessor_cache
80
+ @from_graph[from] ||= Set.new
81
+ @from_graph[from].delete(to)
82
+
83
+ @to_graph[to] ||= Set.new
84
+ @to_graph[to].delete(from)
85
+
86
+ invalidate_caches
87
+ end
88
+
89
+ # Deletes all edges going to the given vertex.
90
+ #
91
+ # @param to Vertex to which all edges should be removed
92
+ #
93
+ # @return [void]
94
+ def delete_edges_to(to)
95
+ @to_graph[to] ||= Set.new
96
+ @to_graph[to].each do |from|
97
+ @from_graph[from].delete(to)
98
+ end
99
+ @to_graph.delete(to)
149
100
  end
150
101
 
151
102
  # Returns the direct predecessors of the given vertex, i.e. the vertices
@@ -155,12 +106,7 @@ module Nanoc3
155
106
  #
156
107
  # @return [Array] Direct predecessors of the given vertex
157
108
  def direct_predecessors_of(to)
158
- @direct_predecessors[to] ||= begin
159
- @vertices.select do |from|
160
- from_index, to_index = indices_of(from, to)
161
- @matrix[from_index, to_index] == true
162
- end
163
- end
109
+ @to_graph[to].to_a
164
110
  end
165
111
 
166
112
  # Returns the direct successors of the given vertex, i.e. the vertices y
@@ -170,12 +116,7 @@ module Nanoc3
170
116
  #
171
117
  # @return [Array] Direct successors of the given vertex
172
118
  def direct_successors_of(from)
173
- @direct_successors[from] ||= begin
174
- @vertices.select do |to|
175
- from_index, to_index = indices_of(from, to)
176
- @matrix[from_index, to_index] == true
177
- end
178
- end
119
+ @from_graph[from].to_a
179
120
  end
180
121
 
181
122
  # Returns the predecessors of the given vertex, i.e. the vertices x for
@@ -204,59 +145,30 @@ module Nanoc3
204
145
  # @return [Array] The list of all edges in this graph.
205
146
  def edges
206
147
  result = []
207
-
208
- @vertices.each do |from|
209
- @vertices.each do |to|
210
- from_index, to_index = indices_of(from, to)
211
- next if @matrix[from_index, to_index] == false
212
-
213
- result << [ from_index, to_index ]
148
+ @vertices.each_with_index do |v, i|
149
+ direct_successors_of(v).map { |v2| @vertice_indexes[v2] }.each do |i2|
150
+ result << [ i, i2 ]
214
151
  end
215
152
  end
216
-
217
153
  result
218
154
  end
219
155
 
220
- # Returns a string representing this graph in ASCII art (or, to be more
221
- # precise, the string representation of the matrix backing this graph).
222
- #
223
- # @return [String] The string representation of this graph
224
- def to_s
225
- @matrix.to_s
226
- end
227
-
228
156
  private
229
157
 
230
- # Generates vertex-to-index mapping
231
- def generate_indices
232
- @indices = {}
233
- @vertices.each_with_index do |v, i|
234
- @indices[v] = i
235
- end
236
- end
237
-
238
- # Invalidates the cache that contains successors and predecessors (both
239
- # direct and indirect).
240
- def invalidate_successor_predecessor_cache
241
- @successors = {}
242
- @direct_successors = {}
243
- @predecessors = {}
244
- @direct_predecessors = {}
245
- end
246
-
247
- # Returns an array of indices for the given vertices. Raises an error if
248
- # one or more given objects are not vertices.
249
- def indices_of(*vertices)
250
- vertices.map { |v| @indices[v] or raise RuntimeError, "#{v.inspect} not a vertex" }
158
+ # Invalidates cached data. This method should be called when the internal
159
+ # graph representation is changed.
160
+ def invalidate_caches
161
+ @predecessors = {}
162
+ @successors = {}
251
163
  end
252
164
 
253
165
  # Recursively finds vertices, starting at the vertex start, using the
254
166
  # given method, which should be a symbol to a method that takes a vertex
255
167
  # and returns related vertices (e.g. predecessors, successors).
256
168
  def recursively_find_vertices(start, method)
257
- all_vertices = []
169
+ all_vertices = Set.new
258
170
 
259
- processed_vertices = []
171
+ processed_vertices = Set.new
260
172
  unprocessed_vertices = [ start ]
261
173
 
262
174
  until unprocessed_vertices.empty?
@@ -272,7 +184,7 @@ module Nanoc3
272
184
  end
273
185
  end
274
186
 
275
- all_vertices
187
+ all_vertices.to_a
276
188
  end
277
189
 
278
190
  end
@@ -16,7 +16,7 @@ module Nanoc3
16
16
  # @param [String] data_source_name The data source name for which no
17
17
  # data source could be found
18
18
  def initialize(data_source_name)
19
- super("The data source specified in the site's configuration file, #{data_source_name}, does not exist.")
19
+ super("The data source specified in the sites configuration file, “#{data_source_name}”, does not exist.".make_compatible_with_env)
20
20
  end
21
21
 
22
22
  end
@@ -28,7 +28,7 @@ module Nanoc3
28
28
  # @param [String] layout_identifier The layout identifier for which no
29
29
  # layout could be found
30
30
  def initialize(layout_identifier)
31
- super("The site does not have a layout with identifier '#{layout_identifier}'.")
31
+ super("The site does not have a layout with identifier “#{layout_identifier}”.".make_compatible_with_env)
32
32
  end
33
33
 
34
34
  end
@@ -40,7 +40,7 @@ module Nanoc3
40
40
  # @param [Symbol] filter_name The filter name for which no filter could
41
41
  # be found
42
42
  def initialize(filter_name)
43
- super("The requested filter, #{filter_name}, does not exist.")
43
+ super("The requested filter, “#{filter_name}”, does not exist.".make_compatible_with_env)
44
44
  end
45
45
 
46
46
  end
@@ -53,7 +53,7 @@ module Nanoc3
53
53
  # @param [String] layout_identifier The identifier of the layout for
54
54
  # which the filter could not be determined
55
55
  def initialize(layout_identifier)
56
- super("The filter to be used for the '#{layout_identifier}' could not be determined. Make sure the layout does have a filter.")
56
+ super("The filter to be used for the “#{layout_identifier} layout could not be determined. Make sure the layout does have a filter.".make_compatible_with_env)
57
57
  end
58
58
 
59
59
  end
@@ -70,7 +70,7 @@ module Nanoc3
70
70
  # example, if the given type is `"site"`, plural would be `false`; if
71
71
  # the given type is `"items"`, plural would be `true`.
72
72
  def initialize(type, plural)
73
- super("#{type} #{plural ? 'are' : 'is'} not available yet. You may be missing a Nanoc3::Site#load_data call.")
73
+ super("#{type} #{plural ? 'are' : 'is'} not available yet. You may be missing a Nanoc3::Site#load_data call.".make_compatible_with_env)
74
74
  end
75
75
 
76
76
  end
@@ -82,7 +82,7 @@ module Nanoc3
82
82
  # @param [Array<Nanoc3::ItemRep>] reps A list of item representations
83
83
  # that mutually depend on each other
84
84
  def initialize(reps)
85
- super("The site cannot be compiled because the following items mutually depend on each other: #{reps.inspect}.")
85
+ super("The site cannot be compiled because the following items mutually depend on each other: #{reps.inspect}.".make_compatible_with_env)
86
86
  end
87
87
 
88
88
  end
@@ -92,7 +92,7 @@ module Nanoc3
92
92
  class NoRulesFileFound < Generic
93
93
 
94
94
  def initialize
95
- super("This site does not have a rules file, which is required for nanoc sites.")
95
+ super("This site does not have a rules file, which is required for nanoc sites.".make_compatible_with_env)
96
96
  end
97
97
 
98
98
  end
@@ -104,7 +104,7 @@ module Nanoc3
104
104
  # @param [Nanoc3::Item] item The item for which no compilation rule
105
105
  # could be found
106
106
  def initialize(item)
107
- super("No compilation rules were found for the '#{item.identifier}' item.")
107
+ super("No compilation rules were found for the “#{item.identifier} item.".make_compatible_with_env)
108
108
  end
109
109
 
110
110
  end
@@ -116,7 +116,7 @@ module Nanoc3
116
116
  # @param [Nanoc3::Item] item The item for which no routing rule could be
117
117
  # found
118
118
  def initialize(rep)
119
- super("No routing rules were found for the '#{rep.item.identifier}' item (rep '#{rep.name}').")
119
+ super("No routing rules were found for the “#{rep.item.identifier} item (rep “#{rep.name}).".make_compatible_with_env)
120
120
  end
121
121
 
122
122
  end
@@ -133,7 +133,7 @@ module Nanoc3
133
133
  # compiled
134
134
  def initialize(rep)
135
135
  @rep = rep
136
- super("The '#{rep.item.identifier}' item (rep '#{rep.name}') cannot currently be compiled yet due to an unmet dependency.")
136
+ super("The “#{rep.item.identifier} item (rep “#{rep.name}) cannot currently be compiled yet due to an unmet dependency.".make_compatible_with_env)
137
137
  end
138
138
 
139
139
  end
@@ -144,7 +144,7 @@ module Nanoc3
144
144
  # @param [Nanoc3::ItemRep] The item representation that was attempted to
145
145
  # be laid out
146
146
  def initialize(rep)
147
- super("The '#{rep.item.identifier}' item (rep '#{rep.name}') cannot be laid out because it is a binary item.")
147
+ super("The {rep.item.identifier} item (rep “#{rep.name}) cannot be laid out because it is a binary item.".make_compatible_with_env)
148
148
  end
149
149
 
150
150
  end
@@ -158,7 +158,7 @@ module Nanoc3
158
158
  #
159
159
  # @param [Class] filter_class The filter class that was used
160
160
  def initialize(rep, filter_class)
161
- super("The “#{filter_class.inspect}” filter cannot be used to filter the “#{rep.item.identifier}” item (rep “#{rep.name}”), because textual filters cannot be used on binary items.")
161
+ super("The “#{filter_class.inspect}” filter cannot be used to filter the “#{rep.item.identifier}” item (rep “#{rep.name}”), because textual filters cannot be used on binary items.".make_compatible_with_env)
162
162
  end
163
163
 
164
164
  end
@@ -172,7 +172,7 @@ module Nanoc3
172
172
  #
173
173
  # @param [Class] filter_class The filter class that was used
174
174
  def initialize(rep, filter_class)
175
- super("The “#{filter_class.inspect}” filter cannot be used to filter the “#{rep.item.identifier}” item (rep “#{rep.name}”), because binary filters cannot be used on textual items.")
175
+ super("The “#{filter_class.inspect}” filter cannot be used to filter the “#{rep.item.identifier}” item (rep “#{rep.name}”), because binary filters cannot be used on textual items.".make_compatible_with_env)
176
176
  end
177
177
 
178
178
  end
@@ -94,7 +94,7 @@ module Nanoc3
94
94
  #
95
95
  # @param [String] content_or_filename The unprocessed content that should
96
96
  # be filtered (if the item is a textual item) or the path to the file that
97
- # should be fitlered (if the item is a binar item)
97
+ # should be fitlered (if the item is a binary item)
98
98
  #
99
99
  # @param [Hash] params A hash containing parameters. Filter subclasses can
100
100
  # use these parameters to allow modifying the filter's behaviour.
@@ -111,14 +111,16 @@ module Nanoc3
111
111
  # from which the compiled content should be fetched. By default, the
112
112
  # compiled content will be fetched from the default representation.
113
113
  #
114
- # @option params [String] :snapshot (:last) The name of the snapshot from
115
- # which to fetch the compiled content. By default, the fully compiled
116
- # content will be fetched, with all filters and layouts applied--not the
117
- # pre-layout content.
114
+ # @option params [String] :snapshot The name of the snapshot from which to
115
+ # fetch the compiled content. By default, the returned compiled content
116
+ # will be the content compiled right before the first layout call (if
117
+ # any).
118
118
  #
119
119
  # @return [String] The compiled content of the given rep (or the default
120
120
  # rep if no rep is specified) at the given snapshot (or the default
121
121
  # snapshot if no snapshot is specified)
122
+ #
123
+ # @see ItemRep#compiled_content
122
124
  def compiled_content(params={})
123
125
  # Get rep
124
126
  rep_name = params[:rep] || :default
@@ -192,6 +192,11 @@ module Nanoc3
192
192
  snapshot_name ||= :last
193
193
  end
194
194
 
195
+ # Check presence of snapshot
196
+ if @content[snapshot_name].nil?
197
+ warn "WARNING: The “#{self.item.identifier}” item (rep “#{self.name}”) does not have the requested snapshot named #{snapshot_name.inspect}.\n\n* Make sure that you are requesting the correct snapshot.\n* It is not possible to request the compiled content of a binary item representation; if this item is marked as binary even though you believe it should be textual, you may need to add the extension of this item to the site configuration’s `text_extensions` array.".make_compatible_with_env
198
+ end
199
+
195
200
  # Get content
196
201
  @content[snapshot_name]
197
202
  end
@@ -333,6 +338,9 @@ module Nanoc3
333
338
  File.open(self.raw_path, 'w') { |io| io.write(@content[:last]) }
334
339
  @written = true
335
340
 
341
+ # Generate diff
342
+ generate_diff
343
+
336
344
  # Check if file was modified
337
345
  @modified = File.read(self.raw_path) != @old_content
338
346
  end
@@ -346,20 +354,18 @@ module Nanoc3
346
354
  # content in `diff(1)` format, or nil if there is no previous compiled
347
355
  # content
348
356
  def diff
349
- # Check if content can be diffed
350
357
  # TODO allow binary diffs
351
- return nil if self.binary?
352
358
 
353
- # Check if old content exists
354
- if @old_content.nil? or self.raw_path.nil?
359
+ if self.binary?
355
360
  nil
356
361
  else
357
- diff_strings(@old_content, @content[:last])
362
+ @diff_thread.join if @diff_thread
363
+ @diff
358
364
  end
359
365
  end
360
366
 
361
367
  def inspect
362
- "<#{self.class}:0x#{self.object_id.to_s(16)} name=#{self.name} binary=#{self.binary?} item.identifier=#{self.item.identifier}>"
368
+ "<#{self.class}:0x#{self.object_id.to_s(16)} name=#{self.name} binary=#{self.binary?} raw_path=#{self.raw_path} item.identifier=#{self.item.identifier}>"
363
369
  end
364
370
 
365
371
  private
@@ -390,6 +396,18 @@ module Nanoc3
390
396
  [ filter, filter_name, filter_args ]
391
397
  end
392
398
 
399
+ def generate_diff
400
+ if @old_content.nil? or self.raw_path.nil?
401
+ @diff = nil
402
+ else
403
+ @diff_thread = Thread.new do
404
+ @diff = diff_strings(@old_content, @content[:last])
405
+ sleep 2
406
+ @diff_thread = nil
407
+ end
408
+ end
409
+ end
410
+
393
411
  def diff_strings(a, b)
394
412
  # TODO Rewrite this string-diffing method in pure Ruby. It should not
395
413
  # use the "diff" executable, because this will most likely not work on
@@ -406,9 +424,11 @@ module Nanoc3
406
424
  new_file.write(b)
407
425
 
408
426
  # Diff
409
- stdin, stdout, stderr = Open3.popen3('diff', '-u', old_file.path, new_file.path)
410
- result = stdout.read
411
- result == '' ? nil : result
427
+ cmd = [ 'diff', '-u', old_file.path, new_file.path ]
428
+ Open3.popen3(*cmd) do |stdin, stdout, stderr|
429
+ result = stdout.read
430
+ return (result == '' ? nil : result)
431
+ end
412
432
  end
413
433
  end
414
434
  rescue Errno::ENOENT
@@ -28,7 +28,7 @@ module Nanoc3
28
28
  # The default configuration for a data source. A data source's
29
29
  # configuration overrides these options.
30
30
  DEFAULT_DATA_SOURCE_CONFIG = {
31
- :type => 'filesystem_compact',
31
+ :type => 'filesystem_unified',
32
32
  :items_root => '/',
33
33
  :layouts_root => '/',
34
34
  :config => {}
@@ -161,8 +161,8 @@ module Nanoc3
161
161
  return if instance_variable_defined?(:@data_loaded) && @data_loaded && !force
162
162
 
163
163
  # Load all data
164
- data_sources.each { |ds| ds.use }
165
164
  load_code_snippets(force)
165
+ data_sources.each { |ds| ds.use }
166
166
  load_rules
167
167
  load_items
168
168
  load_layouts
@@ -230,7 +230,7 @@ module Nanoc3
230
230
  @code_snippets = Dir['lib/**/*.rb'].sort.map do |filename|
231
231
  Nanoc3::CodeSnippet.new(
232
232
  File.read(filename),
233
- filename.sub(/^lib\//, ''),
233
+ filename,
234
234
  File.stat(filename).mtime
235
235
  )
236
236
  end
@@ -252,7 +252,7 @@ module Nanoc3
252
252
  @rules_mtime = File.stat(rules_filename).mtime
253
253
 
254
254
  # Load DSL
255
- dsl.instance_eval(@rules)
255
+ dsl.instance_eval(@rules, "./#{rules_filename}")
256
256
  end
257
257
 
258
258
  # Loads this site’s items, sets up item child-parent relationships and
@@ -203,8 +203,10 @@ module Nanoc3::CLI
203
203
  def handle_option(option)
204
204
  case option
205
205
  when :version
206
+ gem_info = defined?(Gem) ? "with RubyGems #{Gem::VERSION}" : "without RubyGems"
207
+
206
208
  puts "nanoc #{Nanoc3::VERSION} (c) 2007-2010 Denis Defreyne."
207
- puts "Ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) running on #{RUBY_PLATFORM}"
209
+ puts "Ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) running on #{RUBY_PLATFORM} #{gem_info}"
208
210
  exit 0
209
211
  when :verbose
210
212
  Nanoc3::CLI::Logger.instance.level = :low
@@ -4,6 +4,56 @@ module Nanoc3::CLI::Commands
4
4
 
5
5
  class CreateSite < Cri::Command
6
6
 
7
+ class << self
8
+
9
+ protected
10
+
11
+ # Converts the given array to YAML format
12
+ def array_to_yaml(array)
13
+ '[ ' + array.map { |s| "'" + s + "'" }.join(', ') + ' ]'
14
+ end
15
+
16
+ end
17
+
18
+ DEFAULT_CONFIG = <<EOS
19
+ # A list of file extensions that nanoc will consider to be textual rather than
20
+ # binary. If an item with an extension not in this list is found, the file
21
+ # will be considered as binary.
22
+ text_extensions: #{array_to_yaml(Nanoc3::Site::DEFAULT_CONFIG[:text_extensions])}
23
+
24
+ # The path to the directory where all generated files will be written to. This
25
+ # can be an absolute path starting with a slash, but it can also be path
26
+ # relative to the site directory.
27
+ output_dir: #{Nanoc3::Site::DEFAULT_CONFIG[:output_dir]}
28
+
29
+ # A list of index filenames, i.e. names of files that will be served by a web
30
+ # server when a directory is requested. Usually, index files are named
31
+ # “index.hml”, but depending on the web server, this may be something else,
32
+ # such as “default.htm”. This list is used by nanoc to generate pretty URLs.
33
+ index_filenames: #{array_to_yaml(Nanoc3::Site::DEFAULT_CONFIG[:index_filenames])}
34
+
35
+ # The data sources where nanoc loads its data from. This is an array of
36
+ # hashes; each array element represents a single data source. By default,
37
+ # there is only a single data source that reads data from the “content/” and
38
+ # “layout/” directories in the site directory.
39
+ data_sources:
40
+ -
41
+ # The type is the identifier of the data source. By default, this will be
42
+ # `filesystem_unified`.
43
+ type: #{Nanoc3::Site::DEFAULT_DATA_SOURCE_CONFIG[:type]}
44
+
45
+ # The path where items should be mounted (comparable to mount points in
46
+ # Unix-like systems). This is “/” by default, meaning that items will have
47
+ # “/” prefixed to their identifiers. If the items root were “/en/”
48
+ # instead, an item at content/about.html would have an identifier of
49
+ # “/en/about/” instead of just “/about/”.
50
+ items_root: #{Nanoc3::Site::DEFAULT_DATA_SOURCE_CONFIG[:items_root]}
51
+
52
+ # The path where layouts should be mounted. The layouts root behaves the
53
+ # same as the items root, but applies to layouts rather than items.
54
+ layouts_root: #{Nanoc3::Site::DEFAULT_DATA_SOURCE_CONFIG[:layouts_root]}
55
+ EOS
56
+
7
57
  DEFAULT_RULES = <<EOS
8
58
  #!/usr/bin/env ruby
9
59
 
@@ -262,18 +312,7 @@ EOS
262
312
  FileUtils.mkdir_p('output')
263
313
 
264
314
  # Create config
265
- File.open('config.yaml', 'w') do |io|
266
- io.write(YAML.dump(
267
- 'output_dir' => 'output',
268
- 'data_sources' => [
269
- {
270
- 'type' => data_source,
271
- 'items_root' => '/',
272
- 'layouts_root' => '/'
273
- }
274
- ]
275
- ))
276
- end
315
+ File.open('config.yaml', 'w') { |io| io.write(DEFAULT_CONFIG.make_compatible_with_env) }
277
316
  Nanoc3::NotificationCenter.post(:file_created, 'config.yaml')
278
317
 
279
318
  # Create rakefile
@@ -284,7 +323,7 @@ EOS
284
323
 
285
324
  # Create rules
286
325
  File.open('Rules', 'w') do |io|
287
- io.write DEFAULT_RULES
326
+ io.write DEFAULT_RULES.make_compatible_with_env
288
327
  end
289
328
  Nanoc3::NotificationCenter.post(:file_created, 'Rules')
290
329
  end
@@ -310,14 +349,14 @@ EOS
310
349
 
311
350
  # Create home page
312
351
  data_source.create_item(
313
- DEFAULT_ITEM,
352
+ DEFAULT_ITEM.make_compatible_with_env,
314
353
  { :title => "Home" },
315
354
  '/'
316
355
  )
317
356
 
318
357
  # Create stylesheet
319
358
  data_source.create_item(
320
- DEFAULT_STYLESHEET,
359
+ DEFAULT_STYLESHEET.make_compatible_with_env,
321
360
  {},
322
361
  '/stylesheet/',
323
362
  :extension => '.css'
@@ -325,7 +364,7 @@ EOS
325
364
 
326
365
  # Create layout
327
366
  data_source.create_layout(
328
- DEFAULT_LAYOUT,
367
+ DEFAULT_LAYOUT.make_compatible_with_env,
329
368
  {},
330
369
  '/default/'
331
370
  )
@@ -42,27 +42,34 @@ module Nanoc3::CLI::Commands
42
42
  reps = items.map { |i| i.reps }.flatten
43
43
  layouts = @base.site.layouts
44
44
 
45
- # Calculate prettification data
46
- identifier_length = items.map { |i| i.identifier.size }.max
47
- rep_name_length = reps.map { |r| r.name.size }.max
45
+ # Get dependency tracker
46
+ # FIXME clean this up
47
+ dependency_tracker = @base.site.compiler.send(:dependency_tracker)
48
+ dependency_tracker.load_graph
48
49
 
49
50
  # Print items
50
51
  puts '=== Items'
51
52
  puts
52
53
  row = 0
53
54
  items.sort_by { |i| i.identifier }.each do |item|
55
+ puts "item #{item.identifier}:"
56
+
57
+ # Print item dependencies
58
+ puts " dependencies:"
59
+ predecessors = dependency_tracker.direct_predecessors_of(item).sort_by { |i| i.identifier }
60
+ predecessors.each do |pred|
61
+ puts " #{pred.identifier}"
62
+ end
63
+ puts " (nothing)" if predecessors.empty?
64
+
65
+ # Print item representations
66
+ puts " representations:"
54
67
  item.reps.sort_by { |r| r.name.to_s }.each do |rep|
55
- # Determine filler
56
- filler = (row % 3 == 0 ? '· ' : ' ')
57
- row += 1
58
-
59
- # Print rep
60
- puts "* %s %s -> %s" % [
61
- fill(item.identifier, identifier_length, filler),
62
- fill(rep.name.to_s, rep_name_length, ' '),
63
- rep.raw_path || '-'
64
- ]
68
+ puts " #{rep.name} -> #{rep.raw_path || '(not written)'}"
65
69
  end
70
+
71
+ # Done
72
+ puts
66
73
  end
67
74
  puts
68
75
 
@@ -70,31 +77,10 @@ module Nanoc3::CLI::Commands
70
77
  puts '=== Layouts'
71
78
  puts
72
79
  layouts.each do |layout|
73
- puts "* #{layout.identifier}"
80
+ puts "layout #{layout.identifier}"
74
81
  end
75
82
  end
76
83
 
77
- private
78
-
79
- # Returns a string that is exactly `length` long, starting with `text` and
80
- # filling up any unused space by repeating the string `filler`.
81
- def fill(text, length, filler)
82
- res = text.dup
83
-
84
- filler_length = (length - 1 - text.length)
85
- if filler_length >= 0
86
- # Append spacer to ensure alignment
87
- spacer_length = text.size % filler.length
88
- filler_length -= spacer_length
89
- res << ' ' * (spacer_length + 1)
90
-
91
- # Append leader
92
- res << filler*(filler_length/filler.length)
93
- end
94
-
95
- res
96
- end
97
-
98
84
  end
99
85
 
100
86
  end
@@ -43,23 +43,25 @@ module Nanoc3::Extra
43
43
  r.raw_path == site.config[:output_dir] + path
44
44
  end
45
45
 
46
- if rep
47
- serve(rep)
46
+ # Recompile rep
47
+ site.compiler.run(rep.item) if rep
48
+
49
+ # Get paths by appending index filenames
50
+ if path =~ /\/$/
51
+ possible_paths = site.config[:index_filenames].map { |f| path + f }
48
52
  else
49
- # Get paths by appending index filenames
50
- if path =~ /\/$/
51
- possible_paths = site.config[:index_filenames].map { |f| path + f }
52
- else
53
- possible_paths = [ path ]
54
- end
55
-
56
- # Find matching file
57
- modified_path = possible_paths.find { |f| File.file?(site.config[:output_dir] + f) }
58
- modified_path ||= path
59
-
60
- # Serve using Rack::File
61
- file_server.call(env.merge('PATH_INFO' => modified_path))
53
+ possible_paths = [ path ]
62
54
  end
55
+
56
+ # Find matching file
57
+ modified_path = possible_paths.find { |f| File.file?(site.config[:output_dir] + f) }
58
+ modified_path ||= path
59
+
60
+ # Serve using Rack::File
61
+ puts "*** serving file #{modified_path}"
62
+ res = file_server.call(env.merge('PATH_INFO' => modified_path))
63
+ puts "*** done serving file #{modified_path}"
64
+ res
63
65
  end
64
66
  rescue StandardError, ScriptError => e
65
67
  # Add compilation stack to env
@@ -92,18 +94,6 @@ module Nanoc3::Extra
92
94
  @file_server ||= ::Rack::File.new(site.config[:output_dir])
93
95
  end
94
96
 
95
- def serve(rep)
96
- # Recompile rep
97
- site.compiler.run(rep.item, :force => true)
98
-
99
- # Build response
100
- [
101
- 200,
102
- { 'Content-Type' => mime_type_of(rep.raw_path, 'text/html') },
103
- [ rep.content_at_snapshot(:last) ]
104
- ]
105
- end
106
-
107
97
  end
108
98
 
109
99
  end
@@ -3,6 +3,12 @@
3
3
  module Nanoc3::Filters
4
4
  class BlueCloth < Nanoc3::Filter
5
5
 
6
+ # Runs the content through [BlueCloth](http://deveiate.org/projects/BlueCloth).
7
+ # This method takes no options.
8
+ #
9
+ # @param [String] content The content to filter
10
+ #
11
+ # @return [String] The filtered content
6
12
  def run(content, params={})
7
13
  require 'bluecloth'
8
14
 
@@ -3,9 +3,13 @@
3
3
  module Nanoc3::Filters
4
4
  class CodeRay < Nanoc3::Filter
5
5
 
6
+ # @deprecated Use the `:colorize_syntax` filter instead.
6
7
  def run(content, params={})
7
8
  require 'coderay'
8
9
 
10
+ # Warn
11
+ warn 'The :coderay filter is deprecated; consider using the :colorize_syntax filter instead.'
12
+
9
13
  # Check params
10
14
  raise ArgumentError, "CodeRay filter requires a :language argument which is missing" if params[:language].nil?
11
15
 
@@ -3,8 +3,44 @@
3
3
  module Nanoc3::Filters
4
4
  class ColorizeSyntax < Nanoc3::Filter
5
5
 
6
+ # The default colorizer to use for a language if the colorizer for that
7
+ # language is not overridden.
6
8
  DEFAULT_COLORIZER = :coderay
7
9
 
10
+ # Syntax-highlights code blocks in the given content. Code blocks should
11
+ # be enclosed in `pre` elements that contain a `code` element. The code
12
+ # element should have a class starting with `language-` and followed by
13
+ # the programming language, as specified by HTML5.
14
+ #
15
+ # Options for individual colorizers will be taken from the {#run}
16
+ # options’ value for the given colorizer. For example, if the filter is
17
+ # invoked with a `:coderay => coderay_options_hash` option, the
18
+ # `coderay_options_hash` hash will be passed to the CodeRay colorizer.
19
+ #
20
+ # Currently, only the `:coderay` and `:pygmentize` colorizers are
21
+ # implemented. Additional colorizer implementations are welcome!
22
+ #
23
+ # @example Content that will be highlighted
24
+ #
25
+ # <pre><code class="language-ruby">
26
+ # def foo
27
+ # "asdf"
28
+ # end
29
+ # </code></pre>
30
+ #
31
+ # @example Invoking the filter with custom parameters
32
+ #
33
+ # filter :colorize_syntax,
34
+ # :colorizers => { :ruby => :coderay },
35
+ # :coderay => { :line_numbers => :list }
36
+ #
37
+ # @param [String] content The content to filter
38
+ #
39
+ # @option params [Hash] :colorizers (DEFAULT_COLORIZER) A hash containing
40
+ # a mapping of programming languages (symbols, not strings) onto
41
+ # colorizers (symbols).
42
+ #
43
+ # @return [String] The filtered content
8
44
  def run(content, params={})
9
45
  require 'nokogiri'
10
46
 
@@ -3,6 +3,12 @@
3
3
  module Nanoc3::Filters
4
4
  class ERB < Nanoc3::Filter
5
5
 
6
+ # Runs the content through [ERB](http://ruby-doc.org/stdlib/libdoc/erb/rdoc/classes/ERB.html).
7
+ # This method takes no options.
8
+ #
9
+ # @param [String] content The content to filter
10
+ #
11
+ # @return [String] The filtered content
6
12
  def run(content, params={})
7
13
  require 'erb'
8
14
 
@@ -3,6 +3,12 @@
3
3
  module Nanoc3::Filters
4
4
  class Erubis < Nanoc3::Filter
5
5
 
6
+ # Runs the content through [Erubis](http://www.kuwata-lab.com/erubis/).
7
+ # This method takes no options.
8
+ #
9
+ # @param [String] content The content to filter
10
+ #
11
+ # @return [String] The filtered content
6
12
  def run(content, params={})
7
13
  require 'erubis'
8
14
 
@@ -3,6 +3,12 @@
3
3
  module Nanoc3::Filters
4
4
  class Haml < Nanoc3::Filter
5
5
 
6
+ # Runs the content through [Haml](http://haml-lang.com/).
7
+ # Parameters passed to this filter will be passed on to Haml.
8
+ #
9
+ # @param [String] content The content to filter
10
+ #
11
+ # @return [String] The filtered content
6
12
  def run(content, params={})
7
13
  require 'haml'
8
14
 
@@ -3,6 +3,12 @@
3
3
  module Nanoc3::Filters
4
4
  class Kramdown < Nanoc3::Filter
5
5
 
6
+ # Runs the content through [Kramdown](http://kramdown.rubyforge.org/).
7
+ # Parameters passed to this filter will be passed on to Kramdown.
8
+ #
9
+ # @param [String] content The content to filter
10
+ #
11
+ # @return [String] The filtered content
6
12
  def run(content, params={})
7
13
  require 'kramdown'
8
14
 
@@ -3,6 +3,12 @@
3
3
  module Nanoc3::Filters
4
4
  class Less < Nanoc3::Filter
5
5
 
6
+ # Runs the content through [LESS](http://lesscss.org/).
7
+ # This method takes no options.
8
+ #
9
+ # @param [String] content The content to filter
10
+ #
11
+ # @return [String] The filtered content
6
12
  def run(content, params={})
7
13
  require 'less'
8
14
 
@@ -3,6 +3,12 @@
3
3
  module Nanoc3::Filters
4
4
  class Markaby < Nanoc3::Filter
5
5
 
6
+ # Runs the content through [Markaby](http://markaby.rubyforge.org/).
7
+ # This method takes no options.
8
+ #
9
+ # @param [String] content The content to filter
10
+ #
11
+ # @return [String] The filtered content
6
12
  def run(content, params={})
7
13
  require 'markaby'
8
14
 
@@ -3,6 +3,12 @@
3
3
  module Nanoc3::Filters
4
4
  class Maruku < Nanoc3::Filter
5
5
 
6
+ # Runs the content through [Maruku](http://maruku.rubyforge.org/).
7
+ # Parameters passed to this filter will be passed on to Maruku.
8
+ #
9
+ # @param [String] content The content to filter
10
+ #
11
+ # @return [String] The filtered content
6
12
  def run(content, params={})
7
13
  require 'maruku'
8
14
 
@@ -3,6 +3,12 @@
3
3
  module Nanoc3::Filters
4
4
  class Rainpress < Nanoc3::Filter
5
5
 
6
+ # Runs the content through [Rainpress](http://code.google.com/p/rainpress/).
7
+ # Parameters passed to this filter will be passed on to Rainpress.
8
+ #
9
+ # @param [String] content The content to filter
10
+ #
11
+ # @return [String] The filtered content
6
12
  def run(content, params={})
7
13
  require 'rainpress'
8
14
 
@@ -3,6 +3,12 @@
3
3
  module Nanoc3::Filters
4
4
  class RDiscount < Nanoc3::Filter
5
5
 
6
+ # Runs the content through [RDiscount](http://github.com/rtomayko/rdiscount).
7
+ # This method takes no options.
8
+ #
9
+ # @param [String] content The content to filter
10
+ #
11
+ # @return [String] The filtered content
6
12
  def run(content, params={})
7
13
  require 'rdiscount'
8
14
 
@@ -3,6 +3,12 @@
3
3
  module Nanoc3::Filters
4
4
  class RDoc < Nanoc3::Filter
5
5
 
6
+ # Runs the content through [RDoc::Markup](http://rdoc.rubyforge.org/RDoc/Markup.html).
7
+ # This method takes no options.
8
+ #
9
+ # @param [String] content The content to filter
10
+ #
11
+ # @return [String] The filtered content
6
12
  def run(content, params={})
7
13
  begin
8
14
  # new RDoc
@@ -3,6 +3,12 @@
3
3
  module Nanoc3::Filters
4
4
  class RedCloth < Nanoc3::Filter
5
5
 
6
+ # Runs the content through [RedCloth](http://redcloth.org/).
7
+ # This method takes no options.
8
+ #
9
+ # @param [String] content The content to filter
10
+ #
11
+ # @return [String] The filtered content
6
12
  def run(content, params={})
7
13
  require 'redcloth'
8
14
 
@@ -6,11 +6,23 @@ module Nanoc3::Filters
6
6
  require 'nanoc3/helpers/link_to'
7
7
  include Nanoc3::Helpers::LinkTo
8
8
 
9
+ # Relativizes all paths in the given content, which can be either HTML or
10
+ # CSS. This filter is quite useful if a site needs to be hosted in a
11
+ # subdirectory instead of a subdomain. In HTML, all `href` and `src`
12
+ # attributes will be relativized. In CSS, all `url()` references will be
13
+ # relativized.
14
+ #
15
+ # @param [String] content The content to filter
16
+ #
17
+ # @option params [Symbol] :type The type of content to filter; can be either `:html` or `:css`.
18
+ #
19
+ # @return [String] The filtered content
9
20
  def run(content, params={})
10
21
  # Set assigns so helper function can be used
11
22
  @item_rep = assigns[:item_rep] if @item_rep.nil?
12
23
 
13
24
  # Filter
25
+ # TODO use nokogiri or csspool instead of regular expressions
14
26
  case params[:type]
15
27
  when :html
16
28
  content.gsub(/(<[^>]+\s+(src|href))=(['"]?)(\/.*?)\3([ >])/) do
@@ -3,6 +3,12 @@
3
3
  module Nanoc3::Filters
4
4
  class RubyPants < Nanoc3::Filter
5
5
 
6
+ # Runs the content through [RubyPants](http://chneukirchen.org/blog/static/projects/rubypants.html).
7
+ # This method takes no options.
8
+ #
9
+ # @param [String] content The content to filter
10
+ #
11
+ # @return [String] The filtered content
6
12
  def run(content, params={})
7
13
  require 'rubypants'
8
14
 
@@ -3,6 +3,12 @@
3
3
  module Nanoc3::Filters
4
4
  class Sass < Nanoc3::Filter
5
5
 
6
+ # Runs the content through [Sass](http://sass-lang.com/).
7
+ # Parameters passed to this filter will be passed on to Sass.
8
+ #
9
+ # @param [String] content The content to filter
10
+ #
11
+ # @return [String] The filtered content
6
12
  def run(content, params={})
7
13
  require 'sass'
8
14
 
@@ -64,6 +64,11 @@ module Nanoc3::Helpers
64
64
  # non-outputted items in a feed; such items could have their custom feed
65
65
  # path set to the blog path instead, for example.
66
66
  #
67
+ # * `custom_url_in_feed` — The url that will be used instead of the
68
+ # normal url in the feed (generated from the site’s base url + the item
69
+ # rep’s path). This can be useful when building a link-blog where the
70
+ # URL of article is a remote location.
71
+ #
67
72
  # * `updated_at` — The time when the article was last modified. If this
68
73
  # attribute is not present, the `created_at` attribute will be used as
69
74
  # the time when the article was last modified.
@@ -255,12 +260,14 @@ module Nanoc3::Helpers
255
260
  raise RuntimeError.new('Cannot build Atom feed: site configuration has no base_url')
256
261
  end
257
262
 
258
- # Get path
259
- path = item[:custom_path_in_feed] || item.path
260
- return nil if path.nil?
261
-
262
263
  # Build URL
263
- @site.config[:base_url] + path
264
+ if item[:custom_url_in_feed]
265
+ item[:custom_url_in_feed]
266
+ elsif item[:custom_path_in_feed]
267
+ @site.config[:base_url] + item[:custom_path_in_feed]
268
+ elsif item.path
269
+ @site.config[:base_url] + item.path
270
+ end
264
271
  end
265
272
 
266
273
  # Returns the URL of the feed. It will return the custom feed URL if set,
@@ -15,17 +15,24 @@ module Nanoc3::Helpers
15
15
  # @return [Array] The breadcrumbs, starting with the root item and ending
16
16
  # with the item itself
17
17
  def breadcrumbs_trail
18
- trail = []
19
- current_identifier = @item.identifier
20
-
21
- loop do
22
- item = @items.find { |i| i.identifier == current_identifier }
23
- trail.unshift(item)
24
- break if current_identifier == '/'
25
- current_identifier = current_identifier.sub(/[^\/]+\/$/, '')
18
+ breadcrumbs_for_identifier(@item.identifier)
19
+ end
20
+
21
+ def item_with_identifier(identifier)
22
+ @identifier_cache ||= {}
23
+ @identifier_cache[identifier] ||= begin
24
+ @items.find { |i| i.identifier == identifier }
26
25
  end
26
+ end
27
27
 
28
- trail
28
+ def breadcrumbs_for_identifier(identifier)
29
+ @breadcrumbs_cache ||= {}
30
+ @breadcrumbs_cache[identifier] ||= begin
31
+ head = (identifier == '/' ? [] : breadcrumbs_for_identifier(identifier.sub(/[^\/]+\/$/, '')) )
32
+ tail = [ item_with_identifier(identifier) ]
33
+
34
+ head + tail
35
+ end
29
36
  end
30
37
 
31
38
  end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 3
7
7
  - 1
8
- - 0b1
9
- version: 3.1.0b1
8
+ - 0b2
9
+ version: 3.1.0b2
10
10
  platform: ruby
11
11
  authors:
12
12
  - Denis Defreyne
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-03-07 00:00:00 +01:00
17
+ date: 2010-03-22 00:00:00 +01:00
18
18
  default_executable: nanoc3
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency