1 # $Id: tmdoc.rb,v 1.19 2012/04/17 02:49:40 machan Exp $
2
3 require 'optparse'
4
5 require 'tmdoc/tmstd'
6 require 'tmdoc/constant'
7 require 'tmdoc/environment'
8 require 'tmdoc/transformer/localizable-string'
9 require 'tmdoc/transformer/core-into-module/transformer'
10 require 'tmdoc/transformer/module-into-object/transformer'
11 require 'tmdoc/transformer/object-into-document/transformer'
12 require 'tmdoc/transformer/document-into-docbook/transformer'
13 require 'tmdoc/reader/reader'
14 require 'tmdoc/writer/writer'
15
16
17 module TmDoc
18
19 module Commander
20
21 class Parameter < TmStd::Lsm::Product::Abstract
22 attr_reader :input_dir_name,
23 :input_file_names,
24 :output_dir_name,
25 :log_file_name,
26 :source_dir_name,
27 :document_dir_name,
28 :show_code,
29 :show_module_siblings,
30 :show_class_siblings,
31 :sort_by_name,
32 :book_title,
33 :tab_width
34
35
36 def initialize(
37 input_dir_name,
38 input_file_names,
39 output_dir_name,
40 log_file_name,
41 show_code,
42 show_module_siblings,
43 show_class_siblings,
44 sort_by_name,
45 book_title,
46 tab_width
47 )
48 ASSERT.opt_kind_of input_dir_name, String
49 ASSERT.kind_of input_file_names, LSM::SeqOfString
50 ASSERT.opt_kind_of output_dir_name, String
51 ASSERT.opt_kind_of log_file_name, String
52 ASSERT.boolean show_code
53 ASSERT.boolean show_module_siblings
54 ASSERT.boolean show_class_siblings
55 ASSERT.boolean sort_by_name
56 ASSERT.kind_of book_title, String
57 ASSERT.kind_of tab_width, Integer
58
59 @input_dir_name = input_dir_name
60 @input_file_names = input_file_names
61 @output_dir_name = output_dir_name
62 @log_file_name = log_file_name
63
64 @source_dir_name = nil
65 @document_dir_name = nil
66
67 @show_code = show_code
68 @show_module_siblings = show_module_siblings
69 @show_class_siblings = show_class_siblings
70 @sort_by_name = sort_by_name
71
72 @book_title = book_title
73 @tab_width = tab_width
74 end
75
76
77 def source_dir_name!(source_dir_name)
78 ASSERT.kind_of source_dir_name, String
79
80 @source_dir_name = source_dir_name
81
82 nil
83 end
84
85
86 def document_dir_name!(document_dir_name)
87 ASSERT.kind_of document_dir_name, String
88
89 @document_dir_name = document_dir_name
90
91 nil
92 end
93 end
94
95
96 def Commander.main(argv, &block)
97 ASSERT.kind_of argv, Array
98 ASSERT.opt_kind_of block, Proc
99
100 exit_code = 0
101
102 begin
103 param, env = Commander.parse_arguments argv
104 ASSERT.kind_of param, Parameter
105 ASSERT.kind_of env, ENV::Environment
106
107 TmStd::Logger::File.verbose_level_event!(
108 env.verbose_level_event
109 )
110 TmStd::Logger::File.open(param.log_file_name) do
111 source_dir_name = Commander.check_input_directory(
112 param.input_dir_name
113 )
114 param.source_dir_name! source_dir_name
115
116 document_dir_name = Commander.check_output_directory(
117 param.output_dir_name
118 )
119 param.document_dir_name! document_dir_name
120
121 if block
122 block.call param, env
123 else
124 Commander.execute param, env
125 end
126 end
127 rescue TmStd::Exception::LogfileOpenError
128 exit_code = 1
129 rescue TmDoc::Exception::Abstraction::Exception => exception
130 exit_code =
131 case exception
132 when Exception::NormalEnd; 0
133 when Exception::CommandArgumentError; 1
134 when Exception::OutputError; 2
135 when Exception::SensitivityAbort; 3
136 else
137 ASSERT.abort "Unknown exception: %s", exception.to_s
138 end
139 end
140
141 ASSERT.kind_of exit_code, Integer
142 end
143
144
145 def Commander.parse_arguments(argv)
146 ASSERT.kind_of argv, Array
147
148 input_dir_name = nil
149 output_dir_name = nil
150 log_file_name = nil
151
152 show_code = true
153 show_module_siblings = false
154 show_class_siblings = false
155 sort_by_name = false
156
157 book_title = TLS::BOOK_TITLE
158 tab_width = TAB_WIDTH
159
160 verbose_level_event = LOG::Progress
161 sensitive_level_event = LOG::Fatal
162
163 debug = false
164 debug_model = false
165 debug_parser = false
166 debug_scanner = false
167
168 OptionParser.new { |opts|
169 opts.on(
170 '-i DIRECTORY', '--input-dir DIRECTORY',
171 'Source files directory'
172 ) do |dir_name|
173 input_dir_name = dir_name
174 end
175
176 opts.on(
177 '-o DIRECTORY', '--output-dir DIRECTORY',
178 'Document files directory'
179 ) do |dir_name|
180 output_dir_name = dir_name
181 end
182
183 opts.on(
184 '-t STRING', '--title STRING',
185 'Title of the document'
186 ) do |title|
187 book_title = title
188 end
189
190 opts.on(
191 '-e NUMBER', '--tab-width NUMBER', Integer,
192 'Tab width of source codes(default=8)'
193 ) do |num|
194 unless 1 <= num && num <= 16
195 $stderr.printf(
196 "invalid argument(tab width): %s\n", num.to_s
197 )
198 raise TmDoc::Exception::CommandArgumentError
199 end
200
201 tab_width = num
202 end
203
204 opts.on(
205 '-c', '--[no-]show-code',
206 'Show source code'
207 ) do |answer|
208 show_code = answer
209 end
210
211 opts.on(
212 '--[no-]show-module-siblings',
213 'Show siblings info of module structure'
214 ) do |answer|
215 show_module_siblings = answer
216 end
217
218 opts.on(
219 '--[no-]show-class-siblings',
220 'Show siblings info of class hierarcy'
221 ) do |answer|
222 show_class_siblings = answer
223 end
224
225 opts.on(
226 '-a', '--show-all',
227 'Enable all --show-XXX options'
228 ) do |bool|
229 show_code =
230 show_module_siblings =
231 show_class_siblings = bool
232 end
233
234 opts.on(
235 '--[no-]sort-by-name',
236 'Sort chapter sequence by module/class name'
237 ) do |answer|
238 sort_by_name = answer
239 end
240
241 opts.on(
242 '-v LEVEL', '--verbose-level LEVEL',
243 'LEVEL is "progress", "info", "notice"..etc',
244 {
245 :debug => LOG::Debug,
246 :progress => LOG::Progress,
247 :info => LOG::Information,
248 :notice => LOG::Notice,
249 :warn => LOG::Warning,
250 :error => LOG::Error,
251 :fatal => LOG::Fatal
252 }
253 ) do |level_event|
254 verbose_level_event = level_event
255 end
256
257 opts.on(
258 '--quiet',
259 'Silent mode, equal to "-v fatal"'
260 ) do |bool|
261 verbose_level_event = LOG::Fatal
262 end
263
264 opts.on(
265 '-s LEVEL', '--sensitive-level LEVEL',
266 'LEVEL is "notice", "warn", "error"..etc',
267 {
268 :notice => LOG::Notice,
269 :warn => LOG::Warning,
270 :error => LOG::Error,
271 :fatal => LOG::Fatal
272 }
273 ) do |level_event|
274 sensitive_level_event = level_event
275 end
276
277 opts.on(
278 '-l FILE', '--log-file FILE',
279 'Logging file\'s name'
280 ) do |file_name|
281 log_file_name = file_name
282 end
283
284 opts.on(
285 '--debug-model', 'For debugging'
286 ) do |boolean|
287 debug_model = boolean
288 end
289
290 opts.on(
291 '--debug-parser', 'For debugging'
292 ) do |boolean|
293 debug_parser = boolean
294 end
295
296 opts.on(
297 '--debug-scanner', 'For debugging'
298 ) do |boolean|
299 debug_scanner = boolean
300 end
301
302 opts.on(
303 '--debug', 'For debugging'
304 ) do |boolean|
305 debug = boolean
306 end
307
308 opts.on(
309 '--[no-]assertion', 'For debugging'
310 ) do |boolean|
311 ASSERT.disable!(! boolean)
312 end
313
314 begin
315 opts.banner = 'tmdoc [OPTION ..] SOURCE_FILE ..'
316 opts.version = TMDOC_VERSION
317 opts.parse!(argv)
318 rescue OptionParser::ParseError => exception
319 $stderr.puts exception.to_s
320
321 raise Exception::CommandArgumentError
322 end
323 }
324
325 param = Parameter.new(
326 input_dir_name,
327 LSM::SeqOfString.new(argv),
328 output_dir_name,
329 log_file_name,
330 show_code,
331 show_module_siblings,
332 show_class_siblings,
333 sort_by_name,
334 book_title,
335 tab_width
336 )
337 env = ENV::Environment.new(
338 verbose_level_event,
339 sensitive_level_event,
340 debug,
341 debug_model,
342 debug_parser,
343 debug_scanner
344 )
345
346 ASSERT.tuple_of [param, env], [Parameter, ENV::Environment]
347 end
348
349
350 def Commander.check_input_directory(input_dir_name)
351 ASSERT.opt_kind_of input_dir_name, String
352
353 expand_dir_name = File.expand_path(
354 if input_dir_name
355 input_dir_name
356 else
357 '.'
358 end
359 )
360
361 unless File.exist?(expand_dir_name)
362 LOG::Fatal.log(
363 "No such directory: '%s'", expand_dir_name
364 )
365 raise Exception::CommandArgumentError
366 end
367
368 dir_stat = File.stat(expand_dir_name)
369 ASSERT.kind_of dir_stat, File::Stat
370
371 unless dir_stat.directory?
372 LOG::Fatal.log(
373 "Not a directory: '%s'", expand_dir_name
374 )
375 raise Exception::CommandArgumentError
376 end
377
378 unless dir_stat.readable?
379 LOG::Fatal.log(
380 "Not a readable directory: '%s'", expand_dir_name
381 )
382 raise Exception::CommandArgumentError
383 end
384
385 ASSERT.kind_of expand_dir_name, String
386 end
387
388
389 def Commander.check_output_directory(output_dir_name)
390 ASSERT.opt_kind_of output_dir_name, String
391
392 expand_dir_name = File.expand_path(
393 if output_dir_name
394 output_dir_name
395 else
396 OUTPUT_DIR_NAME
397 end
398 )
399
400 unless File.exist?(expand_dir_name)
401 LOG::Fatal.log(
402 "No such directory: '%s'", expand_dir_name
403 )
404 raise Exception::CommandArgumentError
405 end
406
407 document_dir_stat = File.stat(expand_dir_name)
408 ASSERT.kind_of document_dir_stat, File::Stat
409
410 unless document_dir_stat.directory?
411 LOG::Fatal.log(
412 "Not a directory: '%s'", expand_dir_name
413 )
414 raise Exception::CommandArgumentError
415 end
416
417 unless document_dir_stat.writable?
418 LOG::Fatal.log(
419 "Not a writable directory: '%s'", expand_dir_name
420 )
421 raise Exception::CommandArgumentError
422 end
423
424 ASSERT.kind_of expand_dir_name, String
425 end
426
427
428 def Commander.execute(param, env)
429 ASSERT.kind_of param, Parameter
430 ASSERT.kind_of env, ENV::Environment
431
432 LOG::Information.log 'Starting TmDoc.'
433 LOG::Progress.log
434 started_time = Time.now
435
436
437 GC.enable
438
439 LOG::Information.log 'Reading source files....'
440 LOG::Progress.log
441 mc_store = Reader.read(
442 param.source_dir_name,
443 param.input_file_names,
444 env
445 ) do |reading_file_name|
446 LOG::Progress.log "\t%s", reading_file_name
447 end
448 mc_store.print if env.debug_model?
449 LOG::Progress.log
450
451
452 LOG::Information.log 'Making the module model....'
453 mm_store = Transformer::CoreIntoModule.transform(
454 mc_store,
455 env
456 )
457 mc_store = nil # for GC
458 GC.start
459 mm_store.print if env.debug_model?
460 LOG::Progress.log
461
462
463 LOG::Information.log 'Making the object model....'
464 LOG::Progress.log
465 LOG::Progress.log "\tTransforming...."
466 mo_store = Transformer::ModuleIntoObject.transform(
467 mm_store,
468 env
469 )
470 LOG::Progress.log "\tIndexing...."
471 mo_store.make_indexes!
472 mm_store = nil # for GC
473 GC.start
474 mo_store.print if env.debug_model?
475 LOG::Progress.log
476
477
478 LOG::Information.log 'Making the document model....'
479 LOG::Progress.log
480 md_store = Transformer::ObjectIntoDocument.transform(
481 mo_store,
482 env,
483 param.show_module_siblings,
484 param.show_class_siblings,
485 param.show_code,
486 param.sort_by_name
487 )
488 mo_store = nil # for GC
489 GC.start
490 md_store.print if env.debug_model?
491 LOG::Progress.log
492
493
494 LOG::Information.log 'Making the DocBook model....'
495 LOG::Progress.log
496 mb_store = Transformer::DocumentIntoDocBook.transform(
497 md_store,
498 env,
499 param.book_title,
500 param.source_dir_name,
501 param.tab_width,
502 param.show_code
503 )
504 md_store = nil # for GC
505 GC.start
506 mb_store.print if env.debug_model?
507 LOG::Progress.log
508
509
510 LOG::Information.log 'Writing the document to files....'
511 LOG::Progress.log
512 Writer.write(
513 mb_store,
514 param.document_dir_name,
515 env
516 ) do |writing_file_name|
517 LOG::Progress.log "\t%s", writing_file_name
518 end
519 LOG::Progress.log
520
521
522 elapsed_time = (Time.now - started_time).to_i
523 elapsed_sec = elapsed_time % 60
524 elapsed_min = (elapsed_time / 60).to_i
525 LOG::Information.log(
526 "Finished TmDoc!!\n" +
527 "\telapsed time: %dsec%s",
528 elapsed_time,
529 (
530 if elapsed_min >= 1
531 format(" (%dm%ds)", elapsed_min, elapsed_sec)
532 else
533 ''
534 end
535 )
536 )
537
538 nil
539 end
540
541
542 def Commander.test_sensitive_level(env, level_event)
543 ASSERT.kind_of env, ENV::Environment
544 ASSERT.subclass_of(
545 level_event, TmStd::Logger::Event::Abstract
546 )
547
548 if level_event <= env.sensitive_level_event
549 LOG::Fatal.log 'Sensitivity abort.'
550 raise Exception::SensitivityAbort
551 end
552
553 nil
554 end
555 end
556
557 end # TmDoc
558
559
560 if $0 == __FILE__
561 exit_code = TmDoc::Commander.main(ARGV)
562 exit exit_code
563 end