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
@@ -18,8 +18,10 @@ module Webgen
18
18
  # Write the +data+ to the given output +path+. The parameter +data+ is either a String with the
19
19
  # content or a Webgen::Path::SourceIO object. The parameter +type+ specifies the type of the to
20
20
  # be written path: <tt>:file</tt> or <tt>:directory</tt>.
21
- # [<tt>read(path)</tt>]
22
- # Return the content of the given path if it exists or raise an error otherwise.
21
+ # [<tt>read(path, mode = 'rb')</tt>]
22
+ # Return the content of the given path if it exists or raise an error otherwise. The parameter
23
+ # +mode+ specifies the mode in which the path should be opened and defaults to reading in binary
24
+ # mode.
23
25
  #
24
26
  # It seems a bit odd that an output instance has to implement reading functionality. However,
25
27
  # consider the case where you want webgen to render a website programmatically and *use* the
@@ -52,7 +54,7 @@ module Webgen
52
54
  # @data[path] = [(io.kind_of?(String) ? io : io.data), type]
53
55
  # end
54
56
  #
55
- # def read(path)
57
+ # def read(path, mode = 'rb')
56
58
  # path = File.join('/', path)
57
59
  # raise "No such file #{path}" unless @data[path] && @data[path].last == :file
58
60
  # @data[path].first
@@ -50,7 +50,7 @@ module Webgen::Output
50
50
  if data.kind_of?(String)
51
51
  File.open(dest, 'wb') {|f| f.write(data) }
52
52
  else
53
- data.stream do |source|
53
+ data.stream('rb') do |source|
54
54
  File.open(dest, 'wb') {|f| FileUtils.copy_stream(source, f) }
55
55
  end
56
56
  end
@@ -59,9 +59,9 @@ module Webgen::Output
59
59
  end
60
60
  end
61
61
 
62
- # Return the content of the given +path+.
63
- def read(path)
64
- File.open(File.join(@root, path), 'rb') {|f| f.read}
62
+ # Return the content of the given +path+ which is opened in +mode+.
63
+ def read(path, mode = 'rb')
64
+ File.open(File.join(@root, path), mode) {|f| f.read}
65
65
  end
66
66
 
67
67
  end
@@ -49,7 +49,7 @@ module Webgen
49
49
 
50
50
 
51
51
  # Raised during parsing of data in Webgen Page Format if the data is invalid.
52
- class FormatError < RuntimeError; end
52
+ class FormatError < StandardError; end
53
53
 
54
54
 
55
55
  # :stopdoc:
@@ -57,7 +57,7 @@ module Webgen
57
57
  RE_META_INFO = /\A---\s*(?:\n|\r|\r\n).*?(?:\n|\r|\r\n)(?=---.*?(?:\n|\r|\r\n)|\Z)/m
58
58
  RE_BLOCKS_OPTIONS = /^--- *?(?: *((?:\w+:[^\s]* *)*))?$|^$/
59
59
  RE_BLOCKS_START = /^--- .*?$|^--- *$/
60
- RE_BLOCKS = /(?:(#{RE_BLOCKS_START})|\A)(.*?)(?:(?=#{RE_BLOCKS_START})|\Z)/m
60
+ RE_BLOCKS = /(?:(#{RE_BLOCKS_START})|\A)\n?(.*?)(?:(?=#{RE_BLOCKS_START})|\z)/m
61
61
  # :startdoc:
62
62
 
63
63
  class << self
@@ -100,7 +100,7 @@ module Webgen
100
100
  raise FormatError, "Invalid structure of meta information block: expected YAML hash but found #{meta_info.class}"
101
101
  end
102
102
  rescue ArgumentError => e
103
- raise FormatError, e.message
103
+ raise FormatError, "Invalid YAML syntax in meta information block: #{e.message}"
104
104
  end
105
105
  meta_info
106
106
  end
@@ -126,7 +126,7 @@ module Webgen
126
126
  raise(FormatError, "Previously used name '#{name}' also used for block #{index+1}") if blocks.has_key?(name)
127
127
  content ||= ''
128
128
  content.gsub!(/^(\\+)(---.*?)$/) {|m| "\\" * ($1.length / 2) + $2}
129
- content.strip!
129
+ content.chomp!("\n") unless index + 1 == scanned.length
130
130
  blocks[name] = blocks[index+1] = Block.new(name, content, options)
131
131
  end
132
132
  meta_info.delete('blocks')
@@ -1,11 +1,33 @@
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 one or more nodes as well
11
+ # as methods for accessing the path's content. So a Path object always refers to a source path. In
12
+ # contrast, output paths are always strings and just specify the location where a specific node
13
+ # should be written to.
14
+ #
15
+ # Note the +path+ and +source_path+ attributes of a Path object:
16
+ #
17
+ # * The +source_path+ specifies a path string that was directly created by a Source object. Each
18
+ # Path object must have such a valid source path sothat webgen can infer the Path the lead to
19
+ # the creation of a Node object later.
20
+ #
21
+ # * In contrast, the +path+ attribute specifies the path that is used to create the canonical name
22
+ # (and by default the output path) of a Node object. Normally it is the same as the
23
+ # +source_path+ but can differ (e.g. when fragment nodes are created for page file nodes).
24
+ #
25
+ # A Path object can represent one of three different things: a directory, a file or a fragment. If
26
+ # the +path+ ends with a slash character, then the path object represents a directory, if the path
27
+ # contains a hash character anywhere, then the path object represents a fragment and else it
28
+ # represents a file. Have a look at the webgen manual to see the exact format of a path!
29
+ #
30
+ # == Relation to Source classes
9
31
  #
10
32
  # A webgen source class needs to derive a specialized path class from this class and implement an
11
33
  # approriate #changed? method that returns +true+ if the path's content has changed since the last
@@ -13,30 +35,48 @@ module Webgen
13
35
  class Path
14
36
 
15
37
  # Helper class for easy access to the content of a path.
38
+ #
39
+ # This class is used sothat the creation of the real IO object for #stream can be delayed till
40
+ # it is actually needed. This is done by not directly requiring the user of this class to supply
41
+ # the IO object, but by requiring a block that creates the real IO object.
16
42
  class SourceIO
17
43
 
18
- # Create a new SourceIO object. A block has to be specified that returns an IO object.
44
+ # Create a new SourceIO object. A block has to be specified that returns the to-be-wrapped IO
45
+ # object.
19
46
  def initialize(&block)
20
47
  @block = block
21
- raise ArgumentError, 'Need to provide a block which returns an IO object' if @block.nil?
48
+ raise ArgumentError, 'You need to provide a block which returns an IO object' if @block.nil?
22
49
  end
23
50
 
24
- # Provide direct access to the wrapped IO object.
25
- def stream
26
- io = @block.call
51
+ # Provide direct access to the wrapped IO object by yielding it. After the method block
52
+ # returns the IO object is automatically closed.
53
+ #
54
+ # The parameter +mode+ specifies the mode in which the wrapped IO object should be opened.
55
+ # This can be used, for example, to open a file in binary mode (or specify a certain input
56
+ # encoding under Ruby 1.9).
57
+ def stream(mode = 'r')
58
+ io = @block.call(mode)
27
59
  yield(io)
28
60
  ensure
29
61
  io.close
30
62
  end
31
63
 
32
- # Return the content of the wrapped IO object as string.
33
- def data
34
- stream {|io| io.read}
64
+ # Return the whole content of the wrapped IO object as string. For a description of the
65
+ # parameter +mode+ see #stream.
66
+ def data(mode = 'r')
67
+ stream(mode) {|io| io.read}
35
68
  end
36
69
 
37
70
  end
38
71
 
39
72
 
73
+ # Make the given +path+ absolute by prepending the absolute directory path +base+ if necessary.
74
+ # Also resolves all '..' and '.' references in +path+.
75
+ def self.make_absolute(base, path)
76
+ raise(ArgumentError, 'base has to be an absolute path, ie. needs to start with a slash') unless base =~ /\//
77
+ Pathname.new(path =~ /^\// ? path : File.join(base, path)).cleanpath.to_s
78
+ end
79
+
40
80
  # Return +true+ if the given +path+ matches the given +pattern+ (trailing slashes of directories
41
81
  # are not respected). For information on which patterns are supported, have a look at the
42
82
  # documentation of File.fnmatch.
@@ -49,66 +89,86 @@ module Webgen
49
89
 
50
90
  include Comparable
51
91
 
52
- # The full path.
53
- attr_accessor :path
92
+ # The full path for which this Path object was created.
93
+ attr_reader :path
54
94
 
55
- # The source path that lead to the creation of this path.
56
- attr_accessor :source_path
95
+ # A string specifying the path that lead to the creation of this path.
96
+ attr_reader :source_path
57
97
 
58
- # The basename part of the path.
59
- attr_accessor :basename
98
+ # The string specifying the parent path
99
+ attr_reader :parent_path
60
100
 
61
- # The directory part of the path.
62
- attr_accessor :directory
63
-
64
- # The canonical name without the extension.
65
- attr_accessor :cnbase
101
+ # The canonical name of the path without the extension.
102
+ attr_accessor :basename
66
103
 
67
- # The extension.
104
+ # The extension of the +path+.
68
105
  attr_accessor :ext
69
106
 
70
107
  # Extracted meta information for the path.
71
108
  attr_accessor :meta_info
72
109
 
110
+ # Specifies whether this path should be used during the "tree update" phase of a webgen run or
111
+ # only later during node resolution.
112
+ attr_writer :passive
113
+
114
+ # Is this path only used later during node resolution? Defaults to +false+, i.e. used during the
115
+ # "tree update" phase.
116
+ def passive?; @passive; end
117
+
118
+
73
119
  # 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.
120
+ # string that lead to the creation of this path. The optional block needs to return an IO object
121
+ # for getting the content of the path.
122
+ #
123
+ # The +path+ needs to be in a well defined format which can be looked up in the webgen manual.
76
124
  def initialize(path, source_path = path, &ioblock)
77
125
  @meta_info = {}
78
126
  @io = block_given? ? SourceIO.new(&ioblock) : nil
79
127
  @source_path = source_path
128
+ @passive = false
80
129
  analyse(path)
81
130
  end
82
131
 
83
- # Mount this path at the mount point +mp+ optionally stripping +prefix+ from the path and return
84
- # the new path object.
132
+ # Mount this path at the mount point +mp+, optionally stripping +prefix+ from the parent path,
133
+ # and return the new path object.
134
+ #
135
+ # The parameters +mp+ and +prefix+ have to be absolute directory paths, ie. they have to start
136
+ # and end with a slash and must not contain any hash characters!
137
+ #
138
+ #--
139
+ # Can't use self.class.new(...) here because the semantics of the sub constructors is not know
140
+ #++
85
141
  def mount_at(mp, prefix = nil)
142
+ raise(ArgumentError, "The mount point (#{mp}) must be a valid directory path") if mp =~ /^[^\/]|#|[^\/]$/
143
+ raise(ArgumentError, "The strip prefix (#{prefix}) must be a valid directory path") if !prefix.nil? && prefix =~ /^[^\/]|#|[^\/]$/
144
+
86
145
  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
146
+ strip_re = /^#{Regexp.escape(prefix.to_s)}/
147
+ temp.instance_variable_set(:@path, temp.path.sub(strip_re, ''))
148
+ reanalyse = (@path == '/' || temp.path == '')
149
+ temp.instance_variable_set(:@path, File.join(mp, temp.path))
150
+ temp.instance_variable_set(:@source_path, temp.path) if @path == @source_path
91
151
  if reanalyse
92
152
  temp.send(:analyse, temp.path)
93
153
  else
94
- temp.directory = File.join(File.dirname(temp.path), '/')
154
+ temp.instance_variable_set(:@parent_path, File.join(mp, temp.parent_path.sub(strip_re, '')))
95
155
  end
96
156
  temp
97
157
  end
98
158
 
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
159
  # Duplicate the path object.
106
160
  def dup
107
161
  temp = super
108
- temp.meta_info = @meta_info.dup
162
+ temp.instance_variable_set(:@meta_info, @meta_info.dup)
109
163
  temp
110
164
  end
111
165
 
166
+ # Has the content of this path changed since the last webgen run? This default implementation
167
+ # always returns +true+, a specialized sub class needs to override this behaviour!
168
+ def changed?
169
+ true
170
+ end
171
+
112
172
  # The SourceIO object associated with the path.
113
173
  def io
114
174
  if @io
@@ -118,26 +178,45 @@ module Webgen
118
178
  end
119
179
  end
120
180
 
121
- # The canonical name created from the filename (created from cnbase and extension).
181
+ # The canonical name created from the +path+ (namely from the parts +basename+ and +extension+).
122
182
  def cn
123
- @cnbase + (@ext.length > 0 ? '.' + @ext : '')
183
+ @basename + (@ext.length > 0 ? '.' + @ext : '') + (@basename != '/' && @path =~ /.\/$/ ? '/' : '')
124
184
  end
125
185
 
126
- # Utility method for creating the lcn from +cn+ and the language +lang+.
186
+ # Utility method for creating the lcn from the +cn+ and the language +lang+.
127
187
  def self.lcn(cn, lang)
128
188
  if lang.nil?
129
189
  cn
130
190
  else
131
- cn.split('.').insert(1, lang.to_s).join('.')
191
+ cn.split('.').insert((cn =~ /^\./ ? 2 : 1), lang.to_s).join('.')
132
192
  end
133
193
  end
134
194
 
135
- # The localized canonical name created from the filename.
195
+ # The localized canonical name created from the +path+.
136
196
  def lcn
137
197
  self.class.lcn(cn, @meta_info['lang'])
138
198
  end
139
199
 
140
- # Compare this object to another Path or a String.
200
+ # The absolute canonical name of this path.
201
+ def acn
202
+ if @path =~ /#/
203
+ self.class.new(@parent_path).acn + cn
204
+ else
205
+ @parent_path + cn
206
+ end
207
+ end
208
+
209
+ # The absolute localized canonical name of this path.
210
+ def alcn
211
+ if @path =~ /#/
212
+ self.class.new(@parent_path).alcn + lcn
213
+ else
214
+ @parent_path + lcn
215
+ end
216
+ end
217
+
218
+ # Equality -- Return +true+ if +other+ is a Path object with the same #path or if +other+ is a
219
+ # String equal to the #path. Else return +false+.
141
220
  def ==(other)
142
221
  if other.kind_of?(Path)
143
222
  other.path == @path
@@ -149,13 +228,12 @@ module Webgen
149
228
  end
150
229
  alias_method(:eql?, :==)
151
230
 
152
- # Implemented sothat a Path looks like a String when used as key in a hash.
231
+ # Compare the #path of this object to <tt>other.path</tt>
153
232
  def <=>(other)
154
- @path <=> other.to_str
233
+ @path <=> other.path
155
234
  end
156
235
 
157
- # Implemented sothat a Path looks like a String when used as key in a hash.
158
- def hash
236
+ def hash #:nodoc:
159
237
  @path.hash
160
238
  end
161
239
 
@@ -172,21 +250,46 @@ module Webgen
172
250
  private
173
251
  #######
174
252
 
175
- FILENAME_RE = /^(?:(\d+)\.)?([^.]*?)(?:\.(\w\w\w?)(?=.))?(?:\.(.*))?$/
176
-
177
253
  # Analyse the +path+ and fill the object with the extracted information.
178
254
  def analyse(path)
179
255
  @path = path
180
- @basename = File.basename(path)
181
- @directory = File.join(File.dirname(path), '/')
182
- matchData = FILENAME_RE.match(@basename)
256
+ if @path =~ /#/
257
+ analyse_fragment
258
+ elsif @path =~ /\/$/
259
+ analyse_directory
260
+ else
261
+ analyse_file
262
+ end
263
+ @meta_info['title'] = @basename.tr('_-', ' ').capitalize
264
+ @ext ||= ''
265
+ raise "The basename of a path may not be empty: #{@path}" if @basename.empty? || @basename == '#'
266
+ raise "The parent path must start with a slash: #{@path}" if @path !~ /^\// && @path != '/'
267
+ end
183
268
 
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
269
+ # Analyse the path assuming it is a directory.
270
+ def analyse_directory
271
+ @parent_path = (@path == '/' ? '' : File.join(File.dirname(@path), '/'))
272
+ @basename = File.basename(@path)
273
+ end
274
+
275
+ FILENAME_RE = /^(?:(\d+)\.)?(\.?[^.]*?)(?:\.(\w\w\w?)(?=\.))?(?:\.(.*))?$/
276
+
277
+ # Analyse the path assuming it is a file.
278
+ def analyse_file
279
+ @parent_path = File.join(File.dirname(@path), '/')
280
+ match_data = FILENAME_RE.match(File.basename(@path))
281
+
282
+ @meta_info['sort_info'] = (match_data[1].nil? ? nil : match_data[1].to_i)
283
+ @basename = match_data[2]
284
+ @meta_info['lang'] = Webgen::LanguageManager.language_for_code(match_data[3])
285
+ @ext = (@meta_info['lang'].nil? && !match_data[3].nil? ? match_data[3].to_s : '') + match_data[4].to_s
286
+ end
188
287
 
189
- @meta_info['title'] = @cnbase.tr('_-', ' ').capitalize
288
+ # Analyse the path assuming it is a fragment.
289
+ def analyse_fragment
290
+ @parent_path, @basename = @path.scan(/^(.*?)(#.*?)$/).first
291
+ raise "The parent path of a fragment path must be a file path and not a directory path: #{@path}" if @parent_path =~ /\/$/
292
+ raise "A fragment path must only contain one hash character: #{path}" if @path.count("#") > 1
190
293
  end
191
294
 
192
295
  end
@@ -10,9 +10,13 @@ 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.
17
+ #
18
+ # Also note that the returned Path objects should have the meta information <tt>modified_at</tt>
19
+ # set to the correct last modification time of the path, ie. the value has to be a Time object!
16
20
  #
17
21
  # == Sample Source Class
18
22
  #
@@ -36,10 +40,9 @@ module Webgen
36
40
  # end
37
41
  #
38
42
  # 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):
43
+ # <tt>ext/init.rb</tt>) by updating the <tt>sources</tt> configuration option:
41
44
  #
42
- # WebsiteAccess.website.config['sources'] << ['/', MemorySource]
45
+ # WebsiteAccess.website.config['sources'] << ['/', 'MemorySource']
43
46
  #
44
47
  module Source
45
48
 
@@ -14,7 +14,7 @@ module Webgen
14
14
 
15
15
  # Create a new object with absolute path +path+ for the file system path +fs_path+.
16
16
  def initialize(path, fs_path)
17
- super(path) { File.open(fs_path, 'rb') }
17
+ super(path) {|mode| File.open(fs_path, mode) }
18
18
  @fs_path = fs_path
19
19
  WebsiteAccess.website.cache[[:fs_path, @fs_path]] = File.mtime(@fs_path)
20
20
  @meta_info['modified_at'] = File.mtime(@fs_path)