webgen 0.5.8 → 0.5.9

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 (142) hide show
  1. data/COPYING +4 -0
  2. data/ChangeLog +1037 -0
  3. data/Rakefile +5 -6
  4. data/THANKS +1 -0
  5. data/VERSION +1 -1
  6. data/bin/webgen +1 -1
  7. data/data/webgen/passive_sources/images/generated_by_webgen.png +0 -0
  8. data/data/webgen/passive_sources/images/webgen_logo.png +0 -0
  9. data/data/webgen/passive_sources/stylesheets/coderay-default.css +129 -0
  10. data/data/webgen/passive_sources/templates/atom_feed.template +38 -0
  11. data/data/webgen/passive_sources/templates/rss_feed.template +28 -0
  12. data/data/webgen/passive_sources/templates/sitemap.template +21 -0
  13. data/data/webgen/resources.yaml +2 -1
  14. data/data/webgen/website_skeleton/Rakefile +5 -1
  15. data/doc/contentprocessor/builder.page +2 -2
  16. data/doc/contentprocessor/erb.page +5 -2
  17. data/doc/contentprocessor/erubis.page +2 -2
  18. data/doc/contentprocessor/head.page +21 -0
  19. data/doc/contentprocessor/tidy.page +14 -0
  20. data/doc/extensions.page +1 -1
  21. data/doc/faq.page +2 -2
  22. data/doc/manual.page +108 -43
  23. data/doc/reference_configuration.page +83 -5
  24. data/doc/reference_metainfo.page +24 -4
  25. data/doc/reference_website_styles.page +2 -2
  26. data/doc/sourcehandler/feed.page +11 -13
  27. data/doc/sourcehandler/metainfo.page +10 -3
  28. data/doc/sourcehandler/page.page +4 -4
  29. data/doc/sourcehandler/sitemap.page +8 -7
  30. data/doc/tag/coderay.page +6 -2
  31. data/doc/tag/includefile.page +1 -1
  32. data/doc/tag/menu.page +3 -0
  33. data/lib/webgen/cli/apply_command.rb +1 -1
  34. data/lib/webgen/cli/utils.rb +2 -2
  35. data/lib/webgen/common.rb +0 -9
  36. data/lib/webgen/contentprocessor.rb +18 -3
  37. data/lib/webgen/contentprocessor/blocks.rb +67 -36
  38. data/lib/webgen/contentprocessor/builder.rb +5 -2
  39. data/lib/webgen/contentprocessor/erb.rb +4 -2
  40. data/lib/webgen/contentprocessor/erubis.rb +5 -2
  41. data/lib/webgen/contentprocessor/haml.rb +6 -2
  42. data/lib/webgen/contentprocessor/head.rb +64 -0
  43. data/lib/webgen/contentprocessor/maruku.rb +3 -1
  44. data/lib/webgen/contentprocessor/rdiscount.rb +2 -0
  45. data/lib/webgen/contentprocessor/rdoc.rb +2 -0
  46. data/lib/webgen/contentprocessor/redcloth.rb +2 -0
  47. data/lib/webgen/contentprocessor/sass.rb +5 -3
  48. data/lib/webgen/contentprocessor/tags.rb +40 -24
  49. data/lib/webgen/contentprocessor/tidy.rb +38 -0
  50. data/lib/webgen/context.rb +13 -4
  51. data/lib/webgen/context/render.rb +32 -0
  52. data/lib/webgen/context/tags.rb +20 -0
  53. data/lib/webgen/default_config.rb +15 -4
  54. data/lib/webgen/deprecated.rb +38 -4
  55. data/lib/webgen/error.rb +135 -0
  56. data/lib/webgen/node.rb +48 -40
  57. data/lib/webgen/output.rb +5 -3
  58. data/lib/webgen/output/filesystem.rb +4 -4
  59. data/lib/webgen/page.rb +4 -4
  60. data/lib/webgen/path.rb +161 -58
  61. data/lib/webgen/source.rb +9 -6
  62. data/lib/webgen/source/filesystem.rb +1 -1
  63. data/lib/webgen/source/stacked.rb +13 -5
  64. data/lib/webgen/source/tararchive.rb +6 -2
  65. data/lib/webgen/sourcehandler.rb +100 -54
  66. data/lib/webgen/sourcehandler/base.rb +58 -24
  67. data/lib/webgen/sourcehandler/copy.rb +6 -5
  68. data/lib/webgen/sourcehandler/directory.rb +3 -9
  69. data/lib/webgen/sourcehandler/feed.rb +25 -50
  70. data/lib/webgen/sourcehandler/fragment.rb +10 -8
  71. data/lib/webgen/sourcehandler/memory.rb +9 -10
  72. data/lib/webgen/sourcehandler/metainfo.rb +9 -9
  73. data/lib/webgen/sourcehandler/page.rb +6 -5
  74. data/lib/webgen/sourcehandler/sitemap.rb +22 -22
  75. data/lib/webgen/sourcehandler/template.rb +6 -6
  76. data/lib/webgen/sourcehandler/virtual.rb +19 -17
  77. data/lib/webgen/tag/base.rb +27 -27
  78. data/lib/webgen/tag/breadcrumbtrail.rb +3 -3
  79. data/lib/webgen/tag/coderay.rb +19 -8
  80. data/lib/webgen/tag/executecommand.rb +4 -3
  81. data/lib/webgen/tag/langbar.rb +2 -2
  82. data/lib/webgen/tag/link.rb +8 -7
  83. data/lib/webgen/tag/menu.rb +2 -2
  84. data/lib/webgen/tag/metainfo.rb +1 -1
  85. data/lib/webgen/tag/relocatable.rb +17 -21
  86. data/lib/webgen/tag/tikz.rb +7 -10
  87. data/lib/webgen/tree.rb +7 -7
  88. data/lib/webgen/version.rb +1 -1
  89. data/lib/webgen/website.rb +32 -2
  90. data/misc/default.css +8 -2
  91. data/misc/default.template +2 -2
  92. data/misc/logo.svg +313 -0
  93. data/misc/style.page +1 -1
  94. data/test/helper.rb +18 -2
  95. data/test/test_cli.rb +104 -0
  96. data/test/test_common_sitemap.rb +1 -1
  97. data/test/test_contentprocessor.rb +8 -2
  98. data/test/test_contentprocessor_blocks.rb +17 -8
  99. data/test/test_contentprocessor_builder.rb +13 -2
  100. data/test/test_contentprocessor_erb.rb +9 -3
  101. data/test/test_contentprocessor_erubis.rb +9 -3
  102. data/test/test_contentprocessor_fragments.rb +12 -11
  103. data/test/test_contentprocessor_haml.rb +11 -2
  104. data/test/test_contentprocessor_head.rb +44 -0
  105. data/test/test_contentprocessor_maruku.rb +5 -1
  106. data/test/test_contentprocessor_rdiscount.rb +4 -0
  107. data/test/test_contentprocessor_rdoc.rb +4 -0
  108. data/test/test_contentprocessor_redcloth.rb +5 -1
  109. data/test/test_contentprocessor_sass.rb +8 -2
  110. data/test/test_contentprocessor_tags.rb +22 -7
  111. data/test/test_contentprocessor_tidy.rb +34 -0
  112. data/test/test_context.rb +39 -0
  113. data/test/test_error.rb +85 -0
  114. data/test/test_node.rb +57 -21
  115. data/test/test_page.rb +23 -5
  116. data/test/test_path.rb +120 -64
  117. data/test/test_source_filesystem.rb +1 -1
  118. data/test/test_source_stacked.rb +19 -6
  119. data/test/test_sourcehandler_base.rb +63 -50
  120. data/test/test_sourcehandler_copy.rb +6 -6
  121. data/test/test_sourcehandler_directory.rb +8 -12
  122. data/test/test_sourcehandler_feed.rb +15 -7
  123. data/test/test_sourcehandler_fragment.rb +6 -5
  124. data/test/test_sourcehandler_main.rb +39 -0
  125. data/test/test_sourcehandler_memory.rb +4 -4
  126. data/test/test_sourcehandler_metainfo.rb +20 -11
  127. data/test/test_sourcehandler_page.rb +10 -10
  128. data/test/test_sourcehandler_sitemap.rb +24 -5
  129. data/test/test_sourcehandler_template.rb +18 -15
  130. data/test/test_sourcehandler_virtual.rb +9 -5
  131. data/test/test_tag_base.rb +6 -29
  132. data/test/test_tag_coderay.rb +16 -3
  133. data/test/test_tag_executecommand.rb +2 -2
  134. data/test/test_tag_link.rb +5 -4
  135. data/test/test_tag_menu.rb +15 -15
  136. data/test/test_tag_metainfo.rb +1 -0
  137. data/test/test_tag_relocatable.rb +3 -2
  138. data/test/test_tag_tikz.rb +5 -5
  139. data/test/test_tree.rb +8 -8
  140. data/test/test_website.rb +15 -0
  141. metadata +21 -14
  142. data/test/test_common.rb +0 -18
@@ -1,5 +1,7 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  require 'webgen/context/nodes'
3
+ require 'webgen/context/tags'
4
+ require 'webgen/context/render'
3
5
 
4
6
  module Webgen
5
7
 
@@ -13,17 +15,23 @@ module Webgen
13
15
  # The content string that should be processed.
14
16
  #
15
17
  # [<tt>:processors</tt>]
16
- # Normally an AccessHash object providing access to all available content processors.
18
+ # Normally an ContentProcessor::AccessHash object providing access to all available content
19
+ # processors.
17
20
  #
18
21
  # [<tt>:chain</tt>]
19
22
  # The chain of nodes that is processed. There are some utiltity methods for getting
20
23
  # special nodes of the chain (see #ref_node, #content_node and #dest_node).
24
+ #
25
+ # The +persistent+ options hash is shared by all cloned Context objects.
21
26
  class Context
22
27
 
23
28
  include Webgen::WebsiteAccess
24
29
  public :website
25
30
 
26
- # Processing options
31
+ # The persistent options. Once initialized, all cloned objects refer to the same hash.
32
+ attr_reader :persistent
33
+
34
+ # Processing options.
27
35
  attr_accessor :options
28
36
 
29
37
  # Create a new Context object. You can use the +options+ hash to set needed options.
@@ -35,17 +43,18 @@ module Webgen
35
43
  #
36
44
  # [<tt>:processors</tt>]
37
45
  # Is set to a new AccessHash.
38
- def initialize(options = {})
46
+ def initialize(options = {}, persistent = {})
39
47
  @options = {
40
48
  :content => '',
41
49
  :processors => Webgen::ContentProcessor::AccessHash.new
42
50
  }.merge(options)
51
+ @persistent = persistent
43
52
  end
44
53
 
45
54
  # Create a copy of the current object. You can use the +options+ parameter to override options
46
55
  # of the current Context object in the newly created Context object.
47
56
  def clone(options = {})
48
- self.class.new(@options.merge(options))
57
+ self.class.new(@options.merge(options), @persistent)
49
58
  end
50
59
 
51
60
  # Return the value of the option +name+.
@@ -0,0 +1,32 @@
1
+ module Webgen
2
+
3
+ class Context
4
+
5
+ # Render the named block and return the result.
6
+ #
7
+ # call-seq:
8
+ # context.render_block(block_name)<br />
9
+ # context.render_block(:name => block_name, :option => value, ...)
10
+ #
11
+ # This method uses the functionality of the content processor +blocks+ for doing the actual
12
+ # work, so you may also want to look at Webgen::ContentProcessor::Blocks#render_block. You can
13
+ # call this method in two ways:
14
+ #
15
+ # [<tt>#render_block(block_name)</tt>]
16
+ # Renders the block named +block_name+ of the next node in the current node chain. This is the
17
+ # version that most want to use since it is equivalent to the use of <tt><webgen:block
18
+ # name="block_name" /></tt>. It is equivalent to <tt>#render_block(:name =>
19
+ # block_name)</tt>.
20
+ #
21
+ # [<tt>#render_block(opts_hash)</tt>]
22
+ # This version allows the same level of control over the output as the blocks content
23
+ # processor. For a list of valid options have a look at the documentation of the
24
+ # Webgen::ContentProcessor::Blocks#render_block method!
25
+ def render_block(name_or_hash)
26
+ name_or_hash = {:name => name_or_hash} if name_or_hash.kind_of?(String)
27
+ website.cache.instance('Webgen::ContentProcessor::Blocks').render_block(self, name_or_hash)
28
+ end
29
+
30
+ end
31
+
32
+ end
@@ -0,0 +1,20 @@
1
+ module Webgen
2
+
3
+ class Context
4
+
5
+ # Returns the result of evaluating the webgen tag +name+ with the tag parameters +params+ and
6
+ # the +body+ in the current context.
7
+ #
8
+ # Have a look at Webgen::Tag::Base for more information about webgen tags!
9
+ #
10
+ # This method is useful when you want to have the functionality of webgen tags available but you
11
+ # don't want to use the content processor for them. Or, for example, if the used markup language
12
+ # uses a similar markup as webgen tags do and therefore you can't use the normal webgen tags
13
+ # content processor.
14
+ def tag(name, params = {}, body = '')
15
+ website.cache.instance('Webgen::ContentProcessor::Tags').process_tag(name, params, body, self)
16
+ end
17
+
18
+ end
19
+
20
+ end
@@ -43,6 +43,7 @@ end
43
43
 
44
44
  # All things regarding sources
45
45
  config.sources [['/', "Webgen::Source::FileSystem", 'src']], :doc => 'One or more sources from which files are read, relative to website directory'
46
+ config.passive_sources([['/', "Webgen::Source::Resource", "webgen-passive-sources"]], :doc => 'One or more sources for delayed node creation on node resolution')
46
47
 
47
48
  # All things regarding source handler
48
49
  config.sourcehandler.patterns({
@@ -70,7 +71,7 @@ config.sourcehandler.default_lang_in_output_path(false, :doc => 'Specifies wheth
70
71
  config.sourcehandler.default_meta_info({
71
72
  :all => {
72
73
  'output_path' => 'standard',
73
- 'output_path_style' => [:parent, :cnbase, ['.', :lang], :ext]
74
+ 'output_path_style' => [:parent, :basename, ['.', :lang], :ext]
74
75
  },
75
76
  'Webgen::SourceHandler::Copy' => {
76
77
  'kind' => 'asset'
@@ -88,19 +89,21 @@ config.sourcehandler.default_meta_info({
88
89
  'kind' => 'fragment'
89
90
  },
90
91
  'Webgen::SourceHandler::Template' => {
91
- 'blocks' => {'default' => {'pipeline' => 'erb,tags,blocks'}}
92
+ 'blocks' => {'default' => {'pipeline' => 'erb,tags,blocks,head'}}
92
93
  },
93
94
  'Webgen::SourceHandler::Metainfo' => {
94
95
  'blocks' => {1 => {'name' => 'paths'}, 2 => {'name' => 'alcn'}}
95
96
  },
96
97
  'Webgen::SourceHandler::Feed' => {
97
98
  'rss' => true,
98
- 'atom' => true
99
+ 'atom' => true,
100
+ 'blocks' => {'default' => {'pipeline' => 'erb'}}
99
101
  },
100
102
  'Webgen::SourceHandler::Sitemap' => {
101
103
  'default_priority' => 0.5,
102
104
  'default_change_freq' => 'weekly',
103
- 'common.sitemap.any_lang' => true
105
+ 'common.sitemap.any_lang' => true,
106
+ 'blocks' => {'default' => {'pipeline' => 'erb'}}
104
107
  }
105
108
  }, :doc => "Default meta information for all nodes and for nodes belonging to a specific source handler")
106
109
 
@@ -114,6 +117,8 @@ website.autoload_service(:parse_html_headers, 'Webgen::SourceHandler::Fragment')
114
117
 
115
118
  # All things regarding output
116
119
  config.output ["Webgen::Output::FileSystem", 'out'], :doc => 'The class which is used to output the generated paths.'
120
+ config.output.do_deletion(false, :doc => 'Specifies whether the generated output paths should be deleted once the sources are deleted')
121
+
117
122
 
118
123
  Webgen::WebsiteAccess.website.blackboard.add_service(:output_instance, Webgen::Output.method(:instance))
119
124
 
@@ -134,10 +139,13 @@ config.contentprocessor.map({
134
139
  'erubis' => 'Webgen::ContentProcessor::Erubis',
135
140
  'rdiscount' => 'Webgen::ContentProcessor::RDiscount',
136
141
  'fragments' => 'Webgen::ContentProcessor::Fragments',
142
+ 'head' => 'Webgen::ContentProcessor::Head',
143
+ 'tidy' => 'Webgen::ContentProcessor::Tidy'
137
144
  }, :doc => 'Content processor name to class map')
138
145
 
139
146
  Webgen::WebsiteAccess.website.blackboard.add_service(:content_processor_names, Webgen::ContentProcessor.method(:list))
140
147
  Webgen::WebsiteAccess.website.blackboard.add_service(:content_processor, Webgen::ContentProcessor.method(:for_name))
148
+ Webgen::WebsiteAccess.website.blackboard.add_service(:content_processor_binary?, Webgen::ContentProcessor.method(:is_binary?))
141
149
 
142
150
  # All things regarding tags
143
151
  config.contentprocessor.tags.prefix('', :doc => 'The prefix used for tag names to avoid name clashes when another content processor uses similar markup.')
@@ -161,6 +169,8 @@ config.contentprocessor.erubis.options({}, :doc => 'A hash of additional options
161
169
 
162
170
  config.contentprocessor.redcloth.hard_breaks(false, :doc => 'Specifies whether new lines are turned into hard breaks')
163
171
 
172
+ config.contentprocessor.tidy.options("-raw", :doc => "The options passed to the tidy command")
173
+
164
174
  config.tag.relocatable.path(nil, :doc => 'The path which should be made relocatable', :mandatory => 'default')
165
175
 
166
176
  config.tag.menu.start_level(1, :doc => 'The level at which the menu starts.')
@@ -196,6 +206,7 @@ config.tag.coderay.line_numbers(true, :doc => 'Show line numbers')
196
206
  config.tag.coderay.line_number_start(1, :doc => 'Line number of first line')
197
207
  config.tag.coderay.bold_every(10, :doc => 'The interval at which the line number appears bold')
198
208
  config.tag.coderay.tab_width(8, :doc => 'Number of spaces used for a tabulator')
209
+ config.tag.coderay.css(:style, :doc => 'Specifies how the highlighted code should be styled')
199
210
 
200
211
  config.tag.date.format('%Y-%m-%d %H:%M:%S', :doc => 'The format of the date (same options as Ruby\'s Time#strftime)')
201
212
 
@@ -3,18 +3,42 @@ module Webgen
3
3
  class Node
4
4
 
5
5
  def flagged(key)
6
- warn("Deprecation warning: this method will be removed in one of the next releases - use Node#flagged? instead!")
6
+ warn("Deprecation warning (~ #{caller.first}): this method will be removed in one of the next releases - use Node#flagged? instead!")
7
7
  flagged?(key)
8
8
  end
9
9
 
10
+ def absolute_cn
11
+ warn("Deprecation warning (~ #{caller.first}): this method will be removed in one of the next releases - use Node#acn instead!")
12
+ acn
13
+ end
14
+
15
+ def absolute_lcn
16
+ warn("Deprecation warning (~ #{caller.first}): this method will be removed in one of the next releases - use Node#alcn instead!")
17
+ alcn
18
+ end
19
+
20
+ end
21
+
22
+ class Path
23
+
24
+ def cnbase
25
+ warn("Deprecation warning (~ #{caller.first}): this method will be removed in one of the next releases - use Path#basename instead!")
26
+ @basename
27
+ end
28
+
29
+ def cnbase=(value)
30
+ warn("Deprecation warning (~ #{caller.first}): this method will be removed in one of the next releases - use Path#basename= instead!")
31
+ basename = value
32
+ end
33
+
10
34
  end
11
35
 
12
36
  def self.const_missing(const)
13
37
  if const.to_s == 'Block'
14
- warn("Deprecation warning: Webgen::Block name will be removed in one of the next releases - use Webgen::Page::Block instead!")
38
+ warn("Deprecation warning (~ #{caller.first}): Webgen::Block name will be removed in one of the next releases - use Webgen::Page::Block instead!")
15
39
  Webgen::Page::Block
16
40
  elsif const.to_s == "WebgenPageFormatError"
17
- warn("Deprecation warning: Webgen::WebgenPageFormatError name will be removed in one of the next releases - use Webgen::Page::FormatError instead!")
41
+ warn("Deprecation warning (~ #{caller.first}): Webgen::WebgenPageFormatError name will be removed in one of the next releases - use Webgen::Page::FormatError instead!")
18
42
  Webgen::Page::FormatError
19
43
  else
20
44
  super
@@ -25,7 +49,7 @@ module Webgen
25
49
 
26
50
  def self.const_missing(const)
27
51
  if const.to_s == 'Context'
28
- warn("Deprecation warning: Webgen::ContentProcessor::Context is now named Webgen::Context! This alias will be removed in one of the next releases.")
52
+ warn("Deprecation warning (~ #{caller.first}): Webgen::ContentProcessor::Context is now named Webgen::Context! This alias will be removed in one of the next releases.")
29
53
  Webgen::Context
30
54
  else
31
55
  super
@@ -39,6 +63,7 @@ module Webgen
39
63
  klass.instance_methods.select {|m| m.to_s !~ /^(__|instance_eval|object_id)/}.each {|m| klass.__send__(:undef_method, m)}
40
64
  result = klass.new
41
65
  result.instance_eval { @old, @new, @obj = old, new, obj }
66
+ def result.inspect; end
42
67
  def result.method_missing(sym, *args, &block)
43
68
  Kernel::warn("Deprecation warning (~ #{caller.first}): The alias '#{@old}' will be removed in one of the next releases - use '#{@new}' instead!")
44
69
  @obj.send(sym, *args, &block)
@@ -50,4 +75,13 @@ module Webgen
50
75
 
51
76
  end
52
77
 
78
+ module Common
79
+
80
+ def self.absolute_path(path, base)
81
+ warn("Deprecation warning (~ #{caller.first}): this method will be removed in one of the next releases - use Webgen::Path.make_absolute(base, path) instead!")
82
+ Path.make_absolute(base, path)
83
+ end
84
+
85
+ end
86
+
53
87
  end
@@ -0,0 +1,135 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module Webgen
4
+
5
+ # Custom webgen error.
6
+ class Error < StandardError
7
+
8
+ # The name of the class where the error happened.
9
+ attr_reader :class_name
10
+
11
+ # This is either the source path or the node alcn which is responsible for the error.
12
+ attr_accessor :alcn
13
+
14
+ # The plain error message.
15
+ attr_reader :plain_message
16
+
17
+ # Create a new Error using the provided values.
18
+ #
19
+ # If +msg_or_error+ is a String, it is treated as the error message. If it is an exception, the
20
+ # exception is wrapped.
21
+ def initialize(msg_or_error, class_name = nil, alcn = nil)
22
+ if msg_or_error.kind_of?(String)
23
+ super(msg_or_error)
24
+ @plain_message = msg_or_error
25
+ else
26
+ super(msg_or_error.message)
27
+ set_backtrace(msg_or_error.backtrace)
28
+ @plain_message = msg_or_error.message
29
+ end
30
+ @class_name, @alcn = class_name, alcn
31
+ end
32
+
33
+ def message # :nodoc:
34
+ msg = 'Error while working'
35
+ msg += (@alcn ? " on <#{@alcn}>" : '')
36
+ msg += " with #{@class_name}" if @class_name
37
+ msg + ":\n " + plain_message
38
+ end
39
+
40
+ end
41
+
42
+ # This error is raised when an error condition occurs during the creation of a node.
43
+ class NodeCreationError < Error
44
+
45
+ def message # :nodoc:
46
+ msg = 'Error while creating a node'
47
+ msg += (@alcn ? " from <#{@alcn}>" : '')
48
+ msg += " with #{@class_name}" if @class_name
49
+ msg + ":\n " + plain_message
50
+ end
51
+
52
+ end
53
+
54
+
55
+ # This error is raised when an error condition occurs during rendering of a node.
56
+ class RenderError < Error
57
+
58
+ # The alcn of the file where the error happened. This can be different from #alcn (e.g. a page
59
+ # file is rendered but the error happens in a used template).
60
+ attr_accessor :error_alcn
61
+
62
+ # The line number in the +error_alcn+ where the errror happened.
63
+ attr_accessor :line
64
+
65
+ # Create a new RenderError using the provided values.
66
+ #
67
+ # If +msg_or_error+ is a String, it is treated as the error message. If it is an exception, the
68
+ # exception is wrapped.
69
+ def initialize(msg_or_error, class_name = nil, alcn = nil, error_alcn = nil, line = nil)
70
+ super(msg_or_error, class_name, alcn)
71
+ @error_alcn, @line = error_alcn, line
72
+ end
73
+
74
+ def message # :nodoc:
75
+ msg = 'Error '
76
+ if @error_alcn
77
+ msg += "in <#{@error_alcn}"
78
+ msg += ":~#{@line}" if @line
79
+ msg += "> "
80
+ end
81
+ msg += 'while rendering '
82
+ msg += (@alcn ? "<#{@alcn}>" : 'the website')
83
+ msg += " with #{@class_name}" if @class_name
84
+ msg + ":\n " + plain_message
85
+ end
86
+
87
+ end
88
+
89
+
90
+ # This error is raised when a needed library is not found.
91
+ class LoadError < Error
92
+
93
+ # The name of the library that is missing.
94
+ attr_reader :library
95
+
96
+ # The name of the Rubygem that provides the missing library.
97
+ attr_reader :gem
98
+
99
+ # Create a new LoadError using the provided values.
100
+ #
101
+ # If +library_or_error+ is a String, it is treated as the missing library name and an approriate
102
+ # error message is created. If it is an exception, the exception is wrapped.
103
+ def initialize(library_or_error, class_name = nil, alcn = nil, gem = nil)
104
+ if library_or_error.kind_of?(String)
105
+ msg = "The needed library '#{library_or_error}' is missing."
106
+ msg += " You can install it via rubygems with 'gem install #{gem}'!" if gem
107
+ super(msg, class_name, alcn)
108
+ @library = library_or_error
109
+ else
110
+ super(library_or_error, class_name, alcn)
111
+ @library = nil
112
+ end
113
+ @gem = gem
114
+ end
115
+
116
+ end
117
+
118
+
119
+ # This error is raised when a needed external command is not found.
120
+ class CommandNotFoundError < Error
121
+
122
+ # The command that is missing.
123
+ attr_reader :cmd
124
+
125
+ # Create a new CommandNotFoundError using the provided values.
126
+ #
127
+ # The parameter +cmd+ specifies the command that is missing.
128
+ def initialize(cmd, class_name = nil, alcn = nil)
129
+ super("The needed command '#{cmd}' is missing!", class_name, alcn)
130
+ @cmd = cmd
131
+ end
132
+
133
+ end
134
+
135
+ end
@@ -22,10 +22,11 @@ module Webgen
22
22
  include WebsiteAccess
23
23
  include Loggable
24
24
 
25
- # The parent node.
25
+ # The parent node. This is in all but one case a Node object. The one exception is that the
26
+ # parent of the Tree#dummy_node is a Tree object.
26
27
  attr_reader :parent
27
28
 
28
- # The children of this node.
29
+ # The child nodes of this node.
29
30
  attr_reader :children
30
31
 
31
32
  # The full output path of this node.
@@ -38,13 +39,13 @@ module Webgen
38
39
  attr_reader :cn
39
40
 
40
41
  # The absolute canonical name of this node.
41
- attr_reader :absolute_cn
42
+ attr_reader :acn
42
43
 
43
44
  # The localized canonical name of this node.
44
45
  attr_reader :lcn
45
46
 
46
47
  # The absolute localized canonical name of this node.
47
- attr_reader :absolute_lcn
48
+ attr_reader :alcn
48
49
 
49
50
  # The level of the node. The level specifies how deep the node is in the hierarchy.
50
51
  attr_reader :level
@@ -76,7 +77,7 @@ module Webgen
76
77
  # found, the node is language neutral.
77
78
  def initialize(parent, path, cn, meta_info = {})
78
79
  @parent = parent
79
- @cn = cn.chomp('/').freeze
80
+ @cn = cn.freeze
80
81
  @children = []
81
82
  reinit(path, meta_info)
82
83
  init_rest
@@ -111,11 +112,11 @@ module Webgen
111
112
 
112
113
  # Return the node information hash which contains information for processing the node.
113
114
  def node_info
114
- tree.node_info[@absolute_lcn] ||= {}
115
+ tree.node_info[@alcn] ||= {}
115
116
  end
116
117
 
117
118
  # Check if the node is a directory.
118
- def is_directory?; @path[-1] == ?/; end
119
+ def is_directory?; @path[-1] == ?/ && !is_fragment?; end
119
120
 
120
121
  # Check if the node is a file.
121
122
  def is_file?; !is_directory? && !is_fragment?; end
@@ -160,14 +161,23 @@ module Webgen
160
161
  # thinks it is dirty.
161
162
  def changed?
162
163
  if_not_checked(:node) do
163
- flag(:dirty) if meta_info_changed? ||
164
- node_info[:used_nodes].any? {|n| n != @absolute_lcn && (!tree[n] || tree[n].changed?)} ||
165
- node_info[:used_meta_info_nodes].any? {|n| n != @absolute_lcn && (!tree[n] || tree[n].meta_info_changed?)}
164
+ flag(:dirty) if meta_info_changed? || user_nodes_changed? ||
165
+ node_info[:used_nodes].any? {|n| n != @alcn && (!tree[n] || tree[n].changed?)} ||
166
+ node_info[:used_meta_info_nodes].any? {|n| n != @alcn && (!tree[n] || tree[n].meta_info_changed?)}
166
167
  website.blackboard.dispatch_msg(:node_changed?, self) unless flagged?(:dirty)
167
168
  end
168
169
  flagged?(:dirty)
169
170
  end
170
171
 
172
+ # Return +true+ if any node matching a pattern from the meta information +used_nodes+ has changed.
173
+ def user_nodes_changed?
174
+ pattern = [@meta_info['used_nodes']].flatten.compact.collect {|pat| Webgen::Path.make_absolute(parent.alcn, pat)}
175
+ tree.node_access[:alcn].any? do |path, n|
176
+ pattern.any? {|pat| n =~ pat && n.changed?}
177
+ end if pattern.length > 0
178
+ end
179
+ private :user_nodes_changed?
180
+
171
181
  # Return +true+ if the meta information of the node has changed.
172
182
  #
173
183
  # Sends the message <tt>:node_meta_info_changed?</tt> with +self+ as argument unless the meta
@@ -183,12 +193,12 @@ module Webgen
183
193
 
184
194
  # Return an informative representation of the node.
185
195
  def inspect
186
- "<##{self.class.name}: alcn=#{@absolute_lcn}>"
196
+ "<##{self.class.name}: alcn=#{@alcn}>"
187
197
  end
188
198
 
189
199
  # Return +true+ if the alcn matches the pattern. See File.fnmatch for useable patterns.
190
200
  def =~(pattern)
191
- File.fnmatch(pattern, @absolute_lcn, File::FNM_DOTMATCH|File::FNM_CASEFOLD|File::FNM_PATHNAME)
201
+ File.fnmatch(pattern, @alcn, File::FNM_DOTMATCH|File::FNM_CASEFOLD|File::FNM_PATHNAME)
192
202
  end
193
203
 
194
204
  # Sort nodes by using the meta info +sort_info+ (or +title+ if +sort_info+ is not set) of both
@@ -203,22 +213,16 @@ module Webgen
203
213
  self_so <=> other_so
204
214
  end
205
215
 
206
- # Construct the absolute (localized) canonical name by using the +parent+ node and +name+ (which
207
- # can be a cn or an lcn). The +type+ can be either <tt>:alcn</tt> or <tt>:acn</tt>.
208
- def self.absolute_name(parent, name, type)
209
- if parent.kind_of?(Tree)
210
- ''
211
- else
212
- parent = parent.parent while parent.is_fragment? # Handle fragment nodes specially in case they are nested
213
- parent_name = (type == :alcn ? parent.absolute_lcn : parent.absolute_cn)
214
- parent_name + (parent_name !~ /\/$/ && (parent.is_directory? || parent == parent.tree.dummy_root) ? '/' : '') + name
215
- end
216
- end
216
+ # This pattern is the the same as URI::UNSAFE except that the hash character (#) is also
217
+ # not escaped. This is needed sothat paths with fragments work correctly.
218
+ URL_UNSAFE_PATTERN = Regexp.new("[^#{URI::PATTERN::UNRESERVED}#{URI::PATTERN::RESERVED}#]") # :nodoc:
217
219
 
218
- # Construct an internal URL for the given +name+ which can be a acn/alcn/path.
219
- def self.url(name)
220
- url = URI::parse(name)
221
- url = URI::parse('webgen://webgen.localhost/') + url unless url.absolute?
220
+ # Construct an internal URL for the given +name+ which can be an acn/alcn/path. If the parameter
221
+ # +make_absolute+ is +true+, then a relative URL will be made absolute by prepending the special
222
+ # URL <tt>webgen:://webgen.localhost/</tt>.
223
+ def self.url(name, make_absolute = true)
224
+ url = URI::parse(URI::escape(name, URL_UNSAFE_PATTERN))
225
+ url = URI::parse('webgen://webgen.localhost/') + url unless url.absolute? || !make_absolute
222
226
  url
223
227
  end
224
228
 
@@ -235,7 +239,7 @@ module Webgen
235
239
  # exists, an unlocalized version of the node. If no such node is found either, +nil+ is
236
240
  # returned.
237
241
  def in_lang(lang)
238
- avail = @tree.node_access[:acn][@absolute_cn]
242
+ avail = @tree.node_access[:acn][@acn]
239
243
  avail.find do |n|
240
244
  n = n.parent while n.is_fragment?
241
245
  n.lang == lang
@@ -252,19 +256,23 @@ module Webgen
252
256
  # If the +path+ is an alcn and a node is found, it is returned. If the +path+ is an acn, the
253
257
  # correct localized node according to +lang+ is returned or if no such node exists but an
254
258
  # unlocalized version does, the unlocalized node is returned.
255
- def resolve(path, lang = nil)
256
- url = self.class.url(self.is_directory? ? File.join(@absolute_lcn, '/') : @absolute_lcn) + path
259
+ def resolve(path, lang = nil, use_passive_sources = true)
260
+ orig_path = path
261
+ url = self.class.url(@alcn) + self.class.url(path, false)
257
262
 
258
263
  path = url.path + (url.fragment.nil? ? '' : '#' + url.fragment)
259
- path.chomp!('/') unless path == '/'
260
264
  return nil if path =~ /^\/\.\./
261
265
 
262
266
  node = @tree[path, :alcn]
263
- if node && node.absolute_cn != path
264
- node
265
- else
266
- (node = @tree[path, :acn]) && node.in_lang(lang)
267
+ if !node || node.acn == path
268
+ (node = (@tree[path, :acn] || @tree[path + '/', :acn])) && (node = node.in_lang(lang))
269
+ end
270
+ if !node && use_passive_sources && !website.config['passive_sources'].empty?
271
+ nodes = website.blackboard.invoke(:create_nodes_from_paths, [path])
272
+ node = resolve(orig_path, lang, false)
273
+ node.node_info[:used_meta_info_nodes] += nodes.collect {|n| n.alcn} if node
267
274
  end
275
+ node
268
276
  end
269
277
 
270
278
  # Return the relative path to the given path +other+. The parameter +other+ can be a Node or a
@@ -295,7 +303,7 @@ module Webgen
295
303
  if !is_directory?
296
304
  self
297
305
  else
298
- key = [absolute_lcn, :index_node, lang]
306
+ key = [alcn, :index_node, lang]
299
307
  vcache = website.cache.volatile
300
308
  return vcache[key] if vcache.has_key?(key)
301
309
 
@@ -306,10 +314,10 @@ module Webgen
306
314
  index_node = resolve(index_path, lang)
307
315
  if index_node
308
316
  vcache[key] = index_node
309
- log(:info) { "Directory index path for <#{absolute_lcn}> => <#{index_node.absolute_lcn}>" }
317
+ log(:info) { "Directory index path for <#{alcn}> => <#{index_node.alcn}>" }
310
318
  elsif log_warning
311
319
  vcache[key] = self
312
- log(:warn) { "No directory index path found for directory <#{absolute_lcn}>" }
320
+ log(:warn) { "No directory index path found for directory <#{alcn}>" }
313
321
  end
314
322
  end
315
323
  vcache[key] || self
@@ -351,8 +359,8 @@ module Webgen
351
359
  # Do the rest of the initialization.
352
360
  def init_rest
353
361
  @lcn = Path.lcn(@cn, @lang)
354
- @absolute_cn = self.class.absolute_name(@parent, @cn, :acn)
355
- @absolute_lcn = self.class.absolute_name(@parent, @lcn, :alcn)
362
+ @acn = (@parent.kind_of?(Tree) ? '' : @parent.acn.sub(/#.*$/, '') + @cn)
363
+ @alcn = (@parent.kind_of?(Tree) ? '' : @parent.alcn.sub(/#.*$/, '') + @lcn)
356
364
 
357
365
  @level = -1
358
366
  @tree = @parent