gettalong-webgen 0.5.8.20090507 → 0.5.9.20090620

Sign up to get free protection for your applications and to get access to all the features.
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)