nanoc3 3.1.0b1 → 3.1.0b2

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.
@@ -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