bitclust-core 0.5.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.
Files changed (127) hide show
  1. data/ChangeLog +2907 -0
  2. data/Gemfile +7 -0
  3. data/README +21 -0
  4. data/Rakefile +20 -0
  5. data/bin/bitclust +14 -0
  6. data/bin/refe +36 -0
  7. data/bitclust-dev.gemspec +33 -0
  8. data/bitclust.gemspec +30 -0
  9. data/config.in +23 -0
  10. data/config.ru +48 -0
  11. data/config.ru.sample +31 -0
  12. data/data/bitclust/catalog/ja_JP.EUC-JP +78 -0
  13. data/data/bitclust/catalog/ja_JP.UTF-8 +78 -0
  14. data/data/bitclust/template.lillia/class +98 -0
  15. data/data/bitclust/template.lillia/class-index +28 -0
  16. data/data/bitclust/template.lillia/doc +48 -0
  17. data/data/bitclust/template.lillia/layout +19 -0
  18. data/data/bitclust/template.lillia/library +129 -0
  19. data/data/bitclust/template.lillia/library-index +32 -0
  20. data/data/bitclust/template.lillia/method +20 -0
  21. data/data/bitclust/template.lillia/rd_file +6 -0
  22. data/data/bitclust/template.offline/class +67 -0
  23. data/data/bitclust/template.offline/class-index +28 -0
  24. data/data/bitclust/template.offline/doc +13 -0
  25. data/data/bitclust/template.offline/function +22 -0
  26. data/data/bitclust/template.offline/function-index +24 -0
  27. data/data/bitclust/template.offline/layout +18 -0
  28. data/data/bitclust/template.offline/library +87 -0
  29. data/data/bitclust/template.offline/library-index +32 -0
  30. data/data/bitclust/template.offline/method +21 -0
  31. data/data/bitclust/template.offline/rd_file +6 -0
  32. data/data/bitclust/template/class +133 -0
  33. data/data/bitclust/template/class-index +30 -0
  34. data/data/bitclust/template/doc +14 -0
  35. data/data/bitclust/template/function +21 -0
  36. data/data/bitclust/template/function-index +25 -0
  37. data/data/bitclust/template/layout +19 -0
  38. data/data/bitclust/template/library +89 -0
  39. data/data/bitclust/template/library-index +35 -0
  40. data/data/bitclust/template/method +24 -0
  41. data/data/bitclust/template/opensearchdescription +10 -0
  42. data/data/bitclust/template/search +57 -0
  43. data/lib/bitclust.rb +9 -0
  44. data/lib/bitclust/app.rb +129 -0
  45. data/lib/bitclust/classentry.rb +425 -0
  46. data/lib/bitclust/compat.rb +39 -0
  47. data/lib/bitclust/completion.rb +531 -0
  48. data/lib/bitclust/crossrubyutils.rb +91 -0
  49. data/lib/bitclust/database.rb +181 -0
  50. data/lib/bitclust/docentry.rb +83 -0
  51. data/lib/bitclust/entry.rb +223 -0
  52. data/lib/bitclust/exception.rb +38 -0
  53. data/lib/bitclust/functiondatabase.rb +115 -0
  54. data/lib/bitclust/functionentry.rb +81 -0
  55. data/lib/bitclust/functionreferenceparser.rb +76 -0
  56. data/lib/bitclust/htmlutils.rb +80 -0
  57. data/lib/bitclust/interface.rb +87 -0
  58. data/lib/bitclust/libraryentry.rb +211 -0
  59. data/lib/bitclust/lineinput.rb +165 -0
  60. data/lib/bitclust/messagecatalog.rb +95 -0
  61. data/lib/bitclust/methoddatabase.rb +401 -0
  62. data/lib/bitclust/methodentry.rb +202 -0
  63. data/lib/bitclust/methodid.rb +209 -0
  64. data/lib/bitclust/methodsignature.rb +82 -0
  65. data/lib/bitclust/nameutils.rb +236 -0
  66. data/lib/bitclust/parseutils.rb +60 -0
  67. data/lib/bitclust/preprocessor.rb +273 -0
  68. data/lib/bitclust/rdcompiler.rb +507 -0
  69. data/lib/bitclust/refsdatabase.rb +66 -0
  70. data/lib/bitclust/requesthandler.rb +330 -0
  71. data/lib/bitclust/ridatabase.rb +349 -0
  72. data/lib/bitclust/rrdparser.rb +522 -0
  73. data/lib/bitclust/runner.rb +143 -0
  74. data/lib/bitclust/screen.rb +554 -0
  75. data/lib/bitclust/searcher.rb +518 -0
  76. data/lib/bitclust/server.rb +59 -0
  77. data/lib/bitclust/simplesearcher.rb +84 -0
  78. data/lib/bitclust/subcommand.rb +746 -0
  79. data/lib/bitclust/textutils.rb +51 -0
  80. data/lib/bitclust/version.rb +3 -0
  81. data/packer.rb +224 -0
  82. data/refe2.gemspec +29 -0
  83. data/server.exe +0 -0
  84. data/server.exy +159 -0
  85. data/server.rb +10 -0
  86. data/setup.rb +1596 -0
  87. data/standalone.rb +193 -0
  88. data/test/run_test.rb +15 -0
  89. data/test/test_bitclust.rb +81 -0
  90. data/test/test_entry.rb +39 -0
  91. data/test/test_functiondatabase.rb +55 -0
  92. data/test/test_libraryentry.rb +31 -0
  93. data/test/test_methoddatabase.rb +81 -0
  94. data/test/test_methodsignature.rb +14 -0
  95. data/test/test_nameutils.rb +324 -0
  96. data/test/test_preprocessor.rb +84 -0
  97. data/test/test_rdcompiler.rb +534 -0
  98. data/test/test_refsdatabase.rb +76 -0
  99. data/test/test_rrdparser.rb +26 -0
  100. data/test/test_runner.rb +102 -0
  101. data/test/test_simplesearcher.rb +48 -0
  102. data/theme/default/images/external.png +0 -0
  103. data/theme/default/rurema.png +0 -0
  104. data/theme/default/style.css +288 -0
  105. data/theme/default/test.css +254 -0
  106. data/theme/lillia/rurema.png +0 -0
  107. data/theme/lillia/style.css +331 -0
  108. data/theme/lillia/test.css +254 -0
  109. data/tools/bc-ancestors.rb +153 -0
  110. data/tools/bc-checkparams.rb +246 -0
  111. data/tools/bc-classes.rb +80 -0
  112. data/tools/bc-convert.rb +165 -0
  113. data/tools/bc-list.rb +63 -0
  114. data/tools/bc-methods.rb +171 -0
  115. data/tools/bc-preproc.rb +42 -0
  116. data/tools/bc-rdoc.rb +343 -0
  117. data/tools/bc-tochm.rb +301 -0
  118. data/tools/bc-tohtml.rb +125 -0
  119. data/tools/bc-tohtmlpackage.rb +241 -0
  120. data/tools/check-signature.rb +19 -0
  121. data/tools/forall-ruby.rb +20 -0
  122. data/tools/gencatalog.rb +69 -0
  123. data/tools/statrefm.rb +98 -0
  124. data/tools/stattodo.rb +150 -0
  125. data/tools/update-database.rb +146 -0
  126. data/view.cgi +6 -0
  127. metadata +222 -0
@@ -0,0 +1,59 @@
1
+ #
2
+ # bitclust/server.rb
3
+ #
4
+ # Copyright (c) 2006-2008 Minero Aoki
5
+ #
6
+ # This program is free software.
7
+ # You can distribute/modify this program under the Ruby License.
8
+ #
9
+
10
+ require 'bitclust/methoddatabase'
11
+ require 'bitclust/functiondatabase'
12
+ require 'bitclust/libraryentry'
13
+ require 'bitclust/classentry'
14
+ require 'bitclust/methodentry'
15
+ require 'bitclust/docentry'
16
+ require 'drb'
17
+ require 'webrick/server'
18
+
19
+ module BitClust
20
+
21
+ class Server
22
+
23
+ def initialize(db)
24
+ @db = db
25
+ end
26
+
27
+ def listen(url, foreground = false)
28
+ WEBrick::Daemon.start unless foreground
29
+ DRb.start_service url, @db
30
+ DRb.thread.join
31
+ end
32
+
33
+ end
34
+
35
+ class Database # reopen
36
+ include DRb::DRbUndumped
37
+ end
38
+
39
+ class Entry # reopen
40
+ include DRb::DRbUndumped
41
+ end
42
+
43
+ class SearchResult # reopen
44
+ include DRb::DRbUndumped
45
+ end
46
+
47
+ end
48
+
49
+ class Object
50
+ def _remote_object?
51
+ false
52
+ end
53
+ end
54
+
55
+ class DRb::DRbObject
56
+ def _remote_object?
57
+ true
58
+ end
59
+ end
@@ -0,0 +1,84 @@
1
+ require 'bitclust/nameutils'
2
+ require 'bitclust/methodid'
3
+
4
+ module BitClust
5
+ module SimpleSearcher
6
+ include NameUtils
7
+
8
+ module_function
9
+
10
+ def search_pattern(db, pat)
11
+ pat = to_pattern(pat)
12
+ return [] if pat.empty? or /\A\s+\z/ =~ pat
13
+ cname, type, mname = parse_method_spec_pattern(pat)
14
+ ret = cs = ms = []
15
+ if cname and not cname.empty?
16
+ if mname
17
+ ms = find_class_method(db, cname, type, mname)
18
+ cs += find_class(db, cname + '::' + mname) if /\A[A-Z]/ =~ mname
19
+ else
20
+ cs = find_class(db, cname)
21
+ end
22
+ elsif type == '$'
23
+ ms = find_special_vars(db, mname)
24
+ else
25
+ ms = find_methods(db, mname)
26
+ end
27
+ ms = ms.sort_by{|e| [e.library.name, e.klass.name] }
28
+ cs = cs.sort_by{|e| [e.library.name] }
29
+ cs + ms
30
+ end
31
+
32
+ def find_class(db, cname)
33
+ db.classes.find_all{|c| /\b#{Regexp.escape(cname)}\w*\z/ =~ c.name }
34
+ end
35
+
36
+ def find_class_method(db, cname, type, mname)
37
+ ret = []
38
+ db.classes.each{|c|
39
+ if /\b#{Regexp.escape(cname)}/ =~ c.name
40
+ ret += c.methods.find_all{|m|
41
+ m.names.any?{|n| /\A#{Regexp.escape(mname)}/ =~ n }
42
+ }
43
+ end
44
+ }
45
+ ret
46
+ end
47
+
48
+ def find_methods(db, mname)
49
+ db.methods.find_all{|m|
50
+ m.names.any?{|n| /\A#{Regexp.escape(mname)}/ =~ n }
51
+ }
52
+ end
53
+
54
+ def find_special_vars(db, mname)
55
+ db.get_class('Kernel').special_variables.find_all{|m|
56
+ m.names.any?{|n| /\A#{Regexp.escape(mname)}/ =~ n }
57
+ }
58
+ end
59
+
60
+ def to_pattern(pat)
61
+ pat = pat.to_str
62
+ pat = pat[/\A\s*(.*?)\s*\z/, 1]
63
+ end
64
+
65
+ def parse_method_spec_pattern(pat)
66
+ if /\s/ =~ pat
67
+ return parse_method_spec_pattern0(pat)
68
+ end
69
+ return pat, nil, nil if /\A[A-Z]\w*\z/ =~ pat
70
+ return nil, '$', $1 if /\$(\S*)/ =~ pat
71
+ _m, _t, _c = pat.reverse.split(/(::|[\#,]\.|\.[\#,]|[\#\.\,])/, 2)
72
+ c = _c.reverse if _c
73
+ t = _t.tr(',', '#').sub(/\#\./, '.#') if _t
74
+ m = _m.reverse
75
+ return c, t, m
76
+ end
77
+
78
+ def parse_method_spec_pattern0(q)
79
+ q = q.scan(/\S+/)[0..1]
80
+ q = q.reverse unless /\A[A-Z]/ =~ q[0]
81
+ return q[0], nil, q[1]
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,746 @@
1
+ require 'pathname'
2
+
3
+ require 'bitclust'
4
+ require 'erb'
5
+ require 'find'
6
+ require 'pp'
7
+ require 'optparse'
8
+ require 'yaml'
9
+
10
+ module BitClust
11
+
12
+ class Subcommand
13
+ def parse(argv)
14
+ @parser.parse! argv
15
+ end
16
+
17
+ def help
18
+ @parser.help
19
+ end
20
+
21
+ # TODO refactor
22
+ def error(message)
23
+ $stderr.puts "#{File.basename($0, '.*')}: error: #{message}"
24
+ exit 1
25
+ end
26
+ end
27
+
28
+
29
+ class InitCommand < Subcommand
30
+
31
+ def initialize
32
+ @parser = OptionParser.new {|opt|
33
+ opt.banner = "Usage: #{File.basename($0, '.*')} init [KEY=VALUE ...]"
34
+ opt.on('--help', 'Prints this message and quit.') {
35
+ puts opt.help
36
+ exit 0
37
+ }
38
+ }
39
+ end
40
+
41
+ STANDARD_PROPERTIES = %w( encoding version )
42
+
43
+ def exec(db, argv)
44
+ db.init
45
+ db.transaction {
46
+ argv.each do |kv|
47
+ k, v = kv.split('=', 2)
48
+ db.propset k, v
49
+ end
50
+ }
51
+ fail = false
52
+ STANDARD_PROPERTIES.each do |key|
53
+ unless db.propget(key)
54
+ $stderr.puts "#{File.basename($0, '.*')}: warning: standard property `#{key}' not given"
55
+ fail = true
56
+ end
57
+ end
58
+ if fail
59
+ $stderr.puts "---- Current Properties ----"
60
+ db.properties.each do |key, value|
61
+ $stderr.puts "#{key}=#{value}"
62
+ end
63
+ end
64
+ end
65
+
66
+ end
67
+
68
+
69
+ class UpdateCommand < Subcommand
70
+
71
+ def initialize
72
+ @root = nil
73
+ @library = nil
74
+ @parser = OptionParser.new {|opt|
75
+ opt.banner = "Usage: #{File.basename($0, '.*')} update [<file>...]"
76
+ opt.on('--stdlibtree=ROOT', 'Process stdlib source directory tree.') {|path|
77
+ @root = path
78
+ }
79
+ opt.on('--library-name=NAME', 'Use NAME for library name in file mode.') {|name|
80
+ @library = name
81
+ }
82
+ opt.on('--help', 'Prints this message and quit.') {
83
+ puts opt.help
84
+ exit 0
85
+ }
86
+ }
87
+ end
88
+
89
+ def parse(argv)
90
+ super
91
+ if not @root and argv.empty?
92
+ error "no input file given"
93
+ end
94
+ end
95
+
96
+ def exec(db, argv)
97
+ db.transaction {
98
+ if @root
99
+ db.update_by_stdlibtree @root
100
+ end
101
+ argv.each do |path|
102
+ db.update_by_file path, @library || guess_library_name(path)
103
+ end
104
+ }
105
+ end
106
+
107
+ private
108
+
109
+ def guess_library_name(path)
110
+ if %r<(\A|/)src/> =~ path
111
+ path.sub(%r<.*(\A|/)src/>, '').sub(/\.rd\z/, '')
112
+ else
113
+ path
114
+ end
115
+ end
116
+
117
+ def get_c_filename(path)
118
+ File.basename(path, '.rd')
119
+ end
120
+
121
+ end
122
+
123
+
124
+ class ListCommand < Subcommand
125
+
126
+ def initialize
127
+ @mode = nil
128
+ @parser = OptionParser.new {|opt|
129
+ opt.banner = "Usage: #{File.basename($0, '.*')} list (--library|--class|--method|--function)"
130
+ opt.on('--library', 'List libraries.') {
131
+ @mode = :library
132
+ }
133
+ opt.on('--class', 'List classes.') {
134
+ @mode = :class
135
+ }
136
+ opt.on('--method', 'List methods.') {
137
+ @mode = :method
138
+ }
139
+ opt.on('--function', 'List functions.') {
140
+ @mode = :function
141
+ }
142
+ opt.on('--help', 'Prints this message and quit.') {
143
+ puts opt.help
144
+ exit 0
145
+ }
146
+ }
147
+ end
148
+
149
+ def parse(argv)
150
+ super
151
+ unless @mode
152
+ error 'one of (--library|--class|--method|--function) is required'
153
+ end
154
+ end
155
+
156
+ def exec(db, argv)
157
+ case @mode
158
+ when :library
159
+ db.libraries.map {|lib| lib.name }.sort.each do |name|
160
+ puts name
161
+ end
162
+ when :class
163
+ db.classes.map {|c| c.name }.sort.each do |name|
164
+ puts name
165
+ end
166
+ when :method
167
+ db.classes.sort_by {|c| c.name }.each do |c|
168
+ c.entries.sort_by {|m| m.id }.each do |m|
169
+ puts m.label
170
+ end
171
+ end
172
+ when :function
173
+ db.functions.sort_by {|f| f.name }.each do |f|
174
+ puts f.name
175
+ end
176
+ else
177
+ raise "must not happen: @mode=#{@mode.inspect}"
178
+ end
179
+ end
180
+
181
+ end
182
+
183
+
184
+ class LookupCommand < Subcommand
185
+
186
+ def initialize
187
+ @format = :text
188
+ @type = nil
189
+ @key = nil
190
+ @parser = OptionParser.new {|opt|
191
+ opt.banner = "Usage: #{File.basename($0, '.*')} lookup (--library|--class|--method|--function) [--html] <key>"
192
+ opt.on('--library=NAME', 'Lookup library.') {|name|
193
+ @type = :library
194
+ @key = name
195
+ }
196
+ opt.on('--class=NAME', 'Lookup class.') {|name|
197
+ @type = :class
198
+ @key = name
199
+ }
200
+ opt.on('--method=NAME', 'Lookup method.') {|name|
201
+ @type = :method
202
+ @key = name
203
+ }
204
+ opt.on('--function=NAME', 'Lookup function.') {|name|
205
+ @type = :function
206
+ @key = name
207
+ }
208
+ opt.on('--html', 'Show result in HTML.') {
209
+ @format = :html
210
+ }
211
+ opt.on('--help', 'Prints this message and quit.') {
212
+ puts opt.help
213
+ exit 0
214
+ }
215
+ }
216
+ end
217
+
218
+ def parse(argv)
219
+ super
220
+ unless @type
221
+ error "one of --library/--class/--method/--function is required"
222
+ end
223
+ unless argv.empty?
224
+ error "too many arguments"
225
+ end
226
+ end
227
+
228
+ def exec(db, argv)
229
+ entry = fetch_entry(db, @type, @key)
230
+ puts fill_template(get_template(@type, @format), entry)
231
+ end
232
+
233
+ def fetch_entry(db, type, key)
234
+ case type
235
+ when :library
236
+ db.fetch_library(key)
237
+ when :class
238
+ db.fetch_class(key)
239
+ when :method
240
+ db.fetch_method(BitClust::MethodSpec.parse(key))
241
+ when :function
242
+ db.fetch_function(key)
243
+ else
244
+ raise "must not happen: #{type.inspect}"
245
+ end
246
+ end
247
+
248
+ def fill_template(template, entry)
249
+ ERB.new(template).result(binding())
250
+ end
251
+
252
+ def get_template(type, format)
253
+ template = TEMPLATE[type][format]
254
+ BitClust::TextUtils.unindent_block(template.lines).join('')
255
+ end
256
+
257
+ TEMPLATE = {
258
+ :library => {
259
+ :text => <<-End,
260
+ type: library
261
+ name: <%= entry.name %>
262
+ classes: <%= entry.classes.map {|c| c.name }.sort.join(', ') %>
263
+ methods: <%= entry.methods.map {|m| m.name }.sort.join(', ') %>
264
+
265
+ <%= entry.source %>
266
+ End
267
+ :html => <<-End
268
+ <dl>
269
+ <dt>type</dt><dd>library</dd>
270
+ <dt>name</dt><dd><%= entry.name %></dd>
271
+ <dt>classes</dt><dd><%= entry.classes.map {|c| c.name }.sort.join(', ') %></dd>
272
+ <dt>methods</dt><dd><%= entry.methods.map {|m| m.name }.sort.join(', ') %></dd>
273
+ </dl>
274
+ <%= compile_rd(entry.source) %>
275
+ End
276
+ },
277
+ :class => {
278
+ :text => <<-End,
279
+ type: class
280
+ name: <%= entry.name %>
281
+ library: <%= entry.library.name %>
282
+ singleton_methods: <%= entry.singleton_methods.map {|m| m.name }.sort.join(', ') %>
283
+ instance_methods: <%= entry.instance_methods.map {|m| m.name }.sort.join(', ') %>
284
+ constants: <%= entry.constants.map {|m| m.name }.sort.join(', ') %>
285
+ special_variables: <%= entry.special_variables.map {|m| '$' + m.name }.sort.join(', ') %>
286
+
287
+ <%= entry.source %>
288
+ End
289
+ :html => <<-End
290
+ <dl>
291
+ <dt>type</dt><dd>class</dd>
292
+ <dt>name</dt><dd><%= entry.name %></dd>
293
+ <dt>library</dt><dd><%= entry.library.name %></dd>
294
+ <dt>singleton_methods</dt><dd><%= entry.singleton_methods.map {|m| m.name }.sort.join(', ') %></dd>
295
+ <dt>instance_methods</dt><dd><%= entry.instance_methods.map {|m| m.name }.sort.join(', ') %></dd>
296
+ </dl>
297
+ <%= compile_rd(entry.source) %>
298
+ End
299
+ },
300
+ :method => {
301
+ :text => <<-End,
302
+ type: <%= entry.type %>
303
+ name: <%= entry.name %>
304
+ names: <%= entry.names.sort.join(', ') %>
305
+ visibility: <%= entry.visibility %>
306
+ kind: <%= entry.kind %>
307
+ library: <%= entry.library.name %>
308
+
309
+ <%= entry.source %>
310
+ End
311
+ :html => <<-End
312
+ <dl>
313
+ <dt>type</dt><dd><%= entry.type %></dd>
314
+ <dt>name</dt><dd><%= entry.name %></dd>
315
+ <dt>names</dt><dd><%= entry.names.sort.join(', ') %></dd>
316
+ <dt>visibility</dt><dd><%= entry.visibility %></dd>
317
+ <dt>kind</dt><dd><%= entry.kind %></dd>
318
+ <dt>library</dt><dd><%= entry.library.name %></dd>
319
+ </dl>
320
+ <%= compile_rd(entry.source) %>
321
+ End
322
+ },
323
+ :function => {
324
+ :text => <<-End,
325
+ kind: <%= entry.kind %>
326
+ header: <%= entry.header %>
327
+ filename: <%= entry.filename %>
328
+
329
+ <%= entry.source %>
330
+ End
331
+ :html => <<-End
332
+ <dl>
333
+ <dt>kind</dt><dd><%= entry.kind %></dd>
334
+ <dt>header</dt><dd><%= entry.header %></dd>
335
+ <dt>filename</dt><dd><%= entry.filename %></dd>
336
+ </dl>
337
+ <%= compile_rd(entry.source) %>
338
+ End
339
+ }
340
+ }
341
+
342
+ def compile_rd(src)
343
+ umap = BitClust::URLMapper.new(:base_url => 'http://example.com',
344
+ :cgi_url => 'http://example.com/view')
345
+ compiler = BitClust::RDCompiler.new(umap, 2)
346
+ compiler.compile(src)
347
+ end
348
+
349
+ end
350
+
351
+
352
+ class QueryCommand < Subcommand
353
+
354
+ def initialize
355
+ @parser = OptionParser.new {|opt|
356
+ opt.banner = "Usage: #{File.basename($0, '.*')} query <ruby-script>"
357
+ opt.on('--help', 'Prints this message and quit.') {
358
+ puts opt.help
359
+ exit 0
360
+ }
361
+ }
362
+ end
363
+
364
+ def parse(argv)
365
+ end
366
+
367
+ def exec(db, argv)
368
+ argv.each do |query|
369
+ #pp eval(query) # FIXME: causes ArgumentError
370
+ p eval(query)
371
+ end
372
+ end
373
+ end
374
+
375
+
376
+ class PropertyCommand < Subcommand
377
+
378
+ def initialize
379
+ @mode = nil
380
+ @parser = OptionParser.new {|opt|
381
+ opt.banner = "Usage: #{File.basename($0, '.*')} property [options]"
382
+ opt.on('--list', 'List all properties.') {
383
+ @mode = :list
384
+ }
385
+ opt.on('--get', 'Get property value.') {
386
+ @mode = :get
387
+ }
388
+ opt.on('--set', 'Set property value.') {
389
+ @mode = :set
390
+ }
391
+ opt.on('--help', 'Prints this message and quit.') {
392
+ puts opt.help
393
+ exit 0
394
+ }
395
+ }
396
+ end
397
+
398
+ def parse(argv)
399
+ super
400
+ unless @mode
401
+ error "one of (--list|--get|--set) is required"
402
+ end
403
+ case @mode
404
+ when :list
405
+ unless argv.empty?
406
+ error "--list requires no argument"
407
+ end
408
+ when :get
409
+ ;
410
+ when :set
411
+ unless argv.size == 2
412
+ error "--set requires just 2 arguments"
413
+ end
414
+ else
415
+ raise "must not happen: #{@mode}"
416
+ end
417
+ end
418
+
419
+ def exec(db, argv)
420
+ case @mode
421
+ when :list
422
+ db.properties.each do |key, val|
423
+ puts "#{key}=#{val}"
424
+ end
425
+ when :get
426
+ argv.each do |key|
427
+ puts db.propget(key)
428
+ end
429
+ when :set
430
+ key, val = *argv
431
+ db.transaction {
432
+ db.propset key, val
433
+ }
434
+ else
435
+ raise "must not happen: #{@mode}"
436
+ end
437
+ end
438
+
439
+ end
440
+
441
+ class SetupCommand < Subcommand
442
+
443
+ REPOSITORY_PATH = "http://jp.rubyist.net/svn/rurema/doctree/trunk"
444
+
445
+ def initialize
446
+ @prepare = nil
447
+ @cleanup = nil
448
+ @versions = ["1.8.7", "1.9.2", "1.9.3"]
449
+ @parser = OptionParser.new {|opt|
450
+ opt.banner = "Usage: #{File.basename($0, '.*')} setup [options]"
451
+ opt.on('--prepare', 'Prepare config file and checkout repository. Do not create database.') {
452
+ @prepare = true
453
+ }
454
+ opt.on('--cleanup', 'Cleanup datebase before create database.') {
455
+ @cleanup = true
456
+ }
457
+ opt.on('--versions=V1,V2,...', "Specify versions. [#{@versions.join(',')}]") {|versions|
458
+ @versions = versions.split(",")
459
+ }
460
+ opt.on('--help', 'Prints this message and quit.') {
461
+ puts opt.help
462
+ exit 0
463
+ }
464
+ }
465
+ end
466
+
467
+ def exec(db, argv)
468
+ prepare
469
+ return if @prepare
470
+ @config[:versions].each do |version|
471
+ puts "Generating database for Ruby#{version}..."
472
+ prefix = "#{@config[:database_prefix]}-#{version}"
473
+ FileUtils.rm_ rf(prefix) if @cleanup
474
+ init_argv = ["version=#{version}", "encoding=#{@config[:encoding]}"]
475
+ db = BitClust::MethodDatabase.new(prefix)
476
+ InitCommand.new.exec(db, init_argv)
477
+ update_method_database(prefix, ["--stdlibtree=#{@config[:stdlibtree]}"])
478
+ argv = Pathname(@config[:capi_src]).children.select(&:file?).map{|v| v.realpath.to_s }
479
+ update_function_database(prefix, argv)
480
+ end
481
+ end
482
+
483
+ private
484
+
485
+ def prepare
486
+ home_directory = Pathname(ENV["HOME"])
487
+ config_dir = home_directory + ".bitclust"
488
+ config_dir.mkpath
489
+ config_path = config_dir + "config"
490
+ rubydoc_dir = config_dir + "rubydoc"
491
+ @config = {
492
+ :database_prefix => (config_dir + "db").to_s,
493
+ :encoding => "utf-8",
494
+ :versions => @versions,
495
+ :default_version => @versions.max,
496
+ :stdlibtree => (rubydoc_dir + "refm/api/src").to_s,
497
+ :capi_src => (rubydoc_dir + "refm/capi/src/").to_s,
498
+ :beseurl => "http://localhost:10080",
499
+ :port => "10080",
500
+ :pid_file => "/tmp/bitclust.pid",
501
+ }
502
+ if config_path.exist?
503
+ @config = YAML.load_file(config_path)
504
+ unless @config[:versions] == @versions
505
+ @config[:versions] = @versions
506
+ @config[:default_version] = @versions.max
507
+ end
508
+ generate_config(config_path, @config)
509
+ else
510
+ generate_config(config_path, @config)
511
+ end
512
+ checkout(rubydoc_dir)
513
+ end
514
+
515
+ def generate_config(path, config)
516
+ path.open("w+", 0644) do |file|
517
+ file.puts config.to_yaml
518
+ end
519
+ end
520
+
521
+ def checkout(rubydoc_dir)
522
+ system("svn", "co", REPOSITORY_PATH, rubydoc_dir.to_s)
523
+ end
524
+
525
+ def update_method_database(prefix, argv)
526
+ db = BitClust::MethodDatabase.new(prefix)
527
+ cmd = UpdateCommand.new
528
+ cmd.parse(argv)
529
+ cmd.exec(db, argv)
530
+ end
531
+
532
+ def update_function_database(prefix, argv)
533
+ db = BitClust::FunctionDatabase.new(prefix)
534
+ cmd = UpdateCommand.new
535
+ cmd.parse(argv)
536
+ cmd.exec(db, argv)
537
+ end
538
+
539
+ end
540
+
541
+ class ServerCommand < Subcommand
542
+
543
+ def initialize
544
+ require 'webrick'
545
+ require 'uri'
546
+
547
+ @params = {
548
+ :Port => 10080
549
+ }
550
+ @baseurl = nil
551
+ @dbpath = nil
552
+ @srcdir = @datadir = @themedir = @theme = @templatedir = nil
553
+ @encoding = 'utf-8' # encoding of view
554
+ if Object.const_defined?(:Encoding)
555
+ Encoding.default_external = @encoding
556
+ end
557
+
558
+ @debugp = false
559
+ @autop = false
560
+ @browser = nil
561
+ @pid_file = nil
562
+ @capi = false
563
+
564
+ @parser = OptionParser.new
565
+ @parser.banner = "#{$0} [--bind-address=ADDR] [--port=NUM] --baseurl=URL --database=PATH [--srcdir=PATH] [--datadir=PATH] [--themedir=PATH] [--debug] [--auto] [--browser=BROWSER] [--pid-file=PATH] [--capi]"
566
+ @parser.on('--bind-address=ADDR', 'Bind address') {|addr|
567
+ @params[:BindAddress] = addr
568
+ }
569
+ @parser.on('--port=NUM', 'Listening port number') {|num|
570
+ @params[:Port] = num.to_i
571
+ }
572
+ @parser.on('--baseurl=URL', 'The base URL to host.') {|url|
573
+ @baseurl = url
574
+ }
575
+ @parser.on('--database=PATH', 'MethodDatabase root directory.') {|path|
576
+ @dbpath = path
577
+ }
578
+ @parser.on('--srcdir=PATH', 'BitClust source directory.') {|path|
579
+ @set_srcdir.call path
580
+ }
581
+ @parser.on('--datadir=PATH', 'BitClust data directory.') {|path|
582
+ @datadir = path
583
+ }
584
+ @parser.on('--templatedir=PATH', 'Template directory.') {|path|
585
+ @templatedir = path
586
+ }
587
+ @parser.on('--themedir=PATH', 'BitClust theme directory.') {|path|
588
+ @themedir = path
589
+ }
590
+ @parser.on('--theme=THEME', 'BitClust theme.') {|th|
591
+ @theme = th
592
+ }
593
+ @parser.on('--[no-]debug', 'Debug mode.') {|flag|
594
+ @debugp = flag
595
+ }
596
+ @parser.on('--[no-]auto', 'Auto mode.') {|flag|
597
+ @autop = flag
598
+ }
599
+ @parser.on('--browser=BROWSER', 'Open with the browser.') {|path|
600
+ @browser = path
601
+ }
602
+ @parser.on('--pid-file=PATH', 'Write pid of the daemon to the specified file.') {|path|
603
+ @pid_file = path
604
+ }
605
+ @parser.on('--help', 'Prints this message and quit.') {
606
+ puts @parser.help
607
+ exit 0
608
+ }
609
+ @parser.on('--capi', 'see also FunctionDatabase.') {|path|
610
+ @capi = true
611
+ }
612
+ end
613
+
614
+ def parse(argv)
615
+ super
616
+ load_config_file
617
+ set_srcdir(srcdir_root) unless @srcdir
618
+
619
+ unless @baseurl
620
+ $stderr.puts "missing base URL. Use --baseurl or check ~/.bitclust/config"
621
+ exit 1
622
+ end
623
+ unless @dbpath || @autop
624
+ $stderr.puts "missing database path. Use --database"
625
+ exit 1
626
+ end
627
+ unless @datadir
628
+ $stderr.puts "missing datadir. Use --datadir"
629
+ exit 1
630
+ end
631
+ unless @themedir
632
+ $stderr.puts "missing themedir. Use --themedir"
633
+ exit 1
634
+ end
635
+ if @pid_file
636
+ if File.exist?(@pid_file)
637
+ $stderr.puts "There is still #{pid_file}. Is another process running?"
638
+ exit 1
639
+ end
640
+ @pid_file = File.expand_path(@pid_file)
641
+ end
642
+ end
643
+
644
+ def exec(db, argv)
645
+ require 'bitclust/app'
646
+ if @debugp
647
+ @params[:Logger] = WEBrick::Log.new($stderr, WEBrick::Log::DEBUG)
648
+ @params[:AccessLog] = [
649
+ [ $stderr, WEBrick::AccessLog::COMMON_LOG_FORMAT ],
650
+ [ $stderr, WEBrick::AccessLog::REFERER_LOG_FORMAT ],
651
+ [ $stderr, WEBrick::AccessLog::AGENT_LOG_FORMAT ],
652
+ ]
653
+ else
654
+ @params[:Logger] = WEBrick::Log.new($stderr, WEBrick::Log::INFO)
655
+ @params[:AccessLog] = []
656
+ end
657
+ basepath = URI.parse(@baseurl).path
658
+ server = WEBrick::HTTPServer.new(@params)
659
+
660
+ if @autop
661
+ app = BitClust::App.new(
662
+ :dbpath => Dir.glob("db-*"),
663
+ :baseurl => @baseurl,
664
+ :datadir => @datadir,
665
+ :templatedir => @templatedir,
666
+ :theme => @theme,
667
+ :encoding => @encoding,
668
+ :capi => @capi
669
+ )
670
+ app.interfaces.each do |version, interface|
671
+ server.mount File.join(basepath, version), interface
672
+ end
673
+ server.mount(File.join(basepath, '/'), app)
674
+ else
675
+ viewpath = File.join(basepath, 'view')
676
+ app = BitClust::App.new(
677
+ :viewpath => viewpath,
678
+ :dbpath => @dbpath,
679
+ :baseurl => @baseurl,
680
+ :datadir => @datadir,
681
+ :templatedir => @templatedir,
682
+ :theme => @theme,
683
+ :encoding => @encoding,
684
+ :capi => @capi
685
+ )
686
+ app.interfaces.each do |viewpath, interface|
687
+ server.mount viewpath, interface
688
+ end
689
+ # Redirect from '/' to "#{viewpath}/"
690
+ server.mount('/', app)
691
+ end
692
+
693
+ server.mount File.join(basepath, 'theme/'), WEBrick::HTTPServlet::FileHandler, @themedir
694
+
695
+ if @debugp
696
+ trap(:INT) { server.shutdown }
697
+ else
698
+ WEBrick::Daemon.start do
699
+ trap(:TERM) {
700
+ server.shutdown
701
+ begin
702
+ File.unlink @pid_file if @pid_file
703
+ rescue Errno::ENOENT
704
+ end
705
+ }
706
+ File.open(@pid_file, 'w') {|f| f.write Process.pid } if @pid_file
707
+ end
708
+ end
709
+ exit if $".include?("exerb/mkexy.rb")
710
+ if @autop && !@browser
711
+ case RUBY_PLATFORM
712
+ when /mswin/
713
+ @browser = "start"
714
+ end
715
+ end
716
+ system("#{browser} http://localhost:#{params[:Port]}/") if @browser
717
+ server.start
718
+ end
719
+
720
+ private
721
+
722
+ def srcdir_root
723
+ Pathname.new(__FILE__).realpath.dirname.parent.parent.cleanpath
724
+ end
725
+
726
+ def set_srcdir(dir)
727
+ @srcdir ||= dir
728
+ @datadir ||= "#{@srcdir}/data/bitclust"
729
+ @themedir ||= "#{@srcdir}/theme"
730
+ end
731
+
732
+ def load_config_file
733
+ home_directory = Pathname(ENV['HOME'])
734
+ config_path = home_directory + ".bitclust/config"
735
+ if config_path.exist?
736
+ config = YAML.load_file(config_path)
737
+ @baseurl ||= config[:baseurl]
738
+ @dbpath ||= "#{config[:database_prefix]}-#{config[:default_version]}"
739
+ @port ||= config[:port]
740
+ @pid_file ||= config[:pid_file]
741
+ end
742
+ end
743
+
744
+ end
745
+
746
+ end