bee 0.1.0

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.
@@ -0,0 +1,464 @@
1
+ # Copyright 2006 Michel Casabianca <casa@sweetohm.net>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ # Script to generate documentation.
16
+
17
+ $:.unshift(File.join(File.dirname(__FILE__)))
18
+ require 'bee'
19
+ require 'erb'
20
+ require 'fileutils'
21
+ require 'yaml'
22
+
23
+ module Bee
24
+
25
+ # Module for Bee documentation generation.
26
+ module Doc
27
+
28
+ # Copyright notice.
29
+ COPYRIGHT = 'BeeDoc version 0.1.0 (C) Michel Casabianca - 2006'
30
+ # Console help.
31
+ HELP = 'beedoc [-h] [-o dir] menu
32
+ -h Print help about usage and exit.
33
+ -o Directory where to generate documentation.
34
+ menu Menu file to process.'
35
+ # Exit value on error parsing command line
36
+ EXIT_PARSING_CMDLINE = 1
37
+ # Exit value on doc error
38
+ EXIT_DOC_ERROR = 2
39
+ # Exit value on unknown error
40
+ EXIT_UNKNOWN_ERROR = 3
41
+
42
+ #########################################################################
43
+ # CLASS DOCERROR #
44
+ #########################################################################
45
+
46
+ class DocError < RuntimeError; end
47
+
48
+ module DocErrorMixin
49
+
50
+ # Convenient method to raise a DocError.
51
+ # - message: error message.
52
+ def error(message)
53
+ raise DocError.new(message)
54
+ end
55
+
56
+ end
57
+
58
+ #########################################################################
59
+ # PARSE COMMAND LINE #
60
+ #########################################################################
61
+
62
+ # Parse command line and return arguments.
63
+ def self.parse_command_line
64
+ help = false
65
+ output = nil
66
+ require 'getoptlong'
67
+ begin
68
+ opts = GetoptLong.new(['--help', '-h', GetoptLong::NO_ARGUMENT ],
69
+ ['--output', '-o', GetoptLong::REQUIRED_ARGUMENT])
70
+ opts.each do |opt, arg|
71
+ case opt
72
+ when '--help'
73
+ help = true
74
+ when '--output'
75
+ output = arg
76
+ end
77
+ end
78
+ help = true if ARGV.length > 1
79
+ menu = ARGV[0]
80
+ return [help, output, menu]
81
+ end
82
+ end
83
+
84
+ # Run BeeDoc from command line.
85
+ def self.start_command_line
86
+ begin
87
+ help, output, menu = parse_command_line
88
+ raise "No menu file" if not menu
89
+ rescue
90
+ puts "ERROR: parsing command line (type 'beedoc -h' for help): #{$!}"
91
+ exit(EXIT_PARSING_CMDLINE)
92
+ end
93
+ begin
94
+ if help
95
+ puts COPYRIGHT
96
+ puts HELP
97
+ else
98
+ Bee::Doc::Menu.new(menu).generate(output)
99
+ puts "OK"
100
+ end
101
+ rescue DocError
102
+ puts "ERROR: #{$!}"
103
+ exit(EXIT_DOC_ERROR)
104
+ rescue Exception => e
105
+ puts "ERROR: #{$!}"
106
+ puts e.backtrace.join("\n")
107
+ exit(EXIT_UNKNOWN_ERROR)
108
+ end
109
+ end
110
+
111
+ #########################################################################
112
+ # CLASS MENU #
113
+ #########################################################################
114
+
115
+ class Menu
116
+
117
+ include DocErrorMixin
118
+
119
+ # Title for documentation.
120
+ attr_reader :title
121
+ # Generation date.
122
+ attr_reader :date
123
+ # List of menu entries.
124
+ attr_reader :entries
125
+ # Base directory.
126
+ attr_reader :base
127
+ # Contents for page being generated.
128
+ attr_reader :contents
129
+
130
+ # Constructor.
131
+ # - file: menu file to load.
132
+ def initialize(file)
133
+ begin
134
+ @base = File.dirname(file)
135
+ source = File.read(file)
136
+ menu = YAML::load(source)
137
+ @entries = []
138
+ error "Menu must be a list" unless menu.kind_of?(Array)
139
+ error "Menu description must be first entry" unless
140
+ menu[0].kind_of?(Hash) and menu[0]['menu']
141
+ @title = menu[0]['menu']
142
+ @date = Time.now
143
+ for entry in menu[1..-1]
144
+ @entries << Entry.new(entry, self)
145
+ end
146
+ rescue
147
+ error "Error loading menu file: #{$!}"
148
+ end
149
+ end
150
+
151
+ # Generate HTML documents.
152
+ # - directory: where to generate documentation.
153
+ def generate(directory)
154
+ error "Destination directory '#{directory}' already exists" if
155
+ File.exists?(directory)
156
+ FileUtils.makedirs(directory)
157
+ write_style_sheet = false
158
+ for entry in @entries
159
+ entry.generate(directory, @date)
160
+ write_style_sheet = true if not entry.template
161
+ end
162
+ if write_style_sheet
163
+ File.open(File.join(directory, 'stylesheet.css'), 'w') do |file|
164
+ file.write(Entry::DEFAULT_STYLESHEET)
165
+ end
166
+ end
167
+ end
168
+
169
+ end
170
+
171
+ #########################################################################
172
+ # CLASS ENTRY #
173
+ #########################################################################
174
+
175
+ # A menu entry.
176
+ class Entry
177
+
178
+ include BuildErrorMixin
179
+
180
+ # Parent menu.
181
+ attr_reader :menu
182
+ # Title.
183
+ attr_reader :title
184
+ # File.
185
+ attr_reader :file
186
+ # Destination file.
187
+ attr_reader :dest
188
+ # Directory.
189
+ attr_reader :dir
190
+ # File type.
191
+ attr_reader :type
192
+ # Table of contents.
193
+ attr_reader :toc
194
+ # Header depth for toc.
195
+ attr_reader :header
196
+ # Template for generation.
197
+ attr_reader :template
198
+ # Generation date.
199
+ attr_reader :date
200
+
201
+ # Constructor.
202
+ # - entry: menu entry as a Hash.
203
+ # - menu: the root menu for the entry.
204
+ def initialize(entry, menu)
205
+ # check keys in entry
206
+ error "Unknown entry type '#{entry['type']}'" unless
207
+ ['html', 'text', 'dir', 'link'].member?(entry['type'])
208
+ error "A menu entry must be a Hash" unless entry.kind_of?(Hash)
209
+ error "'title' key is mandatory in menu entries" unless entry['title']
210
+ error "One of 'file' or 'dir' keys must be in a menu entry" unless
211
+ (entry['file'] or entry['dir']) or entry['type'] == 'link'
212
+ error "'file' and 'dir' keys can't be in a menu entry at the " +
213
+ "same time" if entry['file'] and entry['dir']
214
+ # save keys into fields
215
+ @menu = menu
216
+ @title = entry['title']
217
+ @file = entry['file']
218
+ @dest = entry['dest']
219
+ @dir = entry['dir']
220
+ @type = entry['type']
221
+ @toc = entry['toc']
222
+ @header = entry['header']
223
+ @template = entry['template']
224
+ end
225
+
226
+ # Generate a menu entry.
227
+ # - directory: destination directory.
228
+ # - date: generation date.
229
+ def generate(directory, date)
230
+ return if type == 'link'
231
+ @date = date
232
+ # load template
233
+ if @template
234
+ template = ERB.new(File.read(@template))
235
+ else
236
+ template = ERB.new(DEFAULT_TEMPLATE)
237
+ end
238
+ if @dir
239
+ # entry is a directory: copy it to destination directory
240
+ puts "Copying directory '#{@dir}'..."
241
+ FileUtils.cp_r(File.join(@menu.base, @dir),
242
+ File.join(directory, dest))
243
+ else
244
+ # entry is a regular file: generate documentation with template
245
+ puts "Generating '#{@dest}'..."
246
+ @contents = File.read(File.join(@menu.base, @file))
247
+ @contents = '<pre>' + escape_special_characters(@contents) +
248
+ '</pre>' if text?
249
+ result = template.result(get_binding)
250
+ File.open(File.join(directory, @dest), 'w') do |file|
251
+ file.write(result)
252
+ end
253
+ end
254
+ end
255
+
256
+ private
257
+
258
+ # Tells if entry is a text one.
259
+ def text?
260
+ return type == 'text'
261
+ end
262
+
263
+ # Get a binding for ERB generation.
264
+ def get_binding
265
+ return binding
266
+ end
267
+
268
+ # Escape HTML special characters (such as & " ' < and >).
269
+ # - string: string to process.
270
+ # Return: processed string.
271
+ def escape_special_characters(string)
272
+ return string.gsub(/&/, '&amp;').gsub(/"/, '&quot;').
273
+ gsub(/'/, '&apos;').gsub(/</, '&lt;').gsub(/>/, '&gt;')
274
+ end
275
+
276
+ # Default ERB template to generate HTML.
277
+ DEFAULT_TEMPLATE = '<html>
278
+ <head>
279
+ <title><%= @menu.title %></title>
280
+ <link rel="stylesheet" type="text/css" href="stylesheet.css">
281
+ </head>
282
+ <body marginwidth="0" marginheight="0">
283
+ <table class="frame" width="100%" height="100%">
284
+ <tr>
285
+ <td class="frame" colspan="2"
286
+ align="right" valign="middle"
287
+ height="70">
288
+ <h1 class="title"><b><%= @menu.title %>&#xA0;&#xA0;</b></h1>
289
+ </td>
290
+ </tr>
291
+ <tr>
292
+ <td class="ruller" colspan="2"
293
+ align="left" valign="middle"
294
+ height="10">
295
+ <font size="-1">Generated on <%= @date.strftime("%Y-%m-%d %H:%M:%S") %></font>
296
+ </td>
297
+ </tr>
298
+ <tr>
299
+ <td class="menu"
300
+ align="left" valign="top"
301
+ width="150">
302
+ <%
303
+ for entry in @menu.entries
304
+ link = entry.dest
305
+ link += \'/index.html\' if entry.dir
306
+ %>
307
+ <a class="menu" href="<%= link %>"><%= entry.title %></a><br>
308
+ <%
309
+ end
310
+ %>
311
+ </td>
312
+ <td class="text"
313
+ align="left" valign="top">
314
+ <center><h1 class="title"><b><%= @title %></b></h1></center>
315
+ <%= @contents %>
316
+ </td>
317
+ </tr>
318
+ </table>
319
+ </body>
320
+ </html>'
321
+
322
+ public
323
+
324
+ # Default style sheet.
325
+ DEFAULT_STYLESHEET = '
326
+ h1,h2,h3 {
327
+ font-family: Verdana, Helvetica, Arial, sans-serif;
328
+ color: #FF9000;
329
+ }
330
+
331
+ h1.title {
332
+ font-family: Verdana, Helvetica, Arial, sans-serif;
333
+ font-size: 30pt;
334
+ color: #000000
335
+ }
336
+
337
+ table.frame {
338
+ border-width: 0px;
339
+ border-spacing: 0px;
340
+ }
341
+
342
+ td.frame {
343
+ padding: 0px;
344
+ text-align: right;
345
+ border: thin solid #OOO000;
346
+ }
347
+
348
+ td.text {
349
+ padding: 10px;
350
+ font-family: Verdana, Helvetica, Arial, sans-serif;
351
+ font-size: 10pt;
352
+ }
353
+
354
+ td.ruller {
355
+ padding: 3px;
356
+ background-color: #000000;
357
+ color: #FFFFFF;
358
+ font-family: Verdana, Helvetica, Arial, sans-serif;
359
+ font-size: 10pt;
360
+ }
361
+
362
+ td.menu {
363
+ padding: 10px;
364
+ background-color: #FF9000;
365
+ font-family: Verdana, Helvetica, Arial, sans-serif;
366
+ font-size: 12pt;
367
+ }
368
+
369
+ table.toc {
370
+ text-align: left;
371
+ border-width: 0pt;
372
+ background-color: #F9F9F9;
373
+ font-family: Verdana, Helvetica, Arial, sans-serif;
374
+ font-size: 10pt;
375
+ }
376
+
377
+ p {
378
+ font-family: Verdana, Helvetica, Arial, sans-serif;
379
+ font-size: 10pt;
380
+ }
381
+
382
+ table {
383
+ font-family: Verdana, Helvetica, Arial, sans-serif;
384
+ font-size: 10pt;
385
+ border: medium solid #000000;
386
+ border-collapse: collapse;
387
+ }
388
+
389
+ th {
390
+ padding: 5px;
391
+ background-color: #FF9000;
392
+ border: thin solid #OOO000;
393
+ text-align: left;
394
+ }
395
+
396
+ td {
397
+ padding: 5px;
398
+ border: thin solid #OOO000;
399
+ text-align: left;
400
+ }
401
+
402
+ li {
403
+ font-family: Verdana, Helvetica, Arial, sans-serif;
404
+ font-size: 10pt;
405
+ list-style: square;
406
+ }
407
+
408
+ a:link {
409
+ text-decoration: none;
410
+ color: #FF9000;
411
+ }
412
+
413
+ a:visited {
414
+ text-decoration: none;
415
+ color: #000000;
416
+ }
417
+
418
+ a:active {
419
+ text-decoration: none;
420
+ color: #FFFFFF;
421
+ background-color: #000000;
422
+ }
423
+
424
+ a:hover {
425
+ text-decoration: none;
426
+ color: #FFFFFF;
427
+ background-color: #000000;
428
+ }
429
+
430
+ a.menu:link {
431
+ text-decoration: none;
432
+ color: #000000;
433
+ }
434
+
435
+ a.menu:visited {
436
+ text-decoration: none;
437
+ color: #000000;
438
+ }
439
+
440
+ a.menu:active {
441
+ text-decoration: none;
442
+ color: #000000;
443
+ }
444
+
445
+ a.menu:hover {
446
+ text-decoration: none;
447
+ color: #FFFFFF;
448
+ background-color: #000000;
449
+ }
450
+
451
+ pre.code {
452
+ clear: both;
453
+ overflow: auto;
454
+ background-color: #EFEFEF;
455
+ padding: 0;
456
+ padding: 1.0em;
457
+ }
458
+ '
459
+
460
+ end
461
+
462
+ end
463
+
464
+ end