bitclust-dev 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.
@@ -0,0 +1,165 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- coding: utf-8 -*-
3
+
4
+ $KCODE = 'UTF-8' unless Object.const_defined?(:Encoding)
5
+
6
+ require 'stringio'
7
+ require 'fileutils'
8
+ require 'tmpdir'
9
+ require 'optparse'
10
+
11
+ def main
12
+ mode = :output
13
+ parser = OptionParser.new
14
+ parser.banner = "Usage: #{File.basename($0, '.*')} [--diff] [file...]"
15
+ parser.on('--diff', 'Show the diff between original file and output') {
16
+ mode = :diff
17
+ }
18
+ parser.on('--inplace', 'edit input files in-place (make backup)') {
19
+ mode = :inplace
20
+ }
21
+ parser.on('--help') {
22
+ puts parser.help
23
+ exit
24
+ }
25
+ begin
26
+ parser.parse!
27
+ rescue OptionParser::ParseError => err
28
+ $stderr.puts err.message
29
+ exit 1
30
+ end
31
+ case mode
32
+ when :output
33
+ do_convert ARGF
34
+ when :diff
35
+ ARGV.each do |path|
36
+ diff_output path
37
+ end
38
+ when :inplace
39
+ ARGV.each do |path|
40
+ inplace_edit path
41
+ end
42
+ else
43
+ raise "must not happen: mode=#{mode.inspect}"
44
+ end
45
+ end
46
+
47
+ def inplace_edit(path)
48
+ str = convert_file(path)
49
+ File.rename path, path + '.bak'
50
+ File.open(path, 'w') {|f|
51
+ f.write str
52
+ }
53
+ end
54
+
55
+ def diff_output(path)
56
+ tmppath = "#{Dir.tmpdir}/bc-convert-diff"
57
+ File.open(tmppath, 'w') {|f|
58
+ f.write convert_file(path)
59
+ }
60
+ system 'diff', '-u', path, tmppath
61
+ ensure
62
+ FileUtils.rm_f tmppath
63
+ end
64
+
65
+ def convert_file(path)
66
+ File.open(path) {|f| convert(f) }
67
+ end
68
+
69
+ def convert(f)
70
+ buf = StringIO.new
71
+ do_convert f, buf
72
+ buf.string
73
+ end
74
+
75
+ def do_convert(f, out = $stdout)
76
+ f.each do |line|
77
+ case line
78
+ when /\A\#@/
79
+ out.puts line
80
+ when /\A\#/
81
+ out.puts '#@' + line
82
+ when /\A---\s/
83
+ sig = convert_signature(line.sub(/\A---/, '').sub(/\(\(<.*?>\)\)/i, '').strip)
84
+ out.puts "--- #{sig}"
85
+ if meta = line.slice(/\(\(<.*?>\)\)/i)
86
+ out.puts
87
+ out.puts meta
88
+ out.puts
89
+ end
90
+ else
91
+ out.puts convert_link(line.rstrip)
92
+ end
93
+ end
94
+ end
95
+
96
+ def convert_signature(sig)
97
+ case sig
98
+ when /\A([\w:\.\#]+[?!]?)\s*(?:[\(\{]|--|->|\z)/
99
+ # name(arg), name{}, name,
100
+ # name() -- obsolete
101
+ # name() -> return value type
102
+ sig
103
+ when /\A[\w:]+[\.\#]([+\-<>=~*^&|%\/]+)/ # Complex#+
104
+ sig
105
+ when /\Aself\s*(==|===|=~)\s*(\w+)/ # self == other
106
+ "#{$1}(#{$2})"
107
+ when /\A([\w:\.\#]+)\s*\=(\(|\s*\w+)?/ # name=
108
+ "#{remove_class_spec($1)}=(#{remove_paren($2.to_s.strip)})"
109
+ when /\A\w+\[(.*)\]=(.*)/ # self[key]=
110
+ "[]=(#{$1}, #{$2.strip})"
111
+ when /\A[\w\:]+\[(.*)\]/ # self[key]
112
+ "[](#{$1})"
113
+ when /\Aself\s*([+\-<>=~*^&|%\/]+)\s*(\w+)/ # self + other
114
+ "#{$1}(#{$2})"
115
+ when /\A([+\-~`])\s*\w+/ # ~ self
116
+ case op = $1
117
+ when '+', '-' then op + '@'
118
+ else op
119
+ end
120
+ when /\A(?:[\w:]+[\.\#])?(\[\]=?)/ # Matrix.[](i)
121
+ sig
122
+ when /\A([+\-<>=~*^&|%]+)/ # +(m)
123
+ sig
124
+ when /\A([A-Z]\w+\*)/ # HKEY_*
125
+ sig
126
+ when /\Aself([+\-<>=~*^&|%\/\[\]]+)\(\w/ # self+(other)
127
+ sig.sub(/\Aself/, '')
128
+ else
129
+ $stderr.puts "warning: unknown method signature: #{sig.inspect}"
130
+ sig
131
+ end
132
+ end
133
+
134
+ def remove_class_spec(str)
135
+ str.sub(/\A[A-Z]\w*(?:::[A-Z]\w*)*[\.\#]/, '')
136
+ end
137
+
138
+ def remove_paren(str)
139
+ str.sub(/\A\(/, '').sub(/\)\z/, '')
140
+ end
141
+
142
+ def convert_link(line)
143
+ line.gsub(/\(\(\{(.*?)\}\)\)/) { $1 }\
144
+ .gsub(/\(\(\|(.*?)\|\)\)/) { $1 }\
145
+ .gsub(/\(\(<(.*?)>\)\)/) { convert_href($1) }
146
+ end
147
+
148
+ def convert_href(link)
149
+ case link
150
+ when /\Atrap::(.*)/ then "[[trap:#{$1}]]"
151
+ when /\Aruby 1\.\S+ feature/ then "((<#{link}>))"
152
+ when /\Aobsolete/ then "((<obsolete>))"
153
+ when /\A組み込み変数\/(.*)/u then "[[m:#{$1}]]"
154
+ when /\A組み込み定数\/(.*)/u then "[[m:Kernel::#{$1}]]"
155
+ when /\A組み込み関数\/(.*)/u then "[[m:Kernel\##{$1}]]"
156
+ when /\A([\w:]+[\#\.][^|]+)\|/ then "[[m:#{$1}]]"
157
+ when /\A(.*?)\|manual page\z/ then "[[man:#{$1}]]"
158
+ when /\A([\w:]+)\/(.*)\z/n then "[[m:#{$1}\##{$2}]]"
159
+ when /\A([A-Z][\w:]*)\z/ then "[[c:#{$1}]]"
160
+ else
161
+ "[[unknown:#{link}]]"
162
+ end
163
+ end
164
+
165
+ main
data/tools/bc-list.rb ADDED
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'pathname'
4
+
5
+ bindir = Pathname.new(__FILE__).realpath.dirname
6
+ $LOAD_PATH.unshift((bindir + '../lib').realpath)
7
+
8
+ require 'bitclust'
9
+ require 'optparse'
10
+
11
+ def main
12
+ check_only = false
13
+ parser = OptionParser.new
14
+ parser.banner = "Usage: #{File.basename($0, '.*')} <file>..."
15
+ parser.on('-c', '--check-only', 'Check syntax and output status.') {
16
+ check_only = true
17
+ }
18
+ parser.on('--help', 'Prints this message and quit.') {
19
+ puts parser.help
20
+ exit
21
+ }
22
+ begin
23
+ parser.parse!
24
+ rescue OptionParser::ParseError => err
25
+ $stderr.puts err.message
26
+ exit 1
27
+ end
28
+
29
+ success = true
30
+ ARGV.each do |path|
31
+ begin
32
+ lib = BitClust::RRDParser.parse_stdlib_file(path)
33
+ if check_only
34
+ $stderr.puts "#{path}: OK"
35
+ else
36
+ show_library lib
37
+ end
38
+ rescue BitClust::WriterError => err
39
+ raise if $DEBUG
40
+ $stderr.puts "#{File.basename($0, '.*')}: FAIL: #{err.message}"
41
+ success = false
42
+ end
43
+ end
44
+ exit success
45
+ end
46
+
47
+ def show_library(lib)
48
+ puts "= Library #{lib.name}"
49
+ lib.classes.each do |c|
50
+ puts "#{c.type} #{c.name}"
51
+ c.each do |m|
52
+ puts "\t* #{m.klass.name}#{m.typemark}#{m.names.join(',')}"
53
+ end
54
+ end
55
+ unless lib.methods.empty?
56
+ puts "Additional Methods:"
57
+ lib.methods.each do |m|
58
+ puts "\t* #{m.klass.name}#{m.typemark}#{m.names.join(',')}"
59
+ end
60
+ end
61
+ end
62
+
63
+ main
@@ -0,0 +1,171 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # bc-methods.rb -- list all methods of existing rubys.
4
+ #
5
+ # This program is derived from bc-vdtb.rb, posted in
6
+ # [ruby-reference-manual:160] by sheepman.
7
+ #
8
+
9
+ require 'pathname'
10
+
11
+ bindir = Pathname.new(__FILE__).realpath.dirname
12
+ $LOAD_PATH.unshift((bindir + '../lib').realpath)
13
+
14
+ require 'bitclust'
15
+ require 'bitclust/crossrubyutils'
16
+ require 'optparse'
17
+
18
+ include BitClust::CrossRubyUtils
19
+
20
+ def main
21
+ @requires = []
22
+ @verbose = false
23
+ @ver = RUBY_VERSION
24
+ mode = :list
25
+ target = nil
26
+ opts = OptionParser.new
27
+ opts.banner = "Usage: #{File.basename($0, '.*')} [-r<lib>] <classname>"
28
+ opts.on('-r LIB', 'Requires library LIB') {|lib|
29
+ @requires.push lib
30
+ }
31
+ opts.on('-v', '--verbose', "Prints each ruby's version") {
32
+ @verbose = true
33
+ }
34
+ opts.on('--diff=RDFILE', 'RD file name') {|path|
35
+ mode = :diff
36
+ target = path
37
+ }
38
+ opts.on('-c', '') {
39
+ @content = true
40
+ require 'bitclust/ridatabase'
41
+ }
42
+ opts.on('--ruby=[VER]', "The version of Ruby interpreter"){|ver|
43
+ @ver = ver
44
+ }
45
+ opts.on('--ri-database', 'The path of ri database'){|path|
46
+ @ri_path = path
47
+ }
48
+ opts.on('--help', 'Prints this message and quit.') {
49
+ puts opts.help
50
+ exit 0
51
+ }
52
+ begin
53
+ opts.parse!(ARGV)
54
+ rescue OptionParser::ParseError => err
55
+ $stderr.puts err.message
56
+ exit 1
57
+ end
58
+ unless ARGV.size == 1
59
+ $stderr.puts "wrong number of arguments"
60
+ $stderr.puts opts.help
61
+ exit 1
62
+ end
63
+ classname = ARGV[0]
64
+
65
+ case mode
66
+ when :list
67
+ print_crossruby_table {|ruby| defined_methods(ruby, classname) }
68
+ when :diff
69
+ unless ruby = get_ruby(@ver)
70
+ raise "Not found Ruby interpreter of the given version"
71
+ end
72
+ keys = defined_methods(ruby, classname)
73
+ lib = BitClust::RRDParser.parse_stdlib_file(target, { 'version' => @ver })
74
+ c = lib.fetch_class(classname)
75
+ list0 = lib.classes.find_all{|c0| /\A#{classname}\b/o =~ c0.name }
76
+ list0 = c.entries + list0
77
+ list = list0.map {|ent| ent.labels.map {|n| expand_mf(n) } }.flatten
78
+ if @content
79
+ ri = @ri_path ? RiDatabase.open(@ri_path, nil) : RiDatabase.open_system_db
80
+ ri.current_class = c.name
81
+ mthds = ( ri.singleton_methods + ri.instance_methods )
82
+ fmt = Formatter.new
83
+ (keys - list).sort.each do |name|
84
+ mthd = mthds.find{|m| name == m.fullname }
85
+ if mthd
86
+ puts fmt.method_info(mthd.entry)
87
+ else
88
+ name = name.sub(/\A\w+#/, '')
89
+ puts "--- #{name}\n\#@todo\n\n"
90
+ end
91
+ end
92
+ else
93
+ (keys - list).sort.each do |name|
94
+ puts "-#{name}"
95
+ end
96
+ (list - keys).sort.each do |name|
97
+ puts "+#{name}"
98
+ end
99
+ end
100
+ else
101
+ raise "must not happen: #{mode.inspect}"
102
+ end
103
+ end
104
+
105
+ def expand_mf(n)
106
+ if /\.\#/ =~ n
107
+ [n.sub(/\.\#/, '.'), n.sub(/\.\#/, '#')]
108
+ else
109
+ n
110
+ end
111
+ end
112
+
113
+ def crossrubyutils_sort_entries(ents)
114
+ ents.sort_by {|m| m_order(m) }
115
+ end
116
+
117
+ ORDER = { '.' => 1, '#' => 2, '::' => 3 }
118
+
119
+ def m_order(m)
120
+ m, t, c = *m.reverse.split(/(\#|\.|::)/, 2)
121
+ [ORDER[t] || 0, m.reverse]
122
+ end
123
+
124
+ def defined_methods(ruby, classname)
125
+ req = @requires.map {|lib| "-r#{lib}" }.join(' ')
126
+ avoid_tracer = ""
127
+ avoid_tracer = "Tracer.off" if @requires.include?("tracer")
128
+ if classname == 'Object'
129
+ `#{ruby} #{req} -e '
130
+ c = #{classname}
131
+ c.singleton_methods(false).each do |m|
132
+ puts "#{classname}.\#{m}"
133
+ end
134
+ c.instance_methods(true).each do |m|
135
+ puts "#{classname}\\#\#{m}"
136
+ end
137
+ '`.split
138
+ elsif classname == 'Kernel'
139
+ `#{ruby} #{req} -e '
140
+ c = #{classname}
141
+ c.singleton_methods(true).each do |m|
142
+ puts "#{classname}.\#{m}"
143
+ end
144
+ ( c.private_instance_methods(false) && c.methods(false) ).each do |m|
145
+ puts "#{classname}\\#\#{m}"
146
+ end
147
+ Object::constants.delete_if{|c| cl = Object.const_get(c).class; cl == Class or cl == Module }.each do |m|
148
+ puts "#{classname}::\#{m}"
149
+ end
150
+ global_variables.each do |m|
151
+ puts "#{classname}\#{m}"
152
+ end
153
+ '`.split
154
+ else
155
+ `#{ruby} #{req} -e '
156
+ #{avoid_tracer}
157
+ c = #{classname}
158
+ c.singleton_methods(false).each do |m|
159
+ puts "#{classname}.\#{m}"
160
+ end
161
+ c.instance_methods(false).each do |m|
162
+ puts "#{classname}\\#\#{m}"
163
+ end
164
+ c.ancestors.map {|mod| mod.constants }.inject {|r,n| r-n }.each do |m|
165
+ puts "#{classname}::\#{m}"
166
+ end
167
+ '`.split
168
+ end
169
+ end
170
+
171
+ main
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'pathname'
4
+
5
+ bindir = Pathname.new(__FILE__).realpath.dirname
6
+ $LOAD_PATH.unshift((bindir + '../lib').realpath)
7
+
8
+ require 'bitclust/rrdparser'
9
+ require 'optparse'
10
+
11
+ def main
12
+ params = {"version" => "1.9.0"}
13
+ parser = OptionParser.new
14
+ parser.banner = "Usage: #{File.basename($0, '.*')} <file>..."
15
+ parser.on('--param=KVPAIR', 'Set parameter by key/value pair.') {|kv|
16
+ k, v = kv.split('=', 2)
17
+ params[k] = v
18
+ }
19
+ parser.on('--help', 'Prints this message and quit.') {
20
+ puts parser.help
21
+ exit
22
+ }
23
+ begin
24
+ parser.parse!
25
+ rescue OptionParser::ParseError => err
26
+ $stderr.puts err.message
27
+ exit 1
28
+ end
29
+
30
+ ARGV.each do |path|
31
+ File.open(path) {|f|
32
+ BitClust::Preprocessor.wrap(f, params).each do |line|
33
+ puts line
34
+ end
35
+ }
36
+ end
37
+ rescue BitClust::WriterError => err
38
+ $stderr.puts err.message
39
+ exit 1
40
+ end
41
+
42
+ main
data/tools/bc-rdoc.rb ADDED
@@ -0,0 +1,343 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # bc-rdoc.rb -- handle rdoc (ri) database.
4
+ #
5
+ # "bc-rdoc history" code is derived from bc-history.rb, posted in
6
+ # [ruby-reference-manual:150] by moriq.
7
+ #
8
+
9
+ require 'pathname'
10
+
11
+ srcdir_root = Pathname.new(__FILE__).realpath.dirname.parent.cleanpath
12
+ $LOAD_PATH.unshift srcdir_root + 'lib'
13
+
14
+ require 'bitclust'
15
+ require 'bitclust/ridatabase'
16
+ require 'rdoc/ri/ri_reader'
17
+ require 'rdoc/ri/ri_cache'
18
+ require 'rdoc/ri/ri_paths'
19
+ require 'rdoc/markup/simple_markup/fragments'
20
+ require 'stringio'
21
+ require 'pp'
22
+ require 'optparse'
23
+
24
+ class ApplicationError < StandardError; end
25
+ class RiClassNotFound < ApplicationError; end
26
+
27
+ def main
28
+ Signal.trap(:PIPE) { exit 1 } rescue nil # Win32 does not have SIGPIPE
29
+ Signal.trap(:INT) { exit 1 }
30
+
31
+ parser = OptionParser.new
32
+ parser.banner = <<-EndUsage
33
+ Usage: #{File.basename($0)} (list|diff|history) [options]
34
+
35
+ Subcommands:
36
+ list List methods stored in ri database.
37
+ diff Compare between BitClust and ri database.
38
+ history Show class/method history stored in ri database.
39
+
40
+ Global Options:
41
+ EndUsage
42
+ parser.on('--help', 'Prints this message and quit.') {
43
+ puts parser.help
44
+ exit 0
45
+ }
46
+ begin
47
+ parser.order!
48
+ rescue OptionParser::ParseError => err
49
+ $stderr.puts err.message
50
+ $stderr.puts parser.help
51
+ exit 1
52
+ end
53
+
54
+ subcommands = {
55
+ 'list' => ListCommand.new,
56
+ 'diff' => DiffCommand.new,
57
+ 'history' => HistoryCommand.new
58
+ }
59
+ subcommands['hist'] = subcommands['history']
60
+ unless ARGV[0]
61
+ $stderr.puts 'no subcommand given'
62
+ $stderr.puts parser.help
63
+ exit 1
64
+ end
65
+ unless subcommands.key?(ARGV[0])
66
+ $stderr.puts "unknown subcommand: #{ARGV[0].inspect}"
67
+ $stderr.puts parser.help
68
+ exit 1
69
+ end
70
+ sub = subcommands[ARGV.shift]
71
+ begin
72
+ sub.parse ARGV
73
+ rescue OptionParser::ParseError => err
74
+ $stderr.puts err.message
75
+ $stderr.puts sub.parser.help
76
+ exit 1
77
+ end
78
+ sub.exec
79
+ rescue Errno::EPIPE
80
+ exit 1
81
+ rescue ApplicationError, BitClust::UserError => err
82
+ $stderr.puts err.message
83
+ exit 1
84
+ end
85
+
86
+
87
+ class Subcommand
88
+
89
+ def open_ri_database(prefix)
90
+ if prefix
91
+ RiDatabase.open(prefix, nil)
92
+ else
93
+ RiDatabase.open_system_db
94
+ end
95
+ end
96
+
97
+ end
98
+
99
+
100
+ class ListCommand < Subcommand
101
+
102
+ def initialize
103
+ @prefix = nil
104
+ @type = :name
105
+ @parser = OptionParser.new
106
+ @parser.banner = "Usage: #{File.basename($0, '.*')} list"
107
+ @parser.on('--ri-database=PREFIX', 'Ri database prefix') {|path|
108
+ @prefix = path
109
+ }
110
+ @parser.on('-c', '--content', 'Prints method description') {
111
+ @type = :content
112
+ }
113
+ @parser.on('--help', 'Prints this message and quit.') {
114
+ puts @parser.help
115
+ exit 0
116
+ }
117
+ end
118
+
119
+ attr_reader :parser
120
+
121
+ def parse(argv)
122
+ @parser.parse! argv
123
+ unless argv.size == 1
124
+ $stderr.puts "class name not given"
125
+ exit 1
126
+ end
127
+ @classname = argv[0]
128
+ @ri = open_ri_database(@prefix)
129
+ end
130
+
131
+ def exec
132
+ c = @ri.lookup_class(@classname)
133
+ case @type
134
+ when :name
135
+ c.method_entries.each do |m|
136
+ puts m.fullname
137
+ end
138
+ when :content
139
+ fmt = Formatter.new
140
+ c.method_entries.each do |m|
141
+ puts fmt.method_info(@ri.get_method(m))
142
+ end
143
+ end
144
+ end
145
+
146
+ end
147
+
148
+
149
+ class DiffCommand < Subcommand
150
+
151
+ def initialize
152
+ @bcprefix = nil
153
+ @riprefix = nil
154
+ @type = :name
155
+ @parser = OptionParser.new
156
+ @parser.banner = "Usage: #{File.basename($0, '.*')} diff --bc=PATH --ri=PATH <classname>"
157
+ @parser.on('--bc-database=PREFIX', 'BitClust database prefix') {|path|
158
+ @bcprefix = path
159
+ }
160
+ @parser.on('--ri-database=PREFIX', 'Ri database prefix') {|path|
161
+ @riprefix = path
162
+ }
163
+ @parser.on('-c', '--content', 'Prints method description') {
164
+ @type = :content
165
+ }
166
+ @parser.on('--help', 'Prints this message and quit.') {
167
+ puts @parser.help
168
+ exit 0
169
+ }
170
+ end
171
+
172
+ attr_reader :parser
173
+
174
+ def parse(argv)
175
+ @parser.parse! argv
176
+ unless @bcprefix
177
+ $stderr.puts 'missing BitClust database prefix. Use --bc option'
178
+ exit 1
179
+ end
180
+ @bc = BitClust::MethodDatabase.new(@bcprefix)
181
+ @ri = open_ri_database(@riprefix)
182
+ unless argv.size == 1
183
+ $stderr.puts "wrong number of arguments (#{argv.size} for 1)"
184
+ $stderr.puts @parser.help
185
+ exit 1
186
+ end
187
+ @classname = argv[0]
188
+ end
189
+
190
+ def exec
191
+ @ri.current_class = @classname
192
+ win, lose = *diff_class(bc_lookup_class(@classname), @ri)
193
+ case @type
194
+ when :name
195
+ win.each do |m|
196
+ puts "+ #{m.id}"
197
+ end
198
+ lose.each do |m|
199
+ puts "- #{m.fullname}"
200
+ end
201
+ when :content
202
+ fmt = Formatter.new
203
+ lose.each do |m|
204
+ # puts "\#@\# bc-rdoc: detected missing name: #{m.name}"
205
+ puts fmt.method_info(m.entry)
206
+ end
207
+ end
208
+ end
209
+
210
+ def bc_lookup_class(classname)
211
+ @bc.fetch_class(classname)
212
+ rescue BitClust::ClassNotFound
213
+ $stderr.puts "warning: class #{classname} not exist in BitClust database"
214
+ @bc.get_class(classname)
215
+ end
216
+
217
+ def diff_class(bc, ri)
218
+ unzip(diff_entries(bc, bc_wrap(bc.singleton_methods), ri.singleton_methods),
219
+ diff_entries(bc, bc_wrap(bc.instance_methods), ri.instance_methods))\
220
+ .map {|list| list.flatten }
221
+ end
222
+
223
+ def bc_wrap(ents)
224
+ ents.map {|m|
225
+ m.names.map {|name| BCMethodEntry.new(name, m) }
226
+ }.flatten.uniq
227
+ end
228
+
229
+ def unzip(*tuples)
230
+ [tuples.map {|s, i| s }, tuples.map {|s, i| i }]
231
+ end
232
+
233
+ def diff_entries(bc_class, bc, ri)
234
+ bc = bc.sort
235
+ ri = ri.sort
236
+ [bc - ri, (ri - bc).reject {|m| true_exist?(bc_class, m) }]
237
+ end
238
+
239
+ def true_exist?(c, m)
240
+ if m.singleton_method?
241
+ c.singleton_method?(m.name, true)
242
+ else
243
+ c.instance_method?(m.name, true)
244
+ end
245
+ end
246
+
247
+ end
248
+
249
+
250
+ class HistoryCommand < Subcommand
251
+
252
+ def initialize
253
+ @riprefix = nil
254
+ @parser = OptionParser.new
255
+ @parser.banner = "Usage: #{File.basename($0, '.*')} history --ri=PATH <classname>"
256
+ @parser.on('--ri-database=PREFIX', 'Ri database prefix') {|path|
257
+ @riprefix = path
258
+ }
259
+ @parser.on('--help', 'Prints this message and quit.') {
260
+ puts @parser.help
261
+ exit 0
262
+ }
263
+ end
264
+
265
+ attr_reader :parser
266
+
267
+ def parse(argv)
268
+ @parser.parse! argv
269
+ unless @riprefix
270
+ $stderr.puts 'ri database not given; use --ri option'
271
+ exit 1
272
+ end
273
+ @ris = Dir.glob("#{@riprefix}/1.*").map {|dir|
274
+ RiDatabase.open(dir, File.basename(dir))
275
+ }
276
+ if @ris.empty?
277
+ $stderr.puts 'wrong ri database directory; directories like <path>/1.8.3/, <path>/1.8.4/, ... must exist'
278
+ exit 1
279
+ end
280
+ unless argv.size == 1
281
+ $stderr.puts "wrong number of arguments (#{argv.size} for 1)"
282
+ $stderr.puts @parser.help
283
+ exit 1
284
+ end
285
+ @classname = argv[0]
286
+ end
287
+
288
+ def exec
289
+ @ris.each do |ri|
290
+ ri.current_class = @classname
291
+ end
292
+ s = {}
293
+ i = {}
294
+ @ris.each do |ri|
295
+ ri.singleton_methods.each do |m|
296
+ (s[m] ||= []).push ri.version
297
+ end
298
+ ri.instance_methods.each do |m|
299
+ (i[m] ||= []).push ri.version
300
+ end
301
+ end
302
+ namecols = calculate_n_namecols(s.keys + i.keys)
303
+ versions = @ris.map {|ri| ri.version }
304
+ print_header namecols, versions
305
+ print_records namecols, versions, (s.to_a + i.to_a)
306
+ end
307
+
308
+ def calculate_n_namecols(ms)
309
+ tabstop = 8
310
+ maxnamelen = ms.map {|m| m.fullname.size }.max
311
+ (maxnamelen / tabstop + 1) * tabstop
312
+ end
313
+
314
+ def print_header(namecols, versions)
315
+ print ' ' * namecols
316
+ versions.each do |ver|
317
+ printf '%4s', ver.tr('.', '')
318
+ end
319
+ puts
320
+ end
321
+
322
+ def print_records(namecols, versions, records)
323
+ veridx = {}
324
+ versions.each_with_index do |ver, idx|
325
+ veridx[ver] = idx
326
+ end
327
+ records.sort_by {|m, vers| m.fullname }.each do |m, vers|
328
+ printf "%-#{namecols}s", m.fullname
329
+
330
+ fmt = '%4s' * versions.size
331
+ cols = ['-'] * versions.size
332
+ vers.each do |ver|
333
+ cols[veridx[ver]] = 'o'
334
+ end
335
+ printf fmt, *cols
336
+ puts
337
+ end
338
+ end
339
+
340
+ end
341
+
342
+
343
+ main