webgen 0.4.1 → 0.4.2

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 (93) hide show
  1. data/ChangeLog +300 -0
  2. data/Rakefile +149 -27
  3. data/TODO +3 -4
  4. data/VERSION +1 -1
  5. data/data/webgen/gallery_styles/slides/collage.rb +7 -4
  6. data/data/webgen/sipttra_styles/default/README +7 -0
  7. data/data/webgen/sipttra_styles/default/css/sipttra.rcss +71 -0
  8. data/data/webgen/sipttra_styles/default/js/sipttra.js +7 -0
  9. data/data/webgen/sipttra_styles/default/sipttra.template +105 -0
  10. data/data/webgen/website_styles/1024px/README +3 -0
  11. data/data/webgen/website_styles/1024px/default.css +3 -3
  12. data/data/webgen/website_styles/andreas00/README +4 -1
  13. data/data/webgen/website_styles/andreas00/default.css +2 -2
  14. data/data/webgen/website_styles/andreas01/README +3 -0
  15. data/data/webgen/website_styles/andreas01/default.css +3 -3
  16. data/data/webgen/website_styles/andreas03/README +4 -1
  17. data/data/webgen/website_styles/andreas03/default.css +2 -2
  18. data/data/webgen/website_styles/andreas04/README +3 -0
  19. data/data/webgen/website_styles/andreas04/default.css +2 -2
  20. data/data/webgen/website_styles/andreas05/README +3 -0
  21. data/data/webgen/website_styles/andreas05/default.css +2 -2
  22. data/data/webgen/website_styles/andreas06/README +3 -0
  23. data/data/webgen/website_styles/andreas06/default.css +3 -2
  24. data/data/webgen/website_styles/andreas07/README +3 -0
  25. data/data/webgen/website_styles/andreas07/default.css +4 -4
  26. data/data/webgen/website_styles/andreas08/README +4 -1
  27. data/data/webgen/website_styles/andreas08/default.css +2 -2
  28. data/data/webgen/website_styles/andreas08/default.template +1 -1
  29. data/data/webgen/website_styles/andreas09/README +3 -0
  30. data/data/webgen/website_styles/andreas09/default.css +2 -2
  31. data/data/webgen/website_styles/default/README +1 -1
  32. data/data/webgen/website_styles/default/default.css +1 -1
  33. data/data/webgen/website_styles/plain/README +7 -2
  34. data/data/webgen/website_styles/plain/default.css +2 -2
  35. data/doc/config.yaml +3 -0
  36. data/doc/metainfo.yaml +20 -3
  37. data/doc/src/css/sipttra.rcss +71 -0
  38. data/doc/src/default.template +10 -5
  39. data/doc/src/documentation/howto.page +221 -0
  40. data/doc/src/documentation/plugins/contentconverter/default.page +1 -0
  41. data/doc/src/documentation/plugins/contentconverter/xmlbuilder.page +2 -2
  42. data/doc/src/documentation/plugins/core/configuration.page +6 -0
  43. data/doc/src/documentation/plugins/core/resourcemanager.page +4 -10
  44. data/doc/src/documentation/plugins/file/defaulthandler.page +1 -0
  45. data/doc/src/documentation/plugins/file/sipttrahandler.page +18 -0
  46. data/doc/src/documentation/plugins/file/templatehandler.page +27 -10
  47. data/doc/src/documentation/plugins/file/thumbnailwriter.page +5 -2
  48. data/doc/src/documentation/plugins/htmlvalidator/default.page +1 -0
  49. data/doc/src/documentation/plugins/index.page +2 -2
  50. data/doc/src/documentation/plugins/menustyle/default.page +1 -0
  51. data/doc/src/documentation/plugins/tag/breadcrumbtrail.page +12 -0
  52. data/doc/src/documentation/plugins/tag/customvar.page +11 -0
  53. data/doc/src/documentation/plugins/tag/default.page +1 -0
  54. data/doc/src/documentation/references/index.page +8 -0
  55. data/doc/src/documentation/references/meta_info_reference.page +35 -16
  56. data/doc/src/documentation/references/resource_reference.page +18 -0
  57. data/doc/src/documentation/references/sipttra_format.page +241 -0
  58. data/doc/src/documentation/references/webpage_format.page +4 -0
  59. data/doc/src/examples/example_sites/index.page +41 -0
  60. data/doc/src/examples/example_sites/personal.zip +0 -0
  61. data/doc/src/examples/example_sites/photo_gallery.zip +0 -0
  62. data/doc/src/examples/index.page +2 -1
  63. data/doc/src/js/sipttra.js +7 -0
  64. data/doc/src/news.page +25 -0
  65. data/doc/src/project.todo +91 -0
  66. data/doc/src/sipttra.template +105 -0
  67. data/lib/webgen/cli.rb +31 -1
  68. data/lib/webgen/config.rb +2 -2
  69. data/lib/webgen/plugin.rb +7 -3
  70. data/lib/webgen/plugins/coreplugins/configuration.rb +3 -1
  71. data/lib/webgen/plugins/filehandlers/filehandler.rb +6 -5
  72. data/lib/webgen/plugins/filehandlers/sipttra.rb +73 -0
  73. data/lib/webgen/plugins/filehandlers/template.rb +2 -1
  74. data/lib/webgen/plugins/miscplugins/treewalker.rb +40 -4
  75. data/lib/webgen/plugins/tags/breadcrumbtrail.rb +14 -1
  76. data/lib/webgen/plugins/tags/customvar.rb +54 -0
  77. data/lib/webgen/sipttra_format.rb +343 -0
  78. data/lib/webgen/website.rb +25 -1
  79. data/man/man1/webgen.1 +75 -0
  80. data/setup.rb +861 -607
  81. data/test/fixtures/sample_site/src/test.todo +7 -0
  82. data/test/fixtures/tc_plugin/plugin1.rb +2 -0
  83. data/test/fixtures/tc_sipttra_format/test.sipttra +46 -0
  84. data/test/unittests/tc_filehandler_sipttra.rb +26 -0
  85. data/test/unittests/tc_filehandler_template.rb +2 -1
  86. data/test/unittests/tc_plugin.rb +8 -1
  87. data/test/unittests/tc_sipttra_format.rb +58 -0
  88. data/test/unittests/tc_tags_breadcrumbtrail.rb +34 -0
  89. data/test/unittests/tc_tags_customvar.rb +24 -0
  90. data/test/unittests/tc_tags_date.rb +1 -0
  91. data/test/unittests/tc_tags_includefile.rb +6 -13
  92. data/test/unittests/tc_treewalker.rb +8 -1
  93. metadata +37 -3
@@ -0,0 +1,73 @@
1
+ #
2
+ #--
3
+ #
4
+ # $Id: gallery.rb 569 2006-12-29 20:11:21Z thomas $
5
+ #
6
+ # webgen: template based static website generator
7
+ # Copyright (C) 2004 Thomas Leitner
8
+ #
9
+ # This program is free software; you can redistribute it and/or modify it under the terms of the GNU
10
+ # General Public License as published by the Free Software Foundation; either version 2 of the
11
+ # License, or (at your option) any later version.
12
+ #
13
+ # This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
14
+ # even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15
+ # General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU General Public License along with this program; if not,
18
+ # write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
+ #
20
+ #++
21
+ #
22
+
23
+ require 'yaml'
24
+ require 'erb'
25
+ require 'webgen/sipttra_format'
26
+
27
+ load_plugin 'webgen/plugins/filehandlers/filehandler'
28
+ load_plugin 'webgen/plugins/filehandlers/page'
29
+
30
+
31
+ module FileHandlers
32
+
33
+ # Handles sipttra (Simple Plain Text Tracker) files.
34
+ class SipttraHandler < DefaultHandler
35
+
36
+ infos( :name => 'File/SipttraHandler',
37
+ :author => Webgen::AUTHOR,
38
+ :summary => "Handles sipttra (Simple Plain Text Tracker) files"
39
+ )
40
+
41
+ register_extension 'todo'
42
+
43
+ default_meta_info( 'template' => '/sipttra.template' )
44
+
45
+ def create_node( file, parent, meta_info )
46
+ begin
47
+ data = File.read( file )
48
+ s = Sipttra::Tracker.new( data )
49
+ rescue
50
+ log(:error) { "Could not parse sipttra file <#{file}>, not creating an output page: #{$!.message}" }
51
+ return
52
+ end
53
+ meta_info.update( s.info['webgen-metainfo'] || {} )
54
+
55
+ filename = File.basename( file, '.todo' ) + '.page'
56
+ filehandler = @plugin_manager['Core/FileHandler']
57
+ pagehandler = @plugin_manager['File/PageHandler']
58
+ node = filehandler.create_node( filename, parent, pagehandler ) do |filename, parent, handler, mi|
59
+ pagehandler.create_node_from_data( filename, parent, "Forgotten to specify a sipttra template?! ;-)", mi.merge( meta_info ) )
60
+ end
61
+ node.node_info[:sipttra] = s if node
62
+ node.node_info[:src] = file if node
63
+
64
+ node
65
+ end
66
+
67
+ def write_node( node )
68
+ # do nothing
69
+ end
70
+
71
+ end
72
+
73
+ end
@@ -1,7 +1,7 @@
1
1
  #
2
2
  #--
3
3
  #
4
- # $Id: template.rb 588 2007-01-12 09:55:32Z thomas $
4
+ # $Id: template.rb 593 2007-02-13 19:33:44Z thomas $
5
5
  #
6
6
  # webgen: template based static website generator
7
7
  # Copyright (C) 2004 Thomas Leitner
@@ -36,6 +36,7 @@ module FileHandlers
36
36
 
37
37
  register_extension 'template'
38
38
 
39
+ default_meta_info( 'blocks' => [['content', 'html']] )
39
40
 
40
41
  def create_node( src_name, parent, meta_info )
41
42
  begin
@@ -1,7 +1,7 @@
1
1
  #
2
2
  #--
3
3
  #
4
- # $Id: treewalker.rb 531 2006-11-17 17:56:14Z thomas $
4
+ # $Id: treewalker.rb 620 2007-02-28 09:19:15Z thomas $
5
5
  #
6
6
  # webgen: template based static website generator
7
7
  # Copyright (C) 2004 Thomas Leitner
@@ -28,13 +28,25 @@ module TreeWalkers
28
28
  # so that it is called when the main class' #execute method is called.
29
29
  class TreeWalker < Webgen::Plugin
30
30
 
31
- infos( :author => Webgen::AUTHOR,
31
+ infos( :name => 'Misc/TreeWalker',
32
+ :author => Webgen::AUTHOR,
32
33
  :summary => "Super plugin for traversing the node tree"
33
34
  )
34
35
 
36
+ attr_reader :walkers
37
+
38
+ def initialize( plugin_manager )
39
+ super
40
+ @walkers = []
41
+ end
42
+
35
43
  # Walks the +tree+ for the +walker+ in the +direction+, either +:forward+ or +:backward+.
36
- def execute( tree, walker, direction = :forward )
37
- walk_tree( tree, walker, 0, direction )
44
+ # If +walker+ is +nil+, then all registered walkers are used!
45
+ def execute( tree, walker = nil, direction = :forward )
46
+ walkers = ( walker.nil? ? @walkers : [walker] )
47
+ walkers.each do |walker|
48
+ walk_tree( tree, walker, 0, direction )
49
+ end
38
50
  end
39
51
 
40
52
  #######
@@ -52,4 +64,28 @@ module TreeWalkers
52
64
 
53
65
  end
54
66
 
67
+
68
+ # Prints the whole tree of read files if the log level is at least DEBUG.
69
+ class DebugTreePrinter < Webgen::Plugin
70
+
71
+ infos( :name => 'Misc/DebugTreePrinter',
72
+ :author => Webgen::AUTHOR,
73
+ :summary => "Prints out the information in the tree for debug purposes."
74
+ )
75
+
76
+ depends_on 'Misc/TreeWalker'
77
+
78
+ def initialize( plugin_manager )
79
+ super
80
+ @plugin_manager['Misc/TreeWalker'].walkers << self
81
+ end
82
+
83
+ def call( node, level )
84
+ log(:debug) do
85
+ " "*level << "\\_ "*(level > 0 ? 1 : 0) << "#{node['title']}: #{node.node_info[:src]} -> #{node.path}"
86
+ end
87
+ end
88
+
89
+ end
90
+
55
91
  end
@@ -1,7 +1,7 @@
1
1
  #
2
2
  #--
3
3
  #
4
- # $Id: breadcrumbtrail.rb 561 2006-12-28 07:41:28Z thomas $
4
+ # $Id: breadcrumbtrail.rb 595 2007-02-14 07:43:28Z thomas $
5
5
  #
6
6
  # webgen: template based static website generator
7
7
  # Copyright (C) 2004 Thomas Leitner
@@ -40,6 +40,8 @@ module Tags
40
40
  )
41
41
 
42
42
  param 'separator', ' / ', 'Separates the hierachy entries from each other.'
43
+ param 'omitLast', false, 'Omits the last path component.'
44
+ param 'omitIndexFile', false, 'Omits the last path component if it is an index file.'
43
45
 
44
46
  register_tag 'breadcrumbTrail'
45
47
 
@@ -47,11 +49,22 @@ module Tags
47
49
  out = []
48
50
  node = chain.last
49
51
 
52
+ omitIndexFile = if node.meta_info.has_key?( 'omitIndexFileInBreadcrumbTrail' )
53
+ node['omitIndexFileInBreadcrumbTrail']
54
+ else
55
+ param( 'omitIndexFile' )
56
+ end
57
+ omitIndexFile = omitIndexFile && node.parent['indexFile'] &&
58
+ node.parent['indexFile'].node_for_lang( node['lang'] ) == node
59
+
60
+ node = node.parent if omitIndexFile
61
+
50
62
  until node.nil?
51
63
  out.push( node.link_from( chain.last ) )
52
64
  node = node.parent
53
65
  end
54
66
 
67
+ out[0] = '' if param( 'omitLast' ) && !omitIndexFile
55
68
  out = out.reverse.join( param( 'separator' ) )
56
69
  log(:debug) { "Breadcrumb trail for <#{chain.last.node_info[:src]}>: #{out}" }
57
70
  out
@@ -0,0 +1,54 @@
1
+ #
2
+ #--
3
+ #
4
+ # $Id: meta.rb 563 2006-12-29 08:59:41Z thomas $
5
+ #
6
+ # webgen: template based static website generator
7
+ # Copyright (C) 2004 Thomas Leitner
8
+ #
9
+ # This program is free software; you can redistribute it and/or modify it under the terms of the GNU
10
+ # General Public License as published by the Free Software Foundation; either version 2 of the
11
+ # License, or (at your option) any later version.
12
+ #
13
+ # This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
14
+ # even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15
+ # General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU General Public License along with this program; if not,
18
+ # write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
+ #
20
+ #++
21
+ #
22
+
23
+ load_plugin 'webgen/plugins/tags/tag_processor'
24
+
25
+ module Tags
26
+
27
+ class CustomVarTag < DefaultTag
28
+
29
+ infos( :name => 'Tag/CustomVar',
30
+ :author => Webgen::AUTHOR,
31
+ :summary => "Used to output custom variables defined in Core/Configuration:customVars."
32
+ )
33
+
34
+ param 'var', nil, 'The variable of which the value should be retrieved.'
35
+ set_mandatory 'var', true
36
+
37
+ register_tag 'customVar'
38
+
39
+ def process_tag( tag, chain )
40
+ output = ''
41
+ customVars = param( 'customVars', 'Core/Configuration' )
42
+ var = param( 'var' )
43
+
44
+ if customVars.kind_of?( Hash ) && customVars.has_key?( var )
45
+ output = customVars[var]
46
+ else
47
+ log(:warn) { "No custom variable called '#{var}' found in Core/Configuration:customVars (file <#{chain.first.node_info[:src]}>)" }
48
+ end
49
+ output
50
+ end
51
+
52
+ end
53
+
54
+ end
@@ -0,0 +1,343 @@
1
+ #
2
+ #--
3
+ #
4
+ # $Id: gallery.rb 569 2006-12-29 20:11:21Z thomas $
5
+ #
6
+ # webgen: template based static website generator
7
+ # Copyright (C) 2004 Thomas Leitner
8
+ #
9
+ # This program is free software; you can redistribute it and/or modify it under the terms of the GNU
10
+ # General Public License as published by the Free Software Foundation; either version 2 of the
11
+ # License, or (at your option) any later version.
12
+ #
13
+ # This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
14
+ # even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15
+ # General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU General Public License along with this program; if not,
18
+ # write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
+ #
20
+ #++
21
+ #
22
+
23
+ require 'yaml'
24
+ require 'cgi'
25
+
26
+
27
+ module Sipttra
28
+
29
+ # A simple node belonging to a Tracker. One node represents exactly one line of a sipttra file.
30
+ class Node
31
+
32
+ attr_accessor :tracker
33
+
34
+ # Returns the line representation of this node.
35
+ def to_line
36
+ ''
37
+ end
38
+
39
+ end
40
+
41
+
42
+ # Base class for all nodes which deal with simple text lines.
43
+ class TextNode < Node
44
+
45
+ # The text of the node/line.
46
+ attr_accessor :text
47
+
48
+ def initialize( text )
49
+ @text = text
50
+ end
51
+
52
+ def to_line
53
+ @text
54
+ end
55
+
56
+ def to_s
57
+ @text
58
+ end
59
+
60
+ end
61
+
62
+
63
+ # A comment is just a special text node.
64
+ class Comment < TextNode; end
65
+
66
+
67
+ # Represents a category line.
68
+ class Category < Node
69
+
70
+ # The name of the category.
71
+ attr_accessor :name
72
+
73
+ # The type of the category.
74
+ attr_accessor :type
75
+
76
+ def initialize( name, type = nil )
77
+ @name = name
78
+ @type = type
79
+ raise "Category must have a name" if name.nil?
80
+ end
81
+
82
+ # Returns all tickets belonging to this category.
83
+ def tickets
84
+ tickets = []
85
+ i = @tracker.nodes.index( self ) + 1
86
+ while i < @tracker.nodes.length
87
+ line = @tracker.nodes[i]
88
+ break if line.kind_of?( Category )
89
+ tickets << line if line.kind_of?( Ticket )
90
+ i += 1
91
+ end
92
+ tickets
93
+ end
94
+
95
+ def to_line
96
+ '### ' + to_s + ' ###'
97
+ end
98
+
99
+ def to_s
100
+ name + (type.nil? ? '' : ' (' + type + ')')
101
+ end
102
+
103
+ end
104
+
105
+
106
+ # Represents a ticket line.
107
+ class Ticket < Node
108
+
109
+ attr_accessor :name, :due_date, :belongs_to, :summary
110
+
111
+ def initialize( name, due_date, belongs_to, text = '' )
112
+ @name = name
113
+ @due_date = due_date
114
+ @belongs_to = belongs_to
115
+ @summary = text.strip
116
+ end
117
+
118
+ # Returns the category to which this ticket belongs.
119
+ def category
120
+ i = @tracker.nodes.index( self ) - 1
121
+ i -= 1 while i >= 0 && !@tracker.nodes[i].kind_of?( Category )
122
+ (i < 0 ? nil : @tracker.nodes[i])
123
+ end
124
+
125
+ # Returns +true+ if this ticket is closed, ie. if it belongs to a category with type closed.
126
+ def closed?
127
+ category.type == 'closed'
128
+ end
129
+
130
+ # Returns the tickets assigned to this ticket, ie. all sub-tickets. The +type+ parameter can be
131
+ # one of:
132
+ # :open :: all tickets with a type different from +closed+
133
+ # :closed :: all tickets with type +closed+
134
+ # :all :: all tickets independent from type
135
+ def assigned_tickets( type = :all )
136
+ if @name.nil?
137
+ []
138
+ else
139
+ @tracker.tickets.select do |t|
140
+ t.belongs_to == @name &&
141
+ (type == :all || (type == :closed ? t.category.type == 'closed' : t.category.type != 'closed' ))
142
+ end
143
+ end
144
+ end
145
+
146
+ # Returns the detailed description for this ticket.
147
+ def description
148
+ text = []
149
+ line = nil
150
+ i = @tracker.nodes.index( self ) + 1
151
+ while i < @tracker.nodes.length && (line = @tracker.nodes[i]).kind_of?( AdditionalText )
152
+ text << line
153
+ i += 1
154
+ end
155
+ text.join( "\n" ).strip
156
+ end
157
+
158
+ # Returns the whole text for the ticket, ie. the summary and ticket joined by a line separator.
159
+ def all_text
160
+ [@summary, description].join( "\n" )
161
+ end
162
+ alias_method :to_s, :all_text
163
+
164
+ def to_line
165
+ s = '*'
166
+ s << ' ' + name unless name.nil?
167
+ s << ' (' + due_date + ')' unless due_date.nil?
168
+ s << ' [' + belongs_to + ']' unless belongs_to.nil?
169
+ s << (!name.nil? && due_date.nil? && belongs_to.nil? ? ':' : '' ) + (summary.empty? ? '' : ' ' + summary.to_s)
170
+ s
171
+ end
172
+
173
+ end
174
+
175
+
176
+ # Represents a milestone which is a special ticket.
177
+ class Milestone < Ticket
178
+
179
+ def initialize( *args )
180
+ super( *args )
181
+ raise "Milestone must have a name" if @name.nil?
182
+ end
183
+
184
+ # Like assigned_tickets but includes tickets in sub milestones.
185
+ def all_assigned_tickets( type = :all )
186
+ (assigned_tickets( type ) + sub_milestones.collect {|sm| sm.all_assigned_tickets( type )}).flatten
187
+ end
188
+
189
+ # A milestone is closed if all assigned tickets are closed, including the ones from the sub
190
+ # milestones.
191
+ def closed?
192
+ assigned_tickets( :open ).empty? && sub_milestones.all? {|sm| sm.closed?}
193
+ end
194
+
195
+ # Returns all direct sub milestones.
196
+ def sub_milestones
197
+ (@name.nil? ? [] : @tracker.milestones.select {|m| m.belongs_to == @name})
198
+ end
199
+
200
+ end
201
+
202
+
203
+ # Represents additional text lines for tickets. All additional text lines for one ticket are the
204
+ # ticket's description.
205
+ class AdditionalText < TextNode
206
+
207
+ def initialize( text )
208
+ super( text.sub( /^ /, '' ) )
209
+ end
210
+
211
+ def to_line
212
+ (@text.strip.empty? ? '' : ' ' + @text)
213
+ end
214
+
215
+ end
216
+
217
+
218
+ # The tracker is used to parse sipttra files and to change the sipttra data in memory.
219
+ class Tracker
220
+
221
+ IDENT_REGEXP=/\w[-.\w\d]*/
222
+
223
+ DATE_REGEXP=/\((\d\d\d\d-\d\d-\d\d)\)/
224
+ BELONGS_REGEXP=/\[(#{IDENT_REGEXP})\]/
225
+
226
+ TICKET_REGEXP=/^\*(?:\s(#{IDENT_REGEXP})(?=:|\s\[|\s\():?)?(?:\s#{DATE_REGEXP})?(?:\s#{BELONGS_REGEXP})?(?:$|\s(.*)$)/
227
+ CONTENT_REGEXP=/^\s\s(.*)$/
228
+ CATEGORY_REGEXP=/^(#+)\s{1,}([^(]*?)(?:\s*\((\w+)\))?\s{1,}\1$/
229
+
230
+ attr_reader :nodes, :info
231
+
232
+ def initialize( data = nil )
233
+ @nodes = []
234
+ @info = {}
235
+ parse( data ) if data
236
+ end
237
+
238
+ # Parses the given +data+ and fills the tracker with information.
239
+ def parse( data )
240
+ @nodes = []
241
+ @info = {}
242
+ level = 0
243
+
244
+ if data =~ /\A---\n/m
245
+ begin
246
+ index = data.index( "---\n", 4 ) || 0
247
+ @info = YAML.load( data[0...index] )
248
+ data = data[index..-1]
249
+ rescue
250
+ ensure
251
+ @info = {} unless @info.kind_of?( Hash )
252
+ end
253
+ end
254
+
255
+ data.split(/\n/).each do |line|
256
+ case
257
+ when (m = CATEGORY_REGEXP.match( line )) && category( m[2], m[3] ).nil?
258
+ @nodes << Category.new( m[2], m[3] )
259
+ level = 1
260
+
261
+ when level == 0
262
+ @nodes << Comment.new( line )
263
+
264
+ when (m = TICKET_REGEXP.match( line )) && (milestone( m[1] ).nil? && ticket( m[1] ).nil?)
265
+ if @nodes.find_all {|child| child.kind_of?( Category )}.last.type.nil?
266
+ @nodes << Milestone.new( m[1], m[2], m[3], m[4] || '' )
267
+ else
268
+ @nodes << Ticket.new( m[1], m[2], m[3], m[4] || '' )
269
+ end
270
+
271
+ when (@nodes.last.kind_of?( Ticket ) || @nodes.last.kind_of?( AdditionalText )) &&
272
+ (line.empty? || (m = CONTENT_REGEXP.match( line )))
273
+ @nodes << AdditionalText.new( line )
274
+
275
+ else
276
+ @nodes << Comment.new( line )
277
+ end
278
+ @nodes.last.tracker = self
279
+ end
280
+
281
+ end
282
+
283
+ def check_consistency
284
+ # TODO what to check?
285
+ end
286
+
287
+ # If Bluecloth is available +text+ is considered to be in Markdown format and converted
288
+ # to HTML. Otherwise the unchanged text is returned.
289
+ def htmlize( text )
290
+ require 'bluecloth'
291
+ BlueCloth.new( text ).to_html
292
+ rescue
293
+ text
294
+ end
295
+
296
+ # Returns all categories.
297
+ def categories
298
+ @nodes.find_all {|child| child.kind_of?( Category ) && !child.type.nil? }
299
+ end
300
+
301
+ # Returns the category with the given +name+ and +type+.
302
+ def category( name, type )
303
+ categories.find {|cat| cat.name == name && cat.type == type}
304
+ end
305
+
306
+ # Returns all category names.
307
+ def category_names
308
+ categories.collect {|cat| cat.name}.uniq
309
+ end
310
+
311
+ # Returns all milestones.
312
+ def milestones
313
+ @nodes.find_all {|child| child.instance_of?( Milestone ) }
314
+ end
315
+
316
+ # Returns the milestone with the given +name+.
317
+ def milestone( name )
318
+ (name.nil? ? nil : milestones.find {|ms| ms.name == name})
319
+ end
320
+
321
+ # Returns all tickets independent from their categories.
322
+ def tickets
323
+ @nodes.find_all {|child| child.instance_of?( Ticket ) }
324
+ end
325
+
326
+ # Returns the ticket with the given +name+.
327
+ def ticket( name )
328
+ (name.nil? ? nil : tickets.find {|ticket| ticket.name == name})
329
+ end
330
+
331
+ # Returns all tickets for the category +name+ independent from the category type.
332
+ def tickets_for_category( name )
333
+ tickets.select {|t| t.category.name == name}
334
+ end
335
+
336
+ # Returns a string representation of the tracker which can later be used by #parse .
337
+ def to_s
338
+ @info.to_yaml.sub( /^---\s*\n/m, '' ) + "\n\n" + @nodes.collect {|line| line.to_line}.join( "\n" )
339
+ end
340
+
341
+ end
342
+
343
+ end