gettalong-webgen 0.5.8.20090507 → 0.5.9.20090620

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. data/Rakefile +5 -6
  2. data/data/webgen/passive_sources/images/generated_by_webgen.png +0 -0
  3. data/data/webgen/passive_sources/images/webgen_logo.png +0 -0
  4. data/data/webgen/passive_sources/templates/atom_feed.template +38 -0
  5. data/data/webgen/passive_sources/templates/rss_feed.template +28 -0
  6. data/data/webgen/resources.yaml +2 -1
  7. data/doc/contentprocessor/builder.page +1 -1
  8. data/doc/contentprocessor/erb.page +5 -2
  9. data/doc/contentprocessor/erubis.page +2 -2
  10. data/doc/extensions.page +1 -1
  11. data/doc/manual.page +56 -26
  12. data/doc/reference_configuration.page +36 -1
  13. data/doc/reference_website_styles.page +1 -1
  14. data/doc/sourcehandler/feed.page +6 -11
  15. data/doc/tag/includefile.page +1 -1
  16. data/lib/webgen/cli/apply_command.rb +1 -1
  17. data/lib/webgen/cli/utils.rb +2 -2
  18. data/lib/webgen/common.rb +0 -9
  19. data/lib/webgen/contentprocessor/blocks.rb +60 -36
  20. data/lib/webgen/contentprocessor/builder.rb +2 -2
  21. data/lib/webgen/contentprocessor/erb.rb +3 -2
  22. data/lib/webgen/contentprocessor/erubis.rb +2 -2
  23. data/lib/webgen/contentprocessor/haml.rb +2 -2
  24. data/lib/webgen/contentprocessor/maruku.rb +1 -1
  25. data/lib/webgen/contentprocessor/sass.rb +2 -2
  26. data/lib/webgen/contentprocessor/tags.rb +25 -11
  27. data/lib/webgen/context.rb +4 -1
  28. data/lib/webgen/context/render.rb +32 -0
  29. data/lib/webgen/context/tags.rb +20 -0
  30. data/lib/webgen/default_config.rb +4 -1
  31. data/lib/webgen/deprecated.rb +37 -4
  32. data/lib/webgen/node.rb +37 -38
  33. data/lib/webgen/path.rb +151 -54
  34. data/lib/webgen/source.rb +6 -6
  35. data/lib/webgen/source/stacked.rb +13 -5
  36. data/lib/webgen/sourcehandler.rb +71 -45
  37. data/lib/webgen/sourcehandler/base.rb +51 -21
  38. data/lib/webgen/sourcehandler/copy.rb +4 -4
  39. data/lib/webgen/sourcehandler/directory.rb +3 -9
  40. data/lib/webgen/sourcehandler/feed.rb +23 -49
  41. data/lib/webgen/sourcehandler/fragment.rb +10 -8
  42. data/lib/webgen/sourcehandler/memory.rb +9 -10
  43. data/lib/webgen/sourcehandler/metainfo.rb +9 -9
  44. data/lib/webgen/sourcehandler/page.rb +5 -5
  45. data/lib/webgen/sourcehandler/sitemap.rb +3 -3
  46. data/lib/webgen/sourcehandler/template.rb +6 -6
  47. data/lib/webgen/sourcehandler/virtual.rb +19 -17
  48. data/lib/webgen/tag/base.rb +34 -26
  49. data/lib/webgen/tag/breadcrumbtrail.rb +3 -3
  50. data/lib/webgen/tag/executecommand.rb +3 -3
  51. data/lib/webgen/tag/langbar.rb +2 -2
  52. data/lib/webgen/tag/link.rb +3 -3
  53. data/lib/webgen/tag/menu.rb +2 -2
  54. data/lib/webgen/tag/metainfo.rb +1 -1
  55. data/lib/webgen/tag/relocatable.rb +17 -21
  56. data/lib/webgen/tag/tikz.rb +5 -6
  57. data/lib/webgen/tree.rb +7 -7
  58. data/lib/webgen/version.rb +1 -1
  59. data/lib/webgen/website.rb +4 -2
  60. data/misc/default.css +8 -2
  61. data/misc/default.template +2 -2
  62. data/misc/logo.svg +313 -0
  63. data/misc/style.page +1 -1
  64. data/test/helper.rb +2 -2
  65. data/test/test_common_sitemap.rb +1 -1
  66. data/test/test_contentprocessor_blocks.rb +12 -4
  67. data/test/test_contentprocessor_builder.rb +2 -1
  68. data/test/test_contentprocessor_erb.rb +2 -1
  69. data/test/test_contentprocessor_erubis.rb +1 -1
  70. data/test/test_contentprocessor_fragments.rb +12 -11
  71. data/test/test_contentprocessor_haml.rb +2 -1
  72. data/test/test_contentprocessor_maruku.rb +1 -0
  73. data/test/test_contentprocessor_rdiscount.rb +1 -0
  74. data/test/test_contentprocessor_rdoc.rb +1 -0
  75. data/test/test_contentprocessor_sass.rb +1 -0
  76. data/test/test_contentprocessor_tags.rb +13 -0
  77. data/test/test_context.rb +28 -0
  78. data/test/test_node.rb +40 -20
  79. data/test/test_path.rb +106 -65
  80. data/test/test_source_filesystem.rb +1 -1
  81. data/test/test_source_stacked.rb +19 -6
  82. data/test/test_sourcehandler_base.rb +53 -47
  83. data/test/test_sourcehandler_copy.rb +6 -6
  84. data/test/test_sourcehandler_directory.rb +8 -12
  85. data/test/test_sourcehandler_feed.rb +10 -6
  86. data/test/test_sourcehandler_fragment.rb +6 -5
  87. data/test/test_sourcehandler_main.rb +39 -0
  88. data/test/test_sourcehandler_memory.rb +4 -4
  89. data/test/test_sourcehandler_metainfo.rb +10 -10
  90. data/test/test_sourcehandler_page.rb +9 -9
  91. data/test/test_sourcehandler_sitemap.rb +4 -4
  92. data/test/test_sourcehandler_template.rb +14 -14
  93. data/test/test_sourcehandler_virtual.rb +9 -5
  94. data/test/test_tag_base.rb +2 -2
  95. data/test/test_tag_executecommand.rb +1 -1
  96. data/test/test_tag_link.rb +4 -3
  97. data/test/test_tag_menu.rb +15 -15
  98. data/test/test_tag_metainfo.rb +1 -0
  99. data/test/test_tag_relocatable.rb +2 -1
  100. data/test/test_tag_tikz.rb +3 -3
  101. data/test/test_tree.rb +8 -8
  102. data/test/test_website.rb +15 -0
  103. metadata +14 -14
  104. data/test/test_common.rb +0 -18
data/lib/webgen/path.rb CHANGED
@@ -1,11 +1,32 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
 
3
+ require 'pathname'
3
4
  require 'webgen/languages'
4
5
 
5
6
  module Webgen
6
7
 
7
- # A path object provides information about a specific path as well as methods for accessing its
8
- # content.
8
+ # == General Information
9
+ #
10
+ # A Path object provides information about a path that is used to create a node as well as methods
11
+ # for accessing its content. In contrast, output paths are always strings and just specify the
12
+ # location where a specific node should be written to.
13
+ #
14
+ # Note the +path+ and +source_path+ attributes of a Path object:
15
+ #
16
+ # * The +source_path+ specifies a path string that was directly created by a Source object. Each
17
+ # Path object must have such a valid source path sothat webgen can infer the Path the lead to
18
+ # the creation of a Node object later.
19
+ #
20
+ # * In contrast, the +path+ attribute specifies the path that is used to create the canonical name
21
+ # (and by default the output path) of a Node object. Normally it is the same as the
22
+ # +source_path+ but can differ (e.g. when fragment nodes are created for page file nodes).
23
+ #
24
+ # A Path object can represent one of three different things: a directory, a file or a fragment. If
25
+ # the +path+ ends with a slash character, then the path object represents a directory, if the path
26
+ # contains a hash character anywhere, then the path object represents a fragment and else it
27
+ # represents a file. Have a look at the webgen manual to see the exact format of a path!
28
+ #
29
+ # == Relation to Source classes
9
30
  #
10
31
  # A webgen source class needs to derive a specialized path class from this class and implement an
11
32
  # approriate #changed? method that returns +true+ if the path's content has changed since the last
@@ -13,15 +34,21 @@ module Webgen
13
34
  class Path
14
35
 
15
36
  # Helper class for easy access to the content of a path.
37
+ #
38
+ # This class is used sothat the creation of the real IO object for #stream can be delayed till
39
+ # it is actually needed. This is done by not directly requiring the user of this class to supply
40
+ # the IO object, but by requiring a block that creates the real IO object.
16
41
  class SourceIO
17
42
 
18
- # Create a new SourceIO object. A block has to be specified that returns an IO object.
43
+ # Create a new SourceIO object. A block has to be specified that returns the to-be-wrapped IO
44
+ # object.
19
45
  def initialize(&block)
20
46
  @block = block
21
- raise ArgumentError, 'Need to provide a block which returns an IO object' if @block.nil?
47
+ raise ArgumentError, 'You need to provide a block which returns an IO object' if @block.nil?
22
48
  end
23
49
 
24
- # Provide direct access to the wrapped IO object.
50
+ # Provide direct access to the wrapped IO object by yielding it. After the method block
51
+ # returns the IO object is automatically closed.
25
52
  def stream
26
53
  io = @block.call
27
54
  yield(io)
@@ -29,7 +56,7 @@ module Webgen
29
56
  io.close
30
57
  end
31
58
 
32
- # Return the content of the wrapped IO object as string.
59
+ # Return the whole content of the wrapped IO object as string.
33
60
  def data
34
61
  stream {|io| io.read}
35
62
  end
@@ -37,6 +64,13 @@ module Webgen
37
64
  end
38
65
 
39
66
 
67
+ # Make the given +path+ absolute by prepending the absolute directory path +base+ if necessary.
68
+ # Also resolves all '..' and '.' references in +path+.
69
+ def self.make_absolute(base, path)
70
+ raise(ArgumentError, 'base has to be an absolute path, ie. needs to start with a slash') unless base =~ /\//
71
+ Pathname.new(path =~ /^\// ? path : File.join(base, path)).cleanpath.to_s
72
+ end
73
+
40
74
  # Return +true+ if the given +path+ matches the given +pattern+ (trailing slashes of directories
41
75
  # are not respected). For information on which patterns are supported, have a look at the
42
76
  # documentation of File.fnmatch.
@@ -49,66 +83,86 @@ module Webgen
49
83
 
50
84
  include Comparable
51
85
 
52
- # The full path.
53
- attr_accessor :path
86
+ # The full path for which this Path object was created.
87
+ attr_reader :path
54
88
 
55
- # The source path that lead to the creation of this path.
56
- attr_accessor :source_path
89
+ # A string specifying the path that lead to the creation of this path.
90
+ attr_reader :source_path
57
91
 
58
- # The basename part of the path.
59
- attr_accessor :basename
92
+ # The string specifying the parent path
93
+ attr_reader :parent_path
60
94
 
61
- # The directory part of the path.
62
- attr_accessor :directory
63
-
64
- # The canonical name without the extension.
65
- attr_accessor :cnbase
95
+ # The canonical name of the path without the extension.
96
+ attr_accessor :basename
66
97
 
67
- # The extension.
98
+ # The extension of the +path+.
68
99
  attr_accessor :ext
69
100
 
70
101
  # Extracted meta information for the path.
71
102
  attr_accessor :meta_info
72
103
 
104
+ # Specifies whether this path should be used during the "tree update" phase of a webgen run or
105
+ # only later during node resolution.
106
+ attr_writer :passive
107
+
108
+ # Is this path only used later during node resolution? Defaults to +false+, i.e. used during the
109
+ # "tree update" phase.
110
+ def passive?; @passive; end
111
+
112
+
73
113
  # Create a new Path object for +path+. The optional +source_path+ parameter specifies the path
74
- # that lead to the creation of this path. The optional block needs to return an IO object for
75
- # the content of the path.
114
+ # string that lead to the creation of this path. The optional block needs to return an IO object
115
+ # for getting the content of the path.
116
+ #
117
+ # The +path+ needs to be in a well defined format which can be looked up in the webgen manual.
76
118
  def initialize(path, source_path = path, &ioblock)
77
119
  @meta_info = {}
78
120
  @io = block_given? ? SourceIO.new(&ioblock) : nil
79
121
  @source_path = source_path
122
+ @passive = false
80
123
  analyse(path)
81
124
  end
82
125
 
83
- # Mount this path at the mount point +mp+ optionally stripping +prefix+ from the path and return
84
- # the new path object.
126
+ # Mount this path at the mount point +mp+, optionally stripping +prefix+ from the parent path,
127
+ # and return the new path object.
128
+ #
129
+ # The parameters +mp+ and +prefix+ have to be absolute directory paths, ie. they have to start
130
+ # and end with a slash and must not contain any hash characters!
131
+ #
132
+ #--
133
+ # Can't use self.class.new(...) here because the semantics of the sub constructors is not know
134
+ #++
85
135
  def mount_at(mp, prefix = nil)
136
+ raise(ArgumentError, "The mount point (#{mp}) must be a valid directory path") if mp =~ /^[^\/]|#|[^\/]$/
137
+ raise(ArgumentError, "The strip prefix (#{prefix}) must be a valid directory path") if !prefix.nil? && prefix =~ /^[^\/]|#|[^\/]$/
138
+
86
139
  temp = dup
87
- temp.path = temp.path.sub(/^#{Regexp.escape(prefix.chomp("/"))}/, '') if prefix #"
88
- reanalyse = (@path == '/' || temp.path == '/')
89
- temp.path = File.join(mp, temp.path)
90
- temp.source_path = temp.path if @path == @source_path
140
+ strip_re = /^#{Regexp.escape(prefix.to_s)}/
141
+ temp.instance_variable_set(:@path, temp.path.sub(strip_re, ''))
142
+ reanalyse = (@path == '/' || temp.path == '')
143
+ temp.instance_variable_set(:@path, File.join(mp, temp.path))
144
+ temp.instance_variable_set(:@source_path, temp.path) if @path == @source_path
91
145
  if reanalyse
92
146
  temp.send(:analyse, temp.path)
93
147
  else
94
- temp.directory = File.join(File.dirname(temp.path), '/')
148
+ temp.instance_variable_set(:@parent_path, File.join(mp, temp.parent_path.sub(strip_re, '')))
95
149
  end
96
150
  temp
97
151
  end
98
152
 
99
- # Has the content of this path changed since the last webgen run? This default implementation
100
- # always returns +true+, a specialized sub class needs to override this behaviour!
101
- def changed?
102
- true
103
- end
104
-
105
153
  # Duplicate the path object.
106
154
  def dup
107
155
  temp = super
108
- temp.meta_info = @meta_info.dup
156
+ temp.instance_variable_set(:@meta_info, @meta_info.dup)
109
157
  temp
110
158
  end
111
159
 
160
+ # Has the content of this path changed since the last webgen run? This default implementation
161
+ # always returns +true+, a specialized sub class needs to override this behaviour!
162
+ def changed?
163
+ true
164
+ end
165
+
112
166
  # The SourceIO object associated with the path.
113
167
  def io
114
168
  if @io
@@ -118,26 +172,45 @@ module Webgen
118
172
  end
119
173
  end
120
174
 
121
- # The canonical name created from the filename (created from cnbase and extension).
175
+ # The canonical name created from the +path+ (namely from the parts +basename+ and +extension+).
122
176
  def cn
123
- @cnbase + (@ext.length > 0 ? '.' + @ext : '')
177
+ @basename + (@ext.length > 0 ? '.' + @ext : '') + (@basename != '/' && @path =~ /.\/$/ ? '/' : '')
124
178
  end
125
179
 
126
- # Utility method for creating the lcn from +cn+ and the language +lang+.
180
+ # Utility method for creating the lcn from the +cn+ and the language +lang+.
127
181
  def self.lcn(cn, lang)
128
182
  if lang.nil?
129
183
  cn
130
184
  else
131
- cn.split('.').insert(1, lang.to_s).join('.')
185
+ cn.split('.').insert((cn =~ /^\./ ? 2 : 1), lang.to_s).join('.')
132
186
  end
133
187
  end
134
188
 
135
- # The localized canonical name created from the filename.
189
+ # The localized canonical name created from the +path+.
136
190
  def lcn
137
191
  self.class.lcn(cn, @meta_info['lang'])
138
192
  end
139
193
 
140
- # Compare this object to another Path or a String.
194
+ # The absolute canonical name of this path.
195
+ def acn
196
+ if @path =~ /#/
197
+ self.class.new(@parent_path).acn + cn
198
+ else
199
+ @parent_path + cn
200
+ end
201
+ end
202
+
203
+ # The absolute localized canonical name of this path.
204
+ def alcn
205
+ if @path =~ /#/
206
+ self.class.new(@parent_path).alcn + lcn
207
+ else
208
+ @parent_path + lcn
209
+ end
210
+ end
211
+
212
+ # Equality -- Return +true+ if +other+ is a Path object with the same #path or if +other+ is a
213
+ # String equal to the #path. Else return +false+.
141
214
  def ==(other)
142
215
  if other.kind_of?(Path)
143
216
  other.path == @path
@@ -149,13 +222,12 @@ module Webgen
149
222
  end
150
223
  alias_method(:eql?, :==)
151
224
 
152
- # Implemented sothat a Path looks like a String when used as key in a hash.
225
+ # Compare the #path of this object to <tt>other.path</tt>
153
226
  def <=>(other)
154
- @path <=> other.to_str
227
+ @path <=> other.path
155
228
  end
156
229
 
157
- # Implemented sothat a Path looks like a String when used as key in a hash.
158
- def hash
230
+ def hash #:nodoc:
159
231
  @path.hash
160
232
  end
161
233
 
@@ -172,21 +244,46 @@ module Webgen
172
244
  private
173
245
  #######
174
246
 
175
- FILENAME_RE = /^(?:(\d+)\.)?([^.]*?)(?:\.(\w\w\w?)(?=.))?(?:\.(.*))?$/
176
-
177
247
  # Analyse the +path+ and fill the object with the extracted information.
178
248
  def analyse(path)
179
249
  @path = path
180
- @basename = File.basename(path)
181
- @directory = File.join(File.dirname(path), '/')
182
- matchData = FILENAME_RE.match(@basename)
250
+ if @path =~ /#/
251
+ analyse_fragment
252
+ elsif @path =~ /\/$/
253
+ analyse_directory
254
+ else
255
+ analyse_file
256
+ end
257
+ @meta_info['title'] = @basename.tr('_-', ' ').capitalize
258
+ @ext ||= ''
259
+ raise "The basename of a path may not be empty: #{@path}" if @basename.empty? || @basename == '#'
260
+ raise "The parent path must start with a slash: #{@path}" if @path !~ /^\// && @path != '/'
261
+ end
183
262
 
184
- @meta_info['sort_info'] = (matchData[1].nil? ? nil : matchData[1].to_i)
185
- @cnbase = matchData[2]
186
- @meta_info['lang'] = Webgen::LanguageManager.language_for_code(matchData[3])
187
- @ext = (@meta_info['lang'].nil? && !matchData[3].nil? ? matchData[3].to_s + '.' : '') + matchData[4].to_s
263
+ # Analyse the path assuming it is a directory.
264
+ def analyse_directory
265
+ @parent_path = (@path == '/' ? '' : File.join(File.dirname(@path), '/'))
266
+ @basename = File.basename(@path)
267
+ end
268
+
269
+ FILENAME_RE = /^(?:(\d+)\.)?(\.?[^.]*?)(?:\.(\w\w\w?)(?=\.))?(?:\.(.*))?$/
270
+
271
+ # Analyse the path assuming it is a file.
272
+ def analyse_file
273
+ @parent_path = File.join(File.dirname(@path), '/')
274
+ match_data = FILENAME_RE.match(File.basename(@path))
275
+
276
+ @meta_info['sort_info'] = (match_data[1].nil? ? nil : match_data[1].to_i)
277
+ @basename = match_data[2]
278
+ @meta_info['lang'] = Webgen::LanguageManager.language_for_code(match_data[3])
279
+ @ext = (@meta_info['lang'].nil? && !match_data[3].nil? ? match_data[3].to_s : '') + match_data[4].to_s
280
+ end
188
281
 
189
- @meta_info['title'] = @cnbase.tr('_-', ' ').capitalize
282
+ # Analyse the path assuming it is a fragment.
283
+ def analyse_fragment
284
+ @parent_path, @basename = @path.scan(/^(.*?)(#.*?)$/).first
285
+ raise "The parent path of a fragment path must be a file path and not a directory path: #{@path}" if @parent_path =~ /\/$/
286
+ raise "A fragment path must only contain one hash character: #{path}" if @path.count("#") > 1
190
287
  end
191
288
 
192
289
  end
data/lib/webgen/source.rb CHANGED
@@ -10,9 +10,10 @@ module Webgen
10
10
  #
11
11
  # A source class only needs to respond to the method +paths+ which needs to return a set of paths
12
12
  # for the source. The returned paths must respond to the method <tt>changed?</tt> (has to return
13
- # +true+ if the paths has changed since the last webgen run). The default implementation in the
14
- # Path class just returns +true+. One can either derive a specialized path class or define
15
- # singleton methods on each path object.
13
+ # +true+ if the paths has changed since the last webgen run). If a path represents a directory, it
14
+ # needs to have a trailing slash! The default implementation in the Path class just returns
15
+ # +true+. One can either derive a specialized path class or define singleton methods on each path
16
+ # object.
16
17
  #
17
18
  # == Sample Source Class
18
19
  #
@@ -36,10 +37,9 @@ module Webgen
36
37
  # end
37
38
  #
38
39
  # You can use this source class in your website (after placing the code in, for example,
39
- # <tt>ext/init.rb</tt>) by updating the <tt>sources</tt> configuration option (the following code
40
- # has to be placed after the definition of the +MemorySource+ class):
40
+ # <tt>ext/init.rb</tt>) by updating the <tt>sources</tt> configuration option:
41
41
  #
42
- # WebsiteAccess.website.config['sources'] << ['/', MemorySource]
42
+ # WebsiteAccess.website.config['sources'] << ['/', 'MemorySource']
43
43
  #
44
44
  module Source
45
45
 
@@ -17,20 +17,27 @@ module Webgen::Source
17
17
  # source, it is discarded and not used.
18
18
  class Stacked
19
19
 
20
- # Return the stack of Webgen::Source objects.
20
+ # Return the stack of mount point to Webgen::Source object maps.
21
21
  attr_reader :stack
22
22
 
23
+ # Specifies whether the result of #paths calls should be cached (default: +false+). If caching
24
+ # is activated, new maps cannot be added to the stacked source anymore!
25
+ attr_accessor :cache_paths
26
+
23
27
  # Create a new stack. The optional +map+ parameter can be used to provide initial mappings of
24
- # mount points to source objects (see #add for details).
25
- def initialize(map = {})
28
+ # mount points to source objects (see #add for details). You cannot add other maps after a call
29
+ # to #paths if +cache_paths+ is +true+
30
+ def initialize(map = {}, cache_paths = false)
26
31
  @stack = []
32
+ @cache_paths = cache_paths
27
33
  add(map)
28
34
  end
29
35
 
30
36
  # Add all mappings found in +maps+ to the stack. The parameter +maps+ should be an array of
31
- # two-element arrays which contain an absolute directoriy (ie. starting with a slash) and a
32
- # source object.
37
+ # two-element arrays which contain an absolute directory (ie. starting and ending with a slash)
38
+ # and a source object.
33
39
  def add(maps)
40
+ raise "Cannot add new maps since caching is activated for this source" if defined?(@paths) && @cache_paths
34
41
  maps.each do |mp, source|
35
42
  raise "Invalid mount point specified: #{mp}" unless mp =~ /^\//
36
43
  @stack << [mp, source]
@@ -41,6 +48,7 @@ module Webgen::Source
41
48
  # returned by later source objects are not used if a prior source object has returned the same
42
49
  # path.
43
50
  def paths
51
+ return @paths if defined?(@paths) && @cache_paths
44
52
  @paths = Set.new
45
53
  @stack.each do |mp, source|
46
54
  source.paths.each do |path|
@@ -28,6 +28,7 @@ module Webgen
28
28
  # * collects all source paths using the source classes
29
29
  # * creates nodes using the source handler classes
30
30
  # * writes changed nodes out using an output class
31
+ # * deletes old nodes
31
32
  class Main
32
33
 
33
34
  include WebsiteAccess
@@ -35,34 +36,40 @@ module Webgen
35
36
 
36
37
  def initialize #:nodoc:
37
38
  website.blackboard.add_service(:create_nodes, method(:create_nodes))
39
+ website.blackboard.add_service(:create_nodes_from_paths, method(:create_nodes_from_paths))
38
40
  website.blackboard.add_service(:source_paths, method(:find_all_source_paths))
39
41
  website.blackboard.add_listener(:node_meta_info_changed?, method(:meta_info_changed?))
42
+
43
+ website.blackboard.add_listener(:before_node_deleted) do |node|
44
+ website.blackboard.invoke(:output_instance).delete(node.path)
45
+ end if website.config['output.do_deletion']
40
46
  end
41
47
 
42
- # Render the nodes provided in the +tree+. Before the actual rendering is done, the sources
43
- # are checked (nodes for deleted sources are deleted, nodes for new and changed sources).
44
- def render(tree)
48
+ # Render the current website. Before the actual rendering is done, the sources are checked for
49
+ # changes, i.e. nodes for deleted sources are deleted, nodes for new and changed sources are
50
+ # updated.
51
+ def render
45
52
  begin
46
53
  website.logger.mark_new_cycle if website.logger
47
54
 
48
55
  puts "Updating tree..."
49
56
  time = Benchmark.measure do
50
57
  website.cache.reset_volatile_cache
51
- update_tree(tree)
58
+ update_tree
52
59
  end
53
60
  puts "...done in " + ('%2.4f' % time.real) + ' seconds'
54
61
 
55
- if !tree.root
62
+ if !website.tree.root
56
63
  puts 'No source files found - maybe not a webgen website?'
57
64
  return nil
58
65
  end
59
66
 
60
67
  puts "Writing changed nodes..."
61
68
  time = Benchmark.measure do
62
- write_tree(tree)
69
+ write_tree
63
70
  end
64
71
  puts "...done in " + ('%2.4f' % time.real) + ' seconds'
65
- end while tree.node_access[:alcn].any? {|name,node| node.flagged?(:created) || node.flagged?(:reinit)}
72
+ end while website.tree.node_access[:alcn].any? {|name,node| node.flagged?(:created) || node.flagged?(:reinit)}
66
73
  :success
67
74
  end
68
75
 
@@ -70,45 +77,60 @@ module Webgen
70
77
  private
71
78
  #######
72
79
 
73
- # Update the +tree+ by creating/reinitializing all needed nodes.
74
- def update_tree(tree)
80
+ # Update the <tt>website.tree</tt> by creating/reinitializing all needed nodes.
81
+ def update_tree
75
82
  unused_paths = Set.new
83
+ referenced_nodes = Set.new
84
+ all_but_passive_paths = Set.new(find_all_source_paths.select {|name, path| !path.passive?}.collect {|name, path| name})
76
85
  begin
77
- used_paths = Set.new(find_all_source_paths.keys) - unused_paths
86
+ used_paths = all_but_passive_paths - unused_paths
78
87
  paths_to_use = Set.new
79
88
  nodes_to_delete = Set.new
89
+ passive_nodes = Set.new
80
90
 
81
- tree.node_access[:alcn].each do |alcn, node|
82
- next if node == tree.dummy_root
91
+ website.tree.node_access[:alcn].each do |alcn, node|
92
+ next if node == website.tree.dummy_root
83
93
  used_paths.delete(node.node_info[:src])
84
94
 
85
- deleted = !find_all_source_paths.include?(node.node_info[:src])
86
- if deleted
95
+ src_path = find_all_source_paths[node.node_info[:src]]
96
+ if !src_path
87
97
  nodes_to_delete << node
88
- #TODO: delete output path
89
- elsif (!node.flagged?(:created) && find_all_source_paths[node.node_info[:src]].changed?) || node.meta_info_changed?
98
+ elsif (!node.flagged?(:created) && src_path.changed?) || node.meta_info_changed?
90
99
  node.flag(:reinit)
91
100
  paths_to_use << node.node_info[:src]
92
101
  elsif node.changed?
93
- # nothing to be done here
102
+ # nothing to be done here but method node.changed? has to be called
103
+ end
104
+
105
+ if src_path && src_path.passive?
106
+ passive_nodes << node
107
+ elsif src_path
108
+ referenced_nodes += node.node_info[:used_meta_info_nodes] + node.node_info[:used_nodes]
94
109
  end
95
110
  end
96
111
 
97
- nodes_to_delete.each {|node| tree.delete_node(node)}
112
+ # add unused passive nodes to node_to_delete set
113
+ unreferenced_passive_nodes, other_passive_nodes = passive_nodes.partition do |pnode|
114
+ !referenced_nodes.include?(pnode.alcn)
115
+ end
116
+ refs = other_passive_nodes.collect {|n| (n.node_info[:used_meta_info_nodes] + n.node_info[:used_nodes]).to_a}.flatten
117
+ unreferenced_passive_nodes.each {|n| nodes_to_delete << n if !refs.include?(n.alcn)}
118
+
119
+ nodes_to_delete.each {|node| website.tree.delete_node(node)}
98
120
  used_paths.merge(paths_to_use)
99
- paths = create_nodes_from_paths(tree, used_paths.to_a.sort)
121
+ paths = create_nodes_from_paths(used_paths.to_a.sort)
100
122
  unused_paths.merge(used_paths - paths)
101
- tree.node_access[:alcn].each {|name, node| tree.delete_node(node) if node.flagged?(:reinit)}
123
+ website.tree.node_access[:alcn].each {|name, node| website.tree.delete_node(node) if node.flagged?(:reinit)}
102
124
  website.cache.reset_volatile_cache
103
125
  end until used_paths.empty?
104
126
  end
105
127
 
106
- # Write out all changed nodes of the +tree+.
107
- def write_tree(tree)
128
+ # Write out all changed nodes of the <tt>website.tree</tt>.
129
+ def write_tree
108
130
  output = website.blackboard.invoke(:output_instance)
109
131
 
110
- tree.node_access[:alcn].select do |name, node|
111
- use_node = (node != tree.dummy_root && node.flagged?(:dirty))
132
+ website.tree.node_access[:alcn].select do |name, node|
133
+ use_node = (node != website.tree.dummy_root && node.flagged?(:dirty))
112
134
  node.unflag(:dirty_meta_info)
113
135
  node.unflag(:created)
114
136
  node.unflag(:dirty)
@@ -127,7 +149,7 @@ module Webgen
127
149
  end
128
150
  output.write(node.path, content, type)
129
151
  rescue
130
- raise RuntimeError, "Error while processing <#{node.absolute_lcn}>: #{$!.message}", $!.backtrace
152
+ raise RuntimeError, "Error while processing <#{node.alcn}>: #{$!.message}", $!.backtrace
131
153
  end
132
154
  end
133
155
  end
@@ -135,9 +157,15 @@ module Webgen
135
157
  # Return a hash with all source paths.
136
158
  def find_all_source_paths
137
159
  if !defined?(@paths)
138
- source = Webgen::Source::Stacked.new(website.config['sources'].collect do |mp, name, *args|
139
- [mp, constant(name).new(*args)]
140
- end)
160
+ active_source = Webgen::Source::Stacked.new(website.config['sources'].collect do |mp, name, *args|
161
+ [mp, constant(name).new(*args)]
162
+ end)
163
+ passive_source = Webgen::Source::Stacked.new(website.config['passive_sources'].collect do |mp, name, *args|
164
+ [mp, constant(name).new(*args)]
165
+ end, true)
166
+ passive_source.paths.each {|path| path.passive = true }
167
+ source = Webgen::Source::Stacked.new([['/', active_source], ['/', passive_source]])
168
+
141
169
  @paths = {}
142
170
  source.paths.each do |path|
143
171
  if !(website.config['sourcehandler.ignore'].any? {|pat| File.fnmatch(pat, path, File::FNM_CASEFOLD|File::FNM_DOTMATCH)})
@@ -155,13 +183,13 @@ module Webgen
155
183
 
156
184
  options = (website.config['sourcehandler.casefold'] ? File::FNM_CASEFOLD : 0) |
157
185
  (website.config['sourcehandler.use_hidden_files'] ? File::FNM_DOTMATCH : 0)
158
- find_all_source_paths.values_at(*paths).select do |path|
186
+ find_all_source_paths.values_at(*paths).compact.select do |path|
159
187
  patterns.any? {|pat| File.fnmatch(pat, path, options)}
160
188
  end
161
189
  end
162
190
 
163
- # Use the source handlers to create nodes for the +paths+ in the +tree+.
164
- def create_nodes_from_paths(tree, paths)
191
+ # Use the source handlers to create nodes for the +paths+ in the <tt>website.tree</tt>.
192
+ def create_nodes_from_paths(paths)
165
193
  used_paths = Set.new
166
194
  website.config['sourcehandler.invoke'].sort.each do |priority, shns|
167
195
  shns.each do |shn|
@@ -169,31 +197,29 @@ module Webgen
169
197
  handler_paths = paths_for_handler(shn, paths)
170
198
  used_paths.merge(handler_paths)
171
199
  handler_paths.sort {|a,b| a.path.length <=> b.path.length}.each do |path|
172
- parent_dir = path.directory.split('/').collect {|p| Path.new(p).cn}.join('/')
173
- parent_dir += '/' if path != '/' && parent_dir == ''
174
- create_nodes(tree, parent_dir, path, sh)
200
+ if !website.tree[path.parent_path]
201
+ used_paths.merge(create_nodes_from_paths([path.parent_path]))
202
+ end
203
+ create_nodes(path, sh)
175
204
  end
176
205
  end
177
206
  end
178
207
  used_paths
179
208
  end
180
209
 
181
- # Prepare everything to create nodes under the absolute lcn path +parent_path_name+ in the
182
- # +tree from the +path+ using the +source_handler+. If a block is given, the actual creation
183
- # of the nodes is deferred to it. After the nodes are created, it is also checked if they have
184
- # all needed properties.
185
- def create_nodes(tree, parent_path_name, path, source_handler) #:yields: parent, path
186
- if !(parent = tree[parent_path_name])
187
- raise "The specified parent path <#{parent_path_name}> does not exist"
188
- end
210
+ # Prepare everything to create from the +path+ using the +source_handler+. If a block is
211
+ # given, the actual creation of the nodes is deferred to it. Otherwise the #create_node method
212
+ # of the +source_handler+ is used. After the nodes are created, it is also checked if they
213
+ # have all needed properties.
214
+ def create_nodes(path, source_handler) #:yields: path
189
215
  path = path.dup
190
216
  path.meta_info = default_meta_info(path, source_handler.class.name)
191
217
  (website.cache[:sourcehandler_path_mi] ||= {})[[path.path, source_handler.class.name]] = path.meta_info.dup
192
- website.blackboard.dispatch_msg(:before_node_created, parent, path)
218
+ website.blackboard.dispatch_msg(:before_node_created, path)
193
219
  *nodes = if block_given?
194
- yield(parent, path)
220
+ yield(path)
195
221
  else
196
- source_handler.create_node(parent, path)
222
+ source_handler.create_node(path)
197
223
  end
198
224
  nodes.flatten.compact.each do |node|
199
225
  website.blackboard.dispatch_msg(:after_node_created, node)