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.
- data/README +4 -0
- data/bin/bee +6 -0
- data/bin/beedoc +6 -0
- data/doc/html/documentation.html +444 -0
- data/doc/html/quickstart.html +108 -0
- data/doc/png/style.png +0 -0
- data/doc/yml/#releases.yml# +36 -0
- data/doc/yml/menu.yml +49 -0
- data/doc/yml/releases.yml +36 -0
- data/doc/yml/todo.yml +17 -0
- data/lib/bee.rb +916 -0
- data/lib/beedoc.rb +464 -0
- data/test/tc_bee_consoleformatter.rb +46 -0
- data/test/ts_bee.rb +27 -0
- metadata +71 -0
data/lib/beedoc.rb
ADDED
@@ -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(/&/, '&').gsub(/"/, '"').
|
273
|
+
gsub(/'/, ''').gsub(/</, '<').gsub(/>/, '>')
|
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 %>  </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
|