webgen 0.4.1 → 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
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