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