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