webgen 0.5.8 → 0.5.9

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -10,49 +10,80 @@ module Webgen::ContentProcessor
10
10
  BLOCK_RE = /<webgen:block\s*?((?:\s\w+=('|")[^'"]+\2)+)\s*\/>/
11
11
  BLOCK_ATTR_RE = /(\w+)=('|")([^'"]+)\2/
12
12
 
13
- # Replace that webgen:block xml tags with the rendered content of a node.
13
+ # Replace the webgen:block xml tags with the rendered content of the specified node.
14
14
  def call(context)
15
- chain = context[:chain]
16
- new_chain = (chain.length > 1 ? chain[1..-1] : chain)
17
-
18
15
  context.content.gsub!(BLOCK_RE) do |match|
19
16
  attr = {}
20
- match.scan(BLOCK_ATTR_RE) {|name, sep, content| attr[name] = content}
21
- if attr['chain'].nil?
22
- used_chain = new_chain.dup
23
- else
24
- paths = attr['chain'].split(';')
25
- used_chain = paths.collect do |path|
26
- temp_node = context.ref_node.resolve(path.strip, context.dest_node.lang)
27
- log(:error) { "Could not resolve <#{path.strip}> in <#{context.ref_node.absolute_lcn}> in '#{match.to_s}'" } if temp_node.nil?
28
- temp_node
29
- end.compact
30
- next match if used_chain.length != paths.length
31
- dest_node = context.content_node
32
- end
17
+ match_object = $~
18
+ attr[:line_nr_proc] = lambda { match_object.pre_match.scan("\n").size + 1 }
19
+ match.scan(BLOCK_ATTR_RE) {|name, sep, content| attr[name.to_sym] = content}
20
+ render_block(context, attr)
21
+ end
22
+ context
23
+ end
33
24
 
34
- if attr['node'] == 'first'
35
- used_chain.shift while used_chain.length > 0 && !used_chain.first.node_info[:page].blocks.has_key?(attr['name'])
36
- elsif attr['node'] == 'current'
37
- used_chain = context[:chain].dup
38
- end
39
- block_node = used_chain.first
40
-
41
- if !block_node || !block_node.node_info[:page].blocks.has_key?(attr['name'])
42
- if attr['notfound'] == 'ignore'
43
- next ''
44
- elsif block_node
45
- raise "Node <#{block_node.absolute_lcn}> has no block named '#{attr['name']}'"
46
- else
47
- raise "No node in the chain has a block named '#{attr['name']}'"
25
+ # Render a block of a page node and return the result.
26
+ #
27
+ # The Webgen::Context object +context+ is used as the render context and the +options+ hash
28
+ # needs to hold all relevant information, that is:
29
+ #
30
+ # [<tt>:name</tt> (mandatory)]
31
+ # The name of the block that should be used.
32
+ # [<tt>:chain</tt>]
33
+ # The node chain used for rendering. If this is not specified, the node chain from the context
34
+ # is used. It needs to be a String in the format <tt>(A)LCN;(A)LCN;...</tt> or an array of
35
+ # nodes.
36
+ # [<tt>:node</tt>]
37
+ # Defines which node in the node chain should be used. Valid values are +next+ (= default
38
+ # value; the next node in the node chain), +first+ (the first node in the node chain with a
39
+ # block called +name+) or +current+ (the currently rendered node, ignores the +chain+ option).
40
+ # [<tt>:notfound</tt>]
41
+ # If this property is set to +ignore+, a missing block will not raise an error. It is unset by
42
+ # default, so missing blocks will raise errors.
43
+ def render_block(context, options)
44
+ if options[:chain].nil?
45
+ used_chain = (context[:chain].length > 1 ? context[:chain][1..-1] : context[:chain]).dup
46
+ elsif options[:chain].kind_of?(Array)
47
+ used_chain = options[:chain]
48
+ else
49
+ paths = options[:chain].split(';')
50
+ used_chain = paths.collect do |path|
51
+ temp_node = context.ref_node.resolve(path.strip, context.dest_node.lang)
52
+ if temp_node.nil?
53
+ raise Webgen::RenderError.new("Could not resolve <#{path.strip}>",
54
+ self.class.name, context.dest_node.alcn,
55
+ context.ref_node.alcn, (options[:line_nr_proc].call if options[:line_nr_proc]))
48
56
  end
49
- end
57
+ temp_node
58
+ end.compact
59
+ return '' if used_chain.length != paths.length
60
+ dest_node = context.content_node
61
+ end
50
62
 
51
- context.dest_node.node_info[:used_nodes] << block_node.absolute_lcn
52
- tmp_context = block_node.node_info[:page].blocks[attr['name']].render(context.clone(:chain => used_chain, :dest_node => dest_node))
53
- tmp_context.content
63
+ if options[:node] == 'first'
64
+ used_chain.shift while used_chain.length > 0 && !used_chain.first.node_info[:page].blocks.has_key?(options[:name])
65
+ elsif options[:node] == 'current'
66
+ used_chain = context[:chain].dup
54
67
  end
55
- context
68
+ block_node = used_chain.first
69
+
70
+ if !block_node || !block_node.node_info[:page].blocks.has_key?(options[:name])
71
+ if options[:notfound] == 'ignore'
72
+ return ''
73
+ elsif block_node
74
+ raise Webgen::RenderError.new("No block named '#{options[:name]}' found in <#{block_node.alcn}>",
75
+ self.class.name, context.dest_node.alcn,
76
+ context.ref_node.alcn, (options[:line_nr_proc].call if options[:line_nr_proc]))
77
+ else
78
+ raise Webgen::RenderError.new("No node in the render chain has a block named '#{options[:name]}'",
79
+ self.class.name, context.dest_node.alcn,
80
+ context.ref_node.alcn, (options[:line_nr_proc].call if options[:line_nr_proc]))
81
+ end
82
+ end
83
+
84
+ context.dest_node.node_info[:used_nodes] << block_node.alcn
85
+ tmp_context = block_node.node_info[:page].blocks[options[:name]].render(context.clone(:chain => used_chain, :dest_node => dest_node))
86
+ tmp_context.content
56
87
  end
57
88
 
58
89
  end
@@ -19,11 +19,14 @@ module Webgen::ContentProcessor
19
19
  dest_node = deprecate('dest_node', 'context.dest_node', context.dest_node)
20
20
 
21
21
  xml = ::Builder::XmlMarkup.new(:indent => 2)
22
- eval(context.content, binding, context.ref_node.absolute_lcn)
22
+ eval(context.content, binding, context.ref_node.alcn)
23
23
  context.content = xml.target!
24
24
  context
25
+ rescue LoadError
26
+ raise Webgen::LoadError.new('builder', self.class.name, context.dest_node.alcn, 'builder')
25
27
  rescue Exception => e
26
- raise RuntimeError, "Error using Builder in <#{context.ref_node.absolute_lcn}> to generate XML: #{e.message}", e.backtrace
28
+ line = (e.is_a?(::SyntaxError) ? e.message : e.backtrace[0]).scan(/:(\d+)/).first.first.to_i rescue nil
29
+ raise Webgen::RenderError.new(e, self.class.name, context.dest_node.alcn, context.ref_node.alcn, line)
27
30
  end
28
31
 
29
32
  end
@@ -10,6 +10,7 @@ module Webgen::ContentProcessor
10
10
  # Process the Ruby statements embedded in the content of +context+.
11
11
  def call(context)
12
12
  require 'erb'
13
+ extend(ERB::Util)
13
14
 
14
15
  website = deprecate('website', 'context.website', context.website)
15
16
  node = deprecate('node', 'context.node', context.content_node)
@@ -17,11 +18,12 @@ module Webgen::ContentProcessor
17
18
  dest_node = deprecate('dest_node', 'context.dest_node', context.dest_node)
18
19
 
19
20
  erb = ERB.new(context.content)
20
- erb.filename = context.ref_node.absolute_lcn
21
+ erb.filename = context.ref_node.alcn
21
22
  context.content = erb.result(binding)
22
23
  context
23
24
  rescue Exception => e
24
- raise RuntimeError, "Erb processing failed in <#{context.ref_node.absolute_lcn}>: #{e.message}", e.backtrace
25
+ line = (e.is_a?(::SyntaxError) ? e.message : e.backtrace[0]).scan(/:(\d+)/).first.first.to_i rescue nil
26
+ raise Webgen::RenderError.new(e, self.class.name, context.dest_node.alcn, context.ref_node.alcn, line)
25
27
  end
26
28
 
27
29
  end
@@ -29,11 +29,14 @@ module Webgen::ContentProcessor
29
29
  else
30
30
  ::Erubis::Eruby.new(context.content, options)
31
31
  end
32
- erubis.filename = context.ref_node.absolute_lcn
32
+ erubis.filename = context.ref_node.alcn
33
33
  context.content = erubis.result(binding)
34
34
  context
35
+ rescue LoadError
36
+ raise Webgen::LoadError.new('erubis', self.class.name, context.dest_node.alcn, 'erubis')
35
37
  rescue Exception => e
36
- raise RuntimeError, "Erubis processing failed in <#{context.ref_node.absolute_lcn}>: #{e.message}", e.backtrace
38
+ line = (e.is_a?(::SyntaxError) ? e.message : e.backtrace[0]).scan(/:(\d+)/).first.first.to_i rescue nil
39
+ raise Webgen::RenderError.new(e, self.class.name, context.dest_node.alcn, context.ref_node.alcn, line)
37
40
  end
38
41
 
39
42
  end
@@ -18,11 +18,15 @@ module Webgen::ContentProcessor
18
18
  :ref_node => deprecate('ref_node', 'context.ref_node', context.ref_node),
19
19
  :dest_node => deprecate('dest_node', 'context.dest_node', context.dest_node)
20
20
  }
21
- context.content = ::Haml::Engine.new(context.content, :filename => context.ref_node.absolute_lcn).
21
+ context.content = ::Haml::Engine.new(context.content, :filename => context.ref_node.alcn).
22
22
  render(Object.new, locals)
23
23
  context
24
+ rescue LoadError
25
+ raise Webgen::LoadError.new('haml', self.class.name, context.dest_node.alcn, 'haml')
26
+ rescue ::Haml::Error => e
27
+ raise Webgen::RenderError.new(e, self.class.name, context.dest_node.alcn, context.ref_node.alcn, (e.line + 1 if e.line))
24
28
  rescue Exception => e
25
- raise RuntimeError, "Error converting Haml markup to HTML in <#{context.ref_node.absolute_lcn}>: #{e.message}", e.backtrace
29
+ raise Webgen::RenderError.new(e, self.class.name, context.dest_node.alcn, context.ref_node.alcn)
26
30
  end
27
31
 
28
32
  end
@@ -0,0 +1,64 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module Webgen::ContentProcessor
4
+
5
+ # Inserts additional links to CSS/JS files and other HTML head meta info directly before the HTML
6
+ # head end tag.
7
+ #
8
+ # The data used by this content processor is taken from the Context object. Therefore this
9
+ # processor should be the last in the processing pipeline so that all other processors have been
10
+ # able to set the data.
11
+ #
12
+ # The key <tt>:cp_head</tt> of <tt>context.persistent</tt> is used (the normal
13
+ # <tt>context.options</tt> won't do because the data needs to be shared 'backwards' during the
14
+ # rendering) and it has to be a Hash with the following values:
15
+ #
16
+ # [:js_file] An array of already resolved relative or absolute paths to Javascript files.
17
+ # [:js_inline] An array of Javascript fragments to be inserted directly into the head section.
18
+ # [:css_file] An array of already resolved relative or absolute paths to CSS files.
19
+ # [:css_inline] An array of CSS fragments to be inserted directly into the head section.
20
+ # [:meta] A hash with key-value pairs from which <tt>meta</tt> tags are generated. The keys and
21
+ # the values will be properly escaped before insertion. The entries in the meta
22
+ # information <tt>meta</tt> of the content node are also used and take precedence over
23
+ # these entries.
24
+ #
25
+ # Duplicate values will be removed from the above mentioned arrays before generating the output.
26
+ class Head
27
+
28
+ include Webgen::Loggable
29
+
30
+ HTML_HEAD_END_RE = /<\/head\s*>/i #:nodoc:
31
+
32
+ # Insert the additional header information.
33
+ def call(context)
34
+ require 'erb'
35
+ context.content.sub!(HTML_HEAD_END_RE) do |match|
36
+ result = ''
37
+ if context.persistent[:cp_head].kind_of?(Hash)
38
+ context.persistent[:cp_head][:js_file].uniq.each do |js_file|
39
+ result += "\n<script type=\"text/javascript\" src=\"#{js_file}\"></script>"
40
+ end if context.persistent[:cp_head][:js_file].kind_of?(Array)
41
+
42
+ context.persistent[:cp_head][:js_inline].uniq.each do |content|
43
+ result += "\n<script type=\"text/javascript\">\n#{content}\n</script>"
44
+ end if context.persistent[:cp_head][:js_inline].kind_of?(Array)
45
+
46
+ context.persistent[:cp_head][:css_file].uniq.each do |css_file|
47
+ result += "\n<link rel=\"stylesheet\" href=\"#{css_file}\" type=\"text/css\"/>"
48
+ end if context.persistent[:cp_head][:css_file].kind_of?(Array)
49
+
50
+ context.persistent[:cp_head][:css_inline].uniq.each do |content|
51
+ result += "\n<style type=\"text/css\"><![CDATA[/\n#{content}\n]]></style>"
52
+ end if context.persistent[:cp_head][:css_inline].kind_of?(Array)
53
+ end
54
+ ((context.persistent[:cp_head] || {})[:meta] || {}).merge(context.node['meta'] || {}).each do |name, content|
55
+ result += "\n<meta name=\"#{ERB::Util.h(name)}\" content=\"#{ERB::Util.h(content)}\" />"
56
+ end
57
+ result + match
58
+ end
59
+ context
60
+ end
61
+
62
+ end
63
+
64
+ end
@@ -11,8 +11,10 @@ module Webgen::ContentProcessor
11
11
  $uid = 0 #fix for invalid fragment ids on second run
12
12
  context.content = ::Maruku.new(context.content, :on_error => :raise).to_html
13
13
  context
14
+ rescue LoadError
15
+ raise Webgen::LoadError.new('maruku', self.class.name, context.dest_node.alcn, 'maruku')
14
16
  rescue Exception => e
15
- raise RuntimeError, "Maruku to HTML conversion failed for <#{context.ref_node.absolute_lcn}>: #{e.message}", e.backtrace
17
+ raise Webgen::RenderError.new(e, self.class.name, context.dest_node.alcn, context.ref_node.alcn)
16
18
  end
17
19
 
18
20
  end
@@ -10,6 +10,8 @@ module Webgen::ContentProcessor
10
10
  require 'rdiscount'
11
11
  context.content = ::RDiscount.new(context.content).to_html
12
12
  context
13
+ rescue LoadError
14
+ raise Webgen::LoadError.new('rdiscount', self.class.name, context.dest_node.alcn, 'rdiscount')
13
15
  end
14
16
 
15
17
  end
@@ -11,6 +11,8 @@ module Webgen::ContentProcessor
11
11
  require 'rdoc/markup/to_html'
12
12
  context.content = ::RDoc::Markup::ToHtml.new.convert(context.content)
13
13
  context
14
+ rescue LoadError
15
+ raise Webgen::LoadError.new('rdoc/markup/to_html', self.class.name, context.dest_node.alcn, 'rdoc')
14
16
  end
15
17
 
16
18
  end
@@ -12,6 +12,8 @@ module Webgen::ContentProcessor
12
12
  doc.hard_breaks = context.website.config['contentprocessor.redcloth.hard_breaks']
13
13
  context.content = doc.to_html
14
14
  context
15
+ rescue LoadError
16
+ raise Webgen::LoadError.new('redcloth', self.class.name, context.dest_node.alcn, 'RedCloth')
15
17
  end
16
18
 
17
19
  end
@@ -9,10 +9,12 @@ module Webgen::ContentProcessor
9
9
  def call(context)
10
10
  require 'sass'
11
11
 
12
- context.content = ::Sass::Engine.new(context.content, :filename => context.ref_node.absolute_lcn).render
12
+ context.content = ::Sass::Engine.new(context.content, :filename => context.ref_node.alcn).render
13
13
  context
14
- rescue Exception => e
15
- raise RuntimeError, "Error converting Sass markup to CSS in <#{context.ref_node.absolute_lcn}>: #{e.message}", e.backtrace
14
+ rescue LoadError
15
+ raise Webgen::LoadError.new('sass', self.class.name, context.dest_node.alcn, 'haml')
16
+ rescue ::Sass::SyntaxError => e
17
+ raise Webgen::RenderError.new(e, self.class.name, context.dest_node.alcn, context.ref_node.alcn, (e.sass_line if e.sass_line))
16
18
  end
17
19
 
18
20
  end
@@ -22,24 +22,38 @@ module Webgen::ContentProcessor
22
22
 
23
23
  # Replace all webgen tags in the content of +context+ with the rendered content.
24
24
  def call(context)
25
- replace_tags(context.content, context.ref_node) do |tag, param_string, body|
26
- log(:debug) { "Replacing tag #{tag} with data '#{param_string}' and body '#{body}' in <#{context.ref_node.absolute_lcn}>" }
27
-
28
- result = ''
29
- processor = processor_for_tag(tag)
30
- if !processor.nil?
31
- processor.set_params(processor.create_tag_params(param_string, context.ref_node))
32
- result, process_output = processor.call(tag, body, context)
33
- processor.set_params(nil)
34
-
35
- result = call(context.clone(:content => result)).content if process_output
36
- end
37
-
38
- result
25
+ replace_tags(context) do |tag, param_string, body|
26
+ log(:debug) { "Replacing tag #{tag} with data '#{param_string}' and body '#{body}' in <#{context.ref_node.alcn}>" }
27
+ process_tag(tag, param_string, body, context)
39
28
  end
40
29
  context
41
30
  end
42
31
 
32
+ # Process the +tag+ and return the result. The parameter +params+ needs to be a Hash holding all
33
+ # needed and optional parameters for the tag or a parameter String in YAML format and +body+ is
34
+ # the optional body for the tag. +context+ needs to be a valid Webgen::Context object.
35
+ def process_tag(tag, params, body, context)
36
+ result = ''
37
+ processor = processor_for_tag(tag)
38
+ if !processor.nil?
39
+ params = if params.kind_of?(String)
40
+ processor.create_tag_params(params, context.ref_node)
41
+ else
42
+ processor.create_params_hash(params, context.ref_node)
43
+ end
44
+
45
+ processor.set_params(params)
46
+ result, process_output = processor.call(tag, body, context)
47
+ processor.set_params(nil)
48
+ result = call(context.clone(:content => result)).content if process_output
49
+ else
50
+ raise Webgen::RenderError.new("No tag processor for '#{tag}' found", self.class.name,
51
+ context.dest_node.alcn, context.ref_node.alcn)
52
+ end
53
+ result
54
+ end
55
+
56
+
43
57
  #######
44
58
  private
45
59
  #######
@@ -48,11 +62,11 @@ module Webgen::ContentProcessor
48
62
  ProcessingStruct = Struct.new(:state, :tag, :simple_tag, :backslashes, :brackets, :start_pos, :end_pos,
49
63
  :params_start_pos, :params_end_pos, :body_end_pos)
50
64
 
51
- # Return the +content+ provided by +node+ with all webgen tags replaced. When a webgen tag is
52
- # encountered by the parser, the method yields all found information and substitutes the
53
- # returned string for the tag.
54
- def replace_tags(content, node) #:yields: tag_name, param_string, body
55
- scanner = StringScanner.new(content)
65
+ # Return the <tt>context.content</tt> provided by <tt>context.ref_node</tt> with all webgen tags
66
+ # replaced. When a webgen tag is encountered by the parser, the method yields all found
67
+ # information and substitutes the returned string for the tag.
68
+ def replace_tags(context) #:yields: tag_name, param_string, body
69
+ scanner = StringScanner.new(context.content)
56
70
  data = ProcessingStruct.new(:before_tag)
57
71
  while true
58
72
  case data.state
@@ -72,8 +86,8 @@ module Webgen::ContentProcessor
72
86
  when :in_start_tag
73
87
  data.brackets += (scanner[1] == '{' ? 1 : -1) while data.brackets != 0 && scanner.skip_until(BRACKETS_RE)
74
88
  if data.brackets != 0
75
- log(:error) { "Unbalanced curly brackets in <#{node.absolute_lcn}>!" }
76
- data.state = :done
89
+ raise Webgen::RenderError.new("Unbalanced curly brackets for tag '#{data.tag}'", self.class.name,
90
+ context.dest_node.alcn, context.ref_node.alcn)
77
91
  else
78
92
  data.params_end_pos = data.body_end_pos = data.end_pos = scanner.pos - 1
79
93
  data.state = (data.simple_tag ? :process : :in_body)
@@ -106,8 +120,8 @@ module Webgen::ContentProcessor
106
120
  data.end_pos = scanner.pos - 1
107
121
  data.body_end_pos = scanner.pos - scanner.matched.length + scanner[1].length / 2
108
122
  else
109
- log(:error) { "Invalid body part in <#{node.absolute_lcn}>!" }
110
- data.state = :done
123
+ raise Webgen::RenderError.new("Invalid body part - no end tag found for '#{data.tag}'", self.class.name,
124
+ context.dest_node.alcn, context.ref_node.alcn)
111
125
  end
112
126
 
113
127
  when :done
@@ -115,6 +129,9 @@ module Webgen::ContentProcessor
115
129
  end
116
130
  end
117
131
  scanner.string
132
+ rescue Webgen::RenderError => e
133
+ e.line = scanner.string[0...scanner.pos].scan("\n").size + 1 unless e.line
134
+ raise
118
135
  end
119
136
 
120
137
  # Return the tag processor for +tag+ or +nil+ if +tag+ is unknown.
@@ -125,7 +142,6 @@ module Webgen::ContentProcessor
125
142
  elsif map.has_key?(:default)
126
143
  map[:default]
127
144
  else
128
- log(:error) { "No tag processor for tag #{tag.inspect} found" }
129
145
  nil
130
146
  end
131
147
  klass.nil? ? nil : website.cache.instance(klass)
@@ -0,0 +1,38 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require 'tempfile'
3
+
4
+ module Webgen::ContentProcessor
5
+
6
+ # Uses the external +tidy+ program to format the content as valid (X)HTML.
7
+ class Tidy
8
+
9
+ include Webgen::Loggable
10
+
11
+ # Process the content of +context+ with the +tidy+ program.
12
+ def call(context)
13
+ error_file = Tempfile.new('webgen-tidy')
14
+ error_file.close
15
+
16
+ `tidy -v 2>&1`
17
+ if $?.exitstatus != 0
18
+ raise Webgen::CommandNotFoundError.new('tidy', self.class.name, context.dest_node.alcn)
19
+ end
20
+
21
+ cmd = "tidy -q -f #{error_file.path} #{context.website.config['contentprocessor.tidy.options']}"
22
+ result = IO.popen(cmd, 'r+') do |io|
23
+ io.write(context.content)
24
+ io.close_write
25
+ io.read
26
+ end
27
+ if $?.exitstatus != 0
28
+ File.readlines(error_file.path).each do |line|
29
+ log($?.exitstatus == 1 ? :warn : :error) { "Tidy reported problems for <#{context.dest_node.alcn}>: #{line}" }
30
+ end
31
+ end
32
+ context.content = result
33
+ context
34
+ end
35
+
36
+ end
37
+
38
+ end