diff options
Diffstat (limited to 'lib')
56 files changed, 5178 insertions, 2267 deletions
diff --git a/lib/Env.rb b/lib/Env.rb index 7101b84c91..452a28659e 100644 --- a/lib/Env.rb +++ b/lib/Env.rb @@ -6,19 +6,7 @@ # $USER = "matz" # p ENV["USER"] -for k,v in ENV - next unless /^[a-zA-Z][_a-zA-Z0-9]*/ =~ k - eval <<EOS - $#{k} = %q!#{v}! - trace_var "$#{k}", proc{|v| - ENV[%q!#{k}!] = v; - $#{k} = %q!#{v}! - if v == nil - untrace_var "$#{k}" - end - } -EOS -end +require 'importenv' if __FILE__ == $0 p $TERM diff --git a/lib/cgi.rb b/lib/cgi.rb index 7d27cecd67..cfbdab8686 100644 --- a/lib/cgi.rb +++ b/lib/cgi.rb @@ -4,7 +4,7 @@ cgi.rb - cgi support library -Version 2.1.2 +Version 2.1.4 Copyright (C) 2000 Network Applied Communication Laboratory, Inc. @@ -185,12 +185,13 @@ class CGI CR = "\015" LF = "\012" EOL = CR + LF - VERSION = "2.1.2" - RELEASE_DATE = "2000-12-25" - VERSION_CODE = 212 - RELEASE_CODE = 20001225 + VERSION = '2.1.4' + RELEASE_DATE = '2001-04-18' + VERSION_CODE = 214 + RELEASE_CODE = 20010418 + REVISION = '$Id$' - NEEDS_BINMODE = true if /WIN/ni === RUBY_PLATFORM + NEEDS_BINMODE = true if /WIN/ni.match(RUBY_PLATFORM) PATH_SEPARATOR = {'UNIX'=>'/', 'WINDOWS'=>'\\', 'MACINTOSH'=>':'} HTTP_STATUS = { @@ -350,11 +351,11 @@ class CGI =begin === MAKE RFC1123 DATE STRING CGI::rfc1123_date(Time.now) - # Sat, 1 Jan 2000 00:00:00 GMT + # Sat, 01 Jan 2000 00:00:00 GMT =end def CGI::rfc1123_date(time) t = time.clone.gmtime - return format("%s, %d %s %d %.2d:%.2d:%.2d GMT", + return format("%s, %.2d %s %.4d %.2d:%.2d:%.2d GMT", RFC822_DAYS[t.wday], t.day, RFC822_MONTHS[t.month-1], t.year, t.hour, t.min, t.sec) end @@ -423,7 +424,8 @@ status: options["type"].concat( options.delete("charset") ) end - if options.delete("nph") or (/IIS/n === env_table['SERVER_SOFTWARE']) + options.delete("nph") if defined?(MOD_RUBY) + if options.delete("nph") or /IIS/n.match(env_table['SERVER_SOFTWARE']) buf.concat( (env_table["SERVER_PROTOCOL"] or "HTTP/1.0") + " " ) buf.concat( (HTTP_STATUS[options["status"]] or options["status"] or @@ -446,7 +448,9 @@ status: end if options.has_key?("status") - buf.concat("Status: " + options.delete("status") + EOL) + status = (HTTP_STATUS[options["status"]] or options["status"]) + buf.concat("Status: " + status + EOL) + options.delete("status") end if options.has_key?("server") @@ -496,8 +500,21 @@ status: } if defined?(MOD_RUBY) - buf.scan(/([^:]+): (.+)#{EOL}/n){ - Apache::request[$1] = $2 + table = Apache::request.headers_out + buf.scan(/([^:]+): (.+)#{EOL}/n){ |name, value| + $stderr.printf("name:%s value:%s\n", name, value) if $DEBUG + case name + when 'Set-Cookie' + table.add($1, $2) + when /^status$/ni + Apache::request.status_line = value + when /^content-type$/ni + Apache::request.content_type = value + when /^content-encoding$/ni + Apache::request.content_encoding = value + else + Apache::request.headers_out[name] = value + end } Apache::request.send_http_header '' @@ -626,13 +643,9 @@ convert string charset, and set language to "ja". # simple support for IE if options["path"] @path = options["path"] - elsif ENV["REQUEST_URI"] - @path = ENV["REQUEST_URI"].sub(/\?.*/n,'') - if ENV["PATH_INFO"] - @path = @path[0...@path.rindex(ENV["PATH_INFO"])] - end else - @path = (ENV["SCRIPT_NAME"] or "") + %r|^(.*/)|.match(ENV["SCRIPT_NAME"]) + @path = ($1 or "") end @domain = options["domain"] @expires = options["expires"] @@ -793,9 +806,9 @@ convert string charset, and set language to "ja". body = Tempfile.new("CGI") body.binmode - until head and (/#{boundary}(?:#{EOL}|--)/n === buf) + until head and /#{boundary}(?:#{EOL}|--)/n.match(buf) - if (not head) and (/#{EOL}#{EOL}/n === buf) + if (not head) and /#{EOL}#{EOL}/n.match(buf) buf = buf.sub(/\A((?:.|\n)*?#{EOL})#{EOL}/n) do head = $1.dup "" @@ -834,14 +847,14 @@ convert string charset, and set language to "ja". end END - /Content-Disposition:.* filename="?([^\";]*)"?/ni === head + /Content-Disposition:.* filename="?([^\";]*)"?/ni.match(head) eval <<-END def body.original_filename #{ filename = ($1 or "").dup - if (/Mac/ni === env_table['HTTP_USER_AGENT']) and - (/Mozilla/ni === env_table['HTTP_USER_AGENT']) and - (not /MSIE/ni === env_table['HTTP_USER_AGENT']) + if /Mac/ni.match(env_table['HTTP_USER_AGENT']) and + /Mozilla/ni.match(env_table['HTTP_USER_AGENT']) and + (not /MSIE/ni.match(env_table['HTTP_USER_AGENT'])) CGI::unescape(filename) else filename @@ -850,14 +863,14 @@ convert string charset, and set language to "ja". end END - /Content-Type: (.*)/ni === head + /Content-Type: (.*)/ni.match(head) eval <<-END def body.content_type #{($1 or "").dump.untaint}.taint end END - /Content-Disposition:.* name="?([^\";]*)"?/ni === head + /Content-Disposition:.* name="?([^\";]*)"?/ni.match(head) name = $1.dup if params.has_key?(name) @@ -889,7 +902,7 @@ convert string charset, and set language to "ja". words = Shellwords.shellwords(string) - if words.find{|x| /=/n === x } + if words.find{|x| /=/n.match(x) } words.join('&') else words.join('+') @@ -899,8 +912,7 @@ convert string charset, and set language to "ja". def initialize_query() if ("POST" == env_table['REQUEST_METHOD']) and - (%r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n === - env_table['CONTENT_TYPE']) + %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n.match(env_table['CONTENT_TYPE']) boundary = $1.dup @params = read_multipart(boundary, Integer(env_table['CONTENT_LENGTH'])) else @@ -975,8 +987,8 @@ convert string charset, and set language to "ja". cgi = CGI.new("html3") # add HTML generation methods cgi.element cgi.element{ "string" } - cgi.element({ "ATTRILUTE1" => "value1", "ATTRIBUTE2" => "value2" }) - cgi.element({ "ATTRILUTE1" => "value1", "ATTRIBUTE2" => "value2" }){ "string" } + cgi.element({ "ATTRIBUTE1" => "value1", "ATTRIBUTE2" => "value2" }) + cgi.element({ "ATTRIBUTE1" => "value1", "ATTRIBUTE2" => "value2" }){ "string" } # add HTML generation methods CGI.new("html3") # html3.2 @@ -1235,8 +1247,10 @@ convert string charset, and set language to "ja". form("get", "url"){ "string" } # <FORM METHOD="get" ACTION="url" ENCTYPE="application/x-www-form-urlencoded">string</FORM> - form({"METHOD" => "post", ENCTYPE => "enctype"}){ "string" } + form({"METHOD" => "post", "ENCTYPE" => "enctype"}){ "string" } # <FORM METHOD="post" ENCTYPE="enctype">string</FORM> + +The hash keys are case sensitive. Ask the samples. =end def form(method = "post", action = nil, enctype = "application/x-www-form-urlencoded") attributes = if method.kind_of?(String) @@ -1244,7 +1258,7 @@ convert string charset, and set language to "ja". "ENCTYPE" => enctype } else unless method.has_key?("METHOD") - method["METHOD"] = method + method["METHOD"] = "post" end unless method.has_key?("ENCTYPE") method["ENCTYPE"] = enctype @@ -1935,161 +1949,7 @@ end == HISTORY -* Mon Dec 25 05:02:27 JST 2000 - wakou - * version 2.1.2 - * bug fix: CGI::escapeElement(): didn't accept empty element. - * bug fix: CGI::unescapeElement(): ditto. - * bug fix: CGI::unescapeHTML(): support for "©, ♥, ..." - thanks to YANAGAWA Kazuhisa <kjana@os.xaxon.ne.jp> - * bug fix: CGI::unescapeHTML(): support for "	" - thanks to OHSHIMA Ryunosuke <ryu@jaist.ac.jp> - * Regexp::last_match[0] --> $& - * Regexp::last_match[1] --> $1 - * Regexp::last_match[2] --> $2 - * add: CGI#param(): test implement. undocumented. - -* Mon Dec 11 00:16:51 JST 2000 - wakou - * version 2.1.1 - * support -T1 on ruby 1.6.2 - * body.original_filename: eval(str.dump.untaint).taint - * body.content_type: eval(str.dump.untaint).taint - * $& --> Regexp::last_match[0] - * $1 --> Regexp::last_match[1] - * $2 --> Regexp::last_match[2] - -* Thu Oct 12 01:16:59 JST 2000 - wakou - * version 2.1.0 - * bug fix: CGI::html(): PRETTY option didn't work. - thanks to akira yamada <akira@ruby-lang.org> - -* Wed Sep 13 06:09:26 JST 2000 - wakou - * version 2.0.1 - * bug fix: CGI::header(): output status header. - thanks to Yasuhiro Fukuma <yasuf@bsdclub.org> - -* Tue Sep 12 06:56:51 JST 2000 - wakou - * version 2.0.0 - * require ruby1.5.4 or later. (ruby1.4 doesn't have block_given? method.) - * improvement: CGI::escape(), CGI::unescape(). - thanks to WATANABE Hirofumi <eban@os.rim.or.jp> - * bug fix: CGI::escapeElement(). - * improvement: CGI::unescapeHTML(). - thanks to Kazuhiro NISHIYAMA <zn@mbf.nifty.com> - -* 2000/08/09 04:32:22 - matz - * improvement: CGI::pretty() - -* 2000/06/23 07:01:34 - matz - * change: iterator? --> block_given? - -* Sun Jun 18 23:31:44 JST 2000 - wakou - * version 1.7.0 - * change: version syntax. old: x.yz, now: x.y.z - -* 2000/06/13 15:49:27 - wakou - * version 1.61 - * read_multipart(): if no content body then raise EOFError. - -* 2000/06/03 18:16:17 - wakou - * version 1.60 - * improve: CGI::pretty() - -* 2000/05/30 19:04:08 - wakou - * version 1.50 - * CGI#out(): if "HEAD" == REQUEST_METHOD then output only HTTP header. - -* 2000/05/24 06:58:51 - wakou - * version 1.40 - * typo: CGI::Cookie::new() - * bug fix: CGI::escape(): bad: " " --> "%2B"; true: " " --> "+"; - thanks to Ryunosuke Ohshima <ryu@jaist.ac.jp> - -* 2000/05/08 21:51:30 - wakou - * version 1.31 - * improvement of time forming new CGI object accompanied with HTML generation methods. - -* 2000/05/07 21:51:14 - wakou - * version 1.30 - * require English.rb - * improvement of load time. - -* 2000/05/02 21:44:12 - wakou - * version 1.21 - * support for ruby 1.5.3 (2000-05-01) (Array#filter --> Array#collect!) - -* 2000/04/03 18:31:42 - wakou - * version 1.20 - * bug fix: CGI#image_button() can't get Hash option. - thanks to Takashi Ikeda <ikeda@auc.co.jp> - * CGI::unescapeHTML(): simple support for "〹" - * CGI::Cookie::new(): simple support for IE - * CGI::escape(): ' ' replaced by '+' - -* 1999/12/06 20:16:34 - wakou - * version 1.10 - * can make many CGI objects. - * if use mod_ruby, then require ruby1.4.3 or later. - -* 1999/11/29 21:35:58 - wakou - * version 1.01 - * support for ruby 1.5.0 (1999-11-20) - -* 1999/09/13 23:00:58 - wakou - * version 1.00 - * COUTION! name change. CGI.rb --> cgi.rb - * CGI#auth_type, CGI#content_length, CGI#content_type, ... - if not ENV included it, then return nil. - * CGI#content_length and CGI#server_port return Integer. - * if not CGI#params.include?('name'), then CGI#params['name'] return []. - * if not CGI#cookies.include?('name'), then CGI#cookies['name'] return []. - -* 1999/08/05 18:04:59 - wakou - * version 0.41 - * typo. thanks to MJ Ray <markj@altern.org> - HTTP_STATUS["NOT_INPLEMENTED"] --> HTTP_STATUS["NOT_IMPLEMENTED"] - -* 1999/07/20 20:44:31 - wakou - * version 0.40 - * COUTION! incompatible change. - sorry, but probably this change is last big incompatible change. - * CGI::print --> CGI#out - cgi = CGI.new - cgi.out{"string"} # old: CGI::print{"string"} - * CGI::cookie --> CGI::Cookie::new - cookie1 = CGI::Cookie::new # old: CGI::cookie - * CGI::header --> CGI#header - -* 1999/06/29 06:50:21 - wakou - * version 0.30 - * COUTION! incompatible change. - query = CGI.new - cookies = query.cookies # old: query.cookie - values = query.cookies[name] # old: query.cookie[name] - -* 1999/06/21 21:05:57 - wakou - * version 0.24 - * CGI::Cookie::parse() return { name => CGI::Cookie object } pairs. - -* 1999/06/20 23:29:12 - wakou - * version 0.23 - * modified a bit to clear module separation. - -* Mon Jun 14 17:49:32 JST 1999 - matz - * version 0.22 - * Cookies are now CGI::Cookie objects. - * Cookie modeled after CGI::Cookie.pm. - -* Fri Jun 11 11:19:11 JST 1999 - matz - * version 0.21 - * modified a bit to clear module separation. - -* 1999/06/03 06:48:15 - wakou - * version 0.20 - * support for multipart form. - -* 1999/05/24 07:05:41 - wakou - * version 0.10 - * first release. - -$Date$ +delete. see cvs log. + + =end diff --git a/lib/cgi/session.rb b/lib/cgi/session.rb index 48f3496939..1a3379b88a 100644 --- a/lib/cgi/session.rb +++ b/lib/cgi/session.rb @@ -1,3 +1,4 @@ +# Copyright (C) 2001 Yukihiro "Matz" Matsumoto # Copyright (C) 2000 Network Applied Communication Laboratory, Inc. # Copyright (C) 2000 Information-technology Promotion Agency, Japan @@ -15,23 +16,22 @@ class CGI } end - def create_new_id + def Session::create_new_id require 'md5' md5 = MD5::new md5.update(String(Time::now)) md5.update(String(rand(0))) md5.update(String($$)) md5.update('foobar') - @session_id = md5.hexdigest[0,16] + md5.hexdigest[0,16] end - private :create_new_id def initialize(request, option={}) session_key = option['session_key'] || '_session_id' id, = option['session_id'] unless id if option['new_session'] - id = create_new_id + id = Session::create_new_id end end unless id @@ -43,7 +43,7 @@ class CGI if option.key?('new_session') and not option['new_session'] raise ArgumentError, "session_key `%s' should be supplied"%session_key end - id = create_new_id + id = Session::create_new_id end end @session_id = id @@ -54,7 +54,9 @@ class CGI @output_cookies = [ Cookie::new("name" => session_key, "value" => id, - "path" => if ENV["SCRIPT_NAME"] then + "path" => if option['session_path'] then + option['session_path'] + elsif ENV["SCRIPT_NAME"] then File::dirname(ENV["SCRIPT_NAME"]) else "" @@ -94,10 +96,19 @@ class CGI end class FileStore + def check_id(id) + /[^0-9a-zA-Z]/ =~ id.to_s ? false : true + end + module_function :check_id + def initialize(session, option={}) dir = option['tmpdir'] || ENV['TMP'] || '/tmp' prefix = option['prefix'] || '' - path = dir+"/"+prefix+session.session_id + id = session.session_id + unless check_id(id) + raise ArgumentError, "session_id `%s' is invalid" % id + end + path = dir+"/"+prefix+id path.untaint unless File::exist? path @hash = {} @@ -132,6 +143,7 @@ class CGI end def close + return if @f.closed? update @f.close end @@ -146,9 +158,9 @@ class CGI class MemoryStore GLOBAL_HASH_TABLE = {} - def initialize(session, option={}) + def initialize(session, option=nil) @session_id = session.session_id - GLOBAL_HASH_TABLE[@session_id] = {} + GLOBAL_HASH_TABLE[@session_id] ||= {} end def restore @@ -164,7 +176,7 @@ class CGI end def delete - GLOBAL_HASH_TABLE[@session_id] = nil + GLOBAL_HASH_TABLE.delete(@session_id) end end end diff --git a/lib/date.rb b/lib/date.rb index 58179a7153..3422121298 100644 --- a/lib/date.rb +++ b/lib/date.rb @@ -1,5 +1,5 @@ -# date.rb: Written by Tadayoshi Funaba 1998-2000 -# $Id: date.rb,v 1.22 2000-07-16 10:23:40+09 tadf Exp $ +# date2.rb: Written by Tadayoshi Funaba 1998-2001 +# $Id: date2.rb,v 1.23 2001-01-18 12:09:47+09 tadf Exp $ class Date @@ -128,16 +128,15 @@ class Date end if d < 0 ny, nm = clfloor(y * 12 + m, 12) - nm, = clfloor(m + 1, 1) - la = nil - 31.downto 1 do |z| - break if la = exist3?(y, m, z, sg) - end - ns = ns?(la, sg) - d = jd_to_civil(civil_to_jd(ny, nm, 1, ns) + d, ns)[-1] + nm, = clfloor(nm + 1, 1) + jd = civil_to_jd(ny, nm, d + 1, sg) + ns = ns?(jd, sg) + return unless [y, m] == jd_to_civil(jd, sg)[0..1] + return unless [ny, nm, 1] == jd_to_civil(jd - d, ns) + else + jd = civil_to_jd(y, m, d, sg) + return unless [y, m, d] == jd_to_civil(jd, sg) end - jd = civil_to_jd(y, m, d, sg) - return unless [y, m, d] == jd_to_civil(jd, sg) jd end @@ -154,16 +153,15 @@ class Date def exist2? (y, d, sg=ITALY) if d < 0 - ny = y + 1 - la = nil - 366.downto 1 do |z| - break if la = exist2?(y, z, sg) - end - ns = ns?(la, sg) - d = jd_to_ordinal(ordinal_to_jd(ny, 1, ns) + d, ns)[-1] + ny, = clfloor(y + 1, 1) + jd = ordinal_to_jd(ny, d + 1, sg) + ns = ns?(jd, sg) + return unless [y] == jd_to_ordinal(jd, sg)[0..0] + return unless [ny, 1] == jd_to_ordinal(jd - d, ns) + else + jd = ordinal_to_jd(y, d, sg) + return unless [y, d] == jd_to_ordinal(jd, sg) end - jd = ordinal_to_jd(y, d, sg) - return unless [y, d] == jd_to_ordinal(jd, sg) jd end diff --git a/lib/debug.rb b/lib/debug.rb index b6968cc338..220b68d2c9 100644 --- a/lib/debug.rb +++ b/lib/debug.rb @@ -91,22 +91,70 @@ class DEBUGGER__ @finish_pos = 0 @trace = false @catch = "StandardError" + @suspend_next = false end def stop_next(n=1) @stop_next = n end + def set_suspend + @suspend_next = true + end + + def clear_suspend + @suspend_next = false + end + + def suspend_all + DEBUGGER__.suspend + end + + def resume_all + DEBUGGER__.resume + end + + def check_suspend + while (Thread.critical = true; @suspend_next) + DEBUGGER__.waiting.push Thread.current + @suspend_next = false + Thread.stop + end + Thread.critical = false + end + + def trace? + @trace + end + + def set_trace(arg) + @trace = arg + end + def stdout DEBUGGER__.stdout end + def break_points DEBUGGER__.break_points end + def display DEBUGGER__.display end + def context(th) + DEBUGGER__.context(th) + end + + def set_trace_all(arg) + DEBUGGER__.set_trace(arg) + end + + def set_last_thread(th) + DEBUGGER__.set_last_thread(th) + end + def debug_eval(str, binding) begin val = eval(str, binding) @@ -205,7 +253,7 @@ class DEBUGGER__ def debug_command(file, line, id, binding) MUTEX.lock - DEBUGGER__.set_last_thread(Thread.current) + set_last_thread(Thread.current) frame_pos = 0 binding_file = file binding_line = line @@ -218,7 +266,8 @@ class DEBUGGER__ end @frames[0] = [binding, file, line, id] display_expressions(binding) - while input = readline("(rdb:%d) "%thnum(), true) + prompt = true + while prompt and input = readline("(rdb:%d) "%thnum(), true) catch(:debug_error) do if input == "" input = DEBUG_LAST_CMD[0] @@ -228,18 +277,24 @@ class DEBUGGER__ end case input - when /^\s*tr(?:ace)?(?:\s+(on|off))?$/ - if defined?( $1 ) + when /^\s*tr(?:ace)?(?:\s+(on|off))?(?:\s+(all))?$/ + if defined?( $2 ) if $1 == 'on' - @trace = true + set_trace_all true else - @trace = false + set_trace_all false + end + elsif defined?( $1 ) + if $1 == 'on' + set_trace true + else + set_trace false end end - if @trace - stdout.print "Trace on\n" + if trace? + stdout.print "Trace on.\n" else - stdout.print "Trace off\n" + stdout.print "Trace off.\n" end when /^\s*b(?:reak)?\s+((?:.*?+:)?.+)$/ @@ -336,8 +391,7 @@ class DEBUGGER__ end when /^\s*c(?:ont)?$/ - MUTEX.unlock - return + prompt = false when /^\s*s(?:tep)?(?:\s+(\d+))?$/ if $1 @@ -346,7 +400,7 @@ class DEBUGGER__ lev = 1 end @stop_next = lev - return + prompt = false when /^\s*n(?:ext)?(?:\s+(\d+))?$/ if $1 @@ -356,7 +410,7 @@ class DEBUGGER__ end @stop_next = lev @no_step = @frames.size - frame_pos - return + prompt = false when /^\s*w(?:here)?$/, /^\s*f(?:rame)?$/ display_frames(frame_pos) @@ -417,8 +471,7 @@ class DEBUGGER__ else @finish_pos = @frames.size - frame_pos frame_pos = 0 - MUTEX.unlock - return + prompt = false end when /^\s*cat(?:ch)?(?:\s+(.+))?$/ @@ -440,8 +493,10 @@ class DEBUGGER__ end when /^\s*q(?:uit)?$/ - input = readline("really quit? (y/n) ", false) - exit if input == "y" + input = readline("Really quit? (y/n) ", false) + if input == "y" + exit! # exit -> exit!: No graceful way to stop threads... + end when /^\s*v(?:ar)?\s+/ debug_variable_info($', binding) @@ -451,8 +506,7 @@ class DEBUGGER__ when /^\s*th(?:read)?\s+/ if DEBUGGER__.debug_thread_info($', binding) == :cont - MUTEX.unlock - return + prompt = false end when /^\s*p\s+/ @@ -467,6 +521,8 @@ class DEBUGGER__ end end end + MUTEX.unlock + resume_all end def debug_print_help @@ -492,7 +548,8 @@ Commands up[ nn] move to higher frame down[ nn] move to lower frame fin[ish] return to outer frame - tr[ace][ (on|off)] set trace mode + tr[ace] (on|off) set trace mode of current thread + tr[ace] (on|off) all set trace mode of all threads q[uit] exit from debugger v[ar] g[lobal] show global variables v[ar] l[ocal] show local variables @@ -501,11 +558,10 @@ Commands m[ethod] i[nstance] <obj> show methods of object m[ethod] <class|module> show instance methods of class or module th[read] l[ist] list all threads - th[read] c[ur[rent]] show current threads - th[read] <nnn> stop thread nnn - th[read] stop <nnn> alias for th[read] <nnn> - th[read] c[ur[rent]] <nnn> alias for th[read] <nnn> - th[read] resume <nnn> run thread nnn + th[read] c[ur[rent]] show current thread + th[read] [sw[itch]] <nnn> switch thread context to nnn + th[read] stop <nnn> stop thread nnn + th[read] resume <nnn> resume thread nnn p expression evaluate expression and print its value h[elp] print this help <everything else> evaluate @@ -587,7 +643,7 @@ EOHELP end def check_break_points(file, pos, binding, id) - MUTEX.lock # Stop all threads before 'line' and 'call'. + return false if break_points.empty? file = File.basename(file) n = 1 for b in break_points @@ -604,7 +660,6 @@ EOHELP end n += 1 end - MUTEX.unlock return false end @@ -616,7 +671,6 @@ EOHELP end if @catch and ($!.type.ancestors.find { |e| e.to_s == @catch }) - MUTEX.lock fs = @frames.size tb = caller(0)[-fs..-1] if tb @@ -624,12 +678,14 @@ EOHELP stdout.printf "\tfrom %s\n", i end end + suspend_all debug_command(file, line, id, binding) end end def trace_func(event, file, line, id, binding, klass) - Tracer.trace_func(event, file, line, id, binding) if @trace + Tracer.trace_func(event, file, line, id, binding, klass) if trace? + context(Thread.current).check_suspend @file = file @line = line case event @@ -647,6 +703,7 @@ EOHELP @stop_next = 1 else @no_step = nil + suspend_all debug_command(file, line, id, binding) @last = [file, line] end @@ -656,6 +713,7 @@ EOHELP @frames.unshift [binding, file, line, id] if check_break_points(file, id.id2name, binding, id) or check_break_points(klass.to_s, id.id2name, binding, id) + suspend_all debug_command(file, line, id, binding) end @@ -668,6 +726,7 @@ EOHELP when 'return', 'end' if @frames.size == @finish_pos @stop_next = 1 + @finish_pos = 0 end @frames.shift @@ -682,19 +741,20 @@ EOHELP end end - trap("INT") { DEBUGGER__.interrupt } -# $DEBUG = true + trap("INT") { DEBUGGER__.interrupt } @last_thread = Thread::main @max_thread = 1 @thread_list = {Thread::main => 1} @break_points = [] @display = [] + @waiting = [] @stdout = STDOUT class <<DEBUGGER__ def stdout @stdout end + def stdout=(s) @stdout = s end @@ -707,10 +767,51 @@ EOHELP @break_points end + def waiting + @waiting + end + + def set_trace( arg ) + Thread.critical = true + make_thread_list + for th in @thread_list + context(th[0]).set_trace arg + end + Thread.critical = false + end + def set_last_thread(th) @last_thread = th end + def suspend + Thread.critical = true + make_thread_list + for th in @thread_list + next if th[0] == Thread.current + context(th[0]).set_suspend + end + Thread.critical = false + # Schedule other threads to suspend as soon as possible. + Thread.pass + end + + def resume + Thread.critical = true + make_thread_list + for th in @thread_list + next if th[0] == Thread.current + context(th[0]).clear_suspend + end + waiting.each do |th| + th.run + end + waiting.clear + Thread.critical = false + # Schedule other threads to restart as soon as possible. + Thread.pass + end + def context(thread=Thread.current) c = thread[:__debugger_data__] unless c @@ -726,7 +827,7 @@ EOHELP def get_thread(num) th = @thread_list.index(num) unless th - @stdout.print "no thread no.", num, "\n" + @stdout.print "No thread ##{num}\n" throw :debug_error end th @@ -773,31 +874,52 @@ EOHELP make_thread_list thread_list_all - when /^c(?:ur(?:rent)?)?\s+(\d+)/, /^stop\s+(\d+)/, /^(\d+)/ + when /^c(?:ur(?:rent)?)?$/ + make_thread_list + thread_list(@thread_list[Thread.current]) + + when /^(?:sw(?:itch)?\s+)?(\d+)/ make_thread_list th = get_thread($1.to_i) - thread_list(@thread_list[th]) - context(th).stop_next - th.run - return :cont + if th == Thread.current + @stdout.print "It's the current thread.\n" + else + thread_list(@thread_list[th]) + context(th).stop_next + th.run + return :cont + end - when /^c(?:ur(?:rent)?)?$/ + when /^stop\s+(\d+)/ make_thread_list - thread_list(@thread_list[Thread.current]) + th = get_thread($1.to_i) + if th == Thread.current + @stdout.print "It's the current thread.\n" + elsif th.stop? + @stdout.print "Already stopped.\n" + else + thread_list(@thread_list[th]) + context(th).suspend + end when /^resume\s+(\d+)/ make_thread_list th = get_thread($1.to_i) - thread_list(@thread_list[th]) - th.run - return :cont + if th == Thread.current + @stdout.print "It's the current thread.\n" + elsif !th.stop? + @stdout.print "Already running." + else + thread_list(@thread_list[th]) + th.run + end end end end stdout.printf "Debug.rb\n" stdout.printf "Emacs support available.\n\n" - set_trace_func proc{|event, file, line, id, binding,klass,*rest| - DEBUGGER__.context.trace_func event, file, line, id, binding,klass + set_trace_func proc { |event, file, line, id, binding, klass, *rest| + DEBUGGER__.context.trace_func event, file, line, id, binding, klass } end diff --git a/lib/delegate.rb b/lib/delegate.rb index 480e1ef6b8..a72ea943ba 100644 --- a/lib/delegate.rb +++ b/lib/delegate.rb @@ -8,7 +8,7 @@ # Usage: # foo = Object.new # foo2 = SimpleDelegator.new(foo) -# foo.hash == foo2.hash # => true +# foo.hash == foo2.hash # => false # # Foo = DelegateClass(Array) # diff --git a/lib/forwardable.rb b/lib/forwardable.rb new file mode 100644 index 0000000000..7f57f77f53 --- /dev/null +++ b/lib/forwardable.rb @@ -0,0 +1,94 @@ +# +# forwardable.rb - +# $Release Version: 1.1$ +# $Revision$ +# $Date$ +# by Keiju ISHITSUKA(keiju@ishitsuka.com) +# original definition by delegator.rb +# -- +# Usage: +# +# class Foo +# extend Forwardable +# +# def_delegators("@out", "printf", "print") +# def_delegators(:@in, :gets) +# def_delegator(:@contents, :[], "content_at") +# end +# f = Foo.new +# f.printf ... +# f.gets +# f.content_at(1) +# +# g = Goo.new +# g.extend SingleForwardable +# g.def_delegator("@out", :puts) +# g.puts ... +# +# + +module Forwardable + + @debug = nil + class<<self + attr_accessor :debug + end + + def def_instance_delegators(accessor, *methods) + for method in methods + def_instance_delegator(accessor, method) + end + end + + def def_instance_delegator(accessor, method, ali = method) + accessor = accessor.id2name if accessor.kind_of?(Integer) + method = method.id2name if method.kind_of?(Integer) + ali = ali.id2name if ali.kind_of?(Integer) + + module_eval(<<-EOS, "(__FORWARDABLE__)", 1) + def #{ali}(*args, &block) + begin + #{accessor}.__send__(:#{method}, *args, &block) + rescue Exception + $@.delete_if{|s| /^\\(__FORWARDABLE__\\):/ =~ s} unless Forwardable::debug + raise + end + end + EOS + end + + alias def_delegators def_instance_delegators + alias def_delegator def_instance_delegator +end + +module SingleForwardable + def def_singleton_delegators(accessor, *methods) + for method in methods + def_singleton_delegator(accessor, method) + end + end + + def def_singleton_delegator(accessor, method, ali = method) + accessor = accessor.id2name if accessor.kind_of?(Integer) + method = method.id2name if method.kind_of?(Integer) + ali = ali.id2name if ali.kind_of?(Integer) + + instance_eval(<<-EOS, "(__FORWARDABLE__)", 1) + def #{ali}(*args, &block) + begin + #{accessor}.__send__(:#{method}, *args,&block) + rescue Exception + $@.delete_if{|s| /^\\(__FORWARDABLE__\\):/ =~ s} unless Forwardable::debug + raise + end + end + EOS + end + + alias def_delegators def_singleton_delegators + alias def_delegator def_singleton_delegator +end + + + + diff --git a/lib/ftools.rb b/lib/ftools.rb index 369a6177d2..5e6203b1a3 100644 --- a/lib/ftools.rb +++ b/lib/ftools.rb @@ -26,6 +26,7 @@ class << File fmode = stat(from).mode tpath = to + not_exist = !exist?(tpath) from = open(from, "r") from.binmode @@ -50,7 +51,7 @@ class << File to.close from.close end - chmod(fmode, tpath) + chmod(fmode, tpath) if not_exist ret end diff --git a/lib/importenv.rb b/lib/importenv.rb index fcf306a9ab..586f37661b 100644 --- a/lib/importenv.rb +++ b/lib/importenv.rb @@ -10,10 +10,10 @@ for k,v in ENV next unless /^[a-zA-Z][_a-zA-Z0-9]*/ =~ k eval <<EOS - $#{k} = %q!#{v}! + $#{k} = v trace_var "$#{k}", proc{|v| - ENV[%q!#{k}!] = v; - $#{k} = %q!#{v}! + ENV[%q!#{k}!] = v + $#{k} = v if v == nil untrace_var "$#{k}" end diff --git a/lib/irb.rb b/lib/irb.rb new file mode 100644 index 0000000000..1b8444b5b3 --- /dev/null +++ b/lib/irb.rb @@ -0,0 +1,314 @@ +# +# irb.rb - irb main module +# $Release Version: 0.7.4 $ +# $Revision$ +# $Date$ +# by Keiju ISHITSUKA(keiju@ishitsuka.com) +# +# -- +# +# +# +require "e2mmap" + +require "irb/init" +require "irb/context" +require "irb/extend-command" +require "irb/workspace" + +require "irb/ruby-lex" +require "irb/input-method" +require "irb/locale" + +STDOUT.sync = true + +module IRB + @RCS_ID='-$Id$-' + + class Abort < Exception;end + + # + @CONF = {} + + def IRB.conf + @CONF + end + + # IRB version method + def IRB.version + if v = @CONF[:VERSION] then return v end + + require "irb/version" + rv = @RELEASE_VERSION.sub(/\.0/, "") + @CONF[:VERSION] = format("irb %s(%s)", rv, @LAST_UPDATE_DATE) + end + + # initialize IRB and start TOP_LEVEL irb + def IRB.start(ap_path = nil) + $0 = File::basename(ap_path, ".rb") if ap_path + + IRB.initialize(ap_path) + IRB.parse_opts + IRB.load_modules + + if @CONF[:SCRIPT] + irb = Irb.new(nil, @CONF[:SCRIPT]) + else + irb = Irb.new + end + + @CONF[:IRB_RC].call(irb.context) if @CONF[:IRB_RC] + @CONF[:MAIN_CONTEXT] = irb.context + + trap("SIGINT") do + irb.signal_handle + end + + catch(:IRB_EXIT) do + irb.eval_input + end + print "\n" + end + + def IRB.irb_exit(irb, ret) + throw :IRB_EXIT, ret + end + + def IRB.irb_abort(irb, exception = Abort) + if defined? Thread + irb.context.thread.raise exception, "abort then interrupt!!" + else + raise exception, "abort then interrupt!!" + end + end + + # + # irb interpriter main routine + # + class Irb + def initialize(workspace = nil, input_method = nil) + @context = Context.new(self, workspace, input_method) + @context.main.extend ExtendCommand + @signal_status = :IN_IRB + + @scanner = RubyLex.new + @scanner.exception_on_syntax_error = false + end + attr_reader :context + attr_accessor :scanner + + def eval_input + @scanner.set_input(@context.io) do + signal_status(:IN_INPUT) do + unless l = @context.io.gets + if @context.ignore_eof? and @context.io.readable_atfer_eof? + l = "\n" + if @context.verbose? + printf "Use \"exit\" to leave %s\n", @context.ap_name + end + end + end + l + end + end + + @scanner.set_prompt do + |ltype, indent, continue, line_no| + if ltype + f = @context.prompt_s + elsif continue + f = @context.prompt_c + else @context.prompt_i + f = @context.prompt_i + end + f = "" unless f + @context.io.prompt = p = prompt(f, ltype, indent, line_no) + if @context.auto_indent_mode + unless ltype + ind = prompt(@context.prompt_i, ltype, indent, line_no).size + + indent * 2 - p.size + ind += 2 if continue + @context.io.prompt = p + " " * ind if ind > 0 + end + end + end + + @scanner.each_top_level_statement do + |line, line_no| + signal_status(:IN_EVAL) do + begin + trace_in do + @context._ = @context.workspace.evaluate(line, + @context.irb_path, + line_no) +# @context._ = irb_eval(line, @context.bind, @context.irb_path, line_no) + end + + if @context.inspect? + printf @context.return_format, @context._.inspect + else + printf @context.return_format, @context._ + end + rescue StandardError, ScriptError, Abort + $! = RuntimeError.new("unknown exception raised") unless $! + print $!.type, ": ", $!, "\n" + if $@[0] =~ /irb(2)?(\/.*|-.*|\.rb)?:/ && $!.type.to_s !~ /^IRB/ + irb_bug = true + else + irb_bug = false + end + + messages = [] + lasts = [] + levels = 0 + for m in $@ + m = @context.workspace.filter_backtrace(m) unless irb_bug + if m + if messages.size < @context.back_trace_limit + messages.push "\tfrom "+m + else + lasts.push "\tfrom "+m + if lasts.size > @context.back_trace_limit + lasts.shift + levels += 1 + end + end + end + end + print messages.join("\n"), "\n" + unless lasts.empty? + printf "... %d levels...\n", levels if levels > 0 + print lasts.join("\n") + end + print "Maybe IRB bug!!\n" if irb_bug + end + end + end + end + +# def irb_eval(line, bind, path, line_no) +# id, str = catch(:IRB_TOPLEVEL_EVAL){ +# return eval(line, bind, path, line_no) +# } +# case id +# when :EVAL_TOPLEVEL +# eval(str, bind, "(irb_internal)", 1) +# when :EVAL_CONTEXT +# @context.instance_eval(str) +# else +# IRB.fail IllegalParameter +# end +# end + + def signal_handle + unless @context.ignore_sigint? + print "\nabort!!\n" if @context.verbose? + exit + end + + case @signal_status + when :IN_INPUT + print "^C\n" + raise RubyLex::TerminateLineInput + when :IN_EVAL + IRB.irb_abort(self) + when :IN_LOAD + IRB.irb_abort(self, LoadAbort) + when :IN_IRB + # ignore + else + # ignore other cases as well + end + end + + def signal_status(status) + return yield if @signal_status == :IN_LOAD + + signal_status_back = @signal_status + @signal_status = status + begin + yield + ensure + @signal_status = signal_status_back + end + end + + def trace_in + Tracer.on if @context.use_tracer? + begin + yield + ensure + Tracer.off if @context.use_tracer? + end + end + + def prompt(prompt, ltype, indent, line_no) + p = prompt.dup + p.gsub!(/%([0-9]+)?([a-zA-Z])/) do + case $2 + when "N" + @context.irb_name + when "m" + @context.main.to_s + when "M" + @context.main.inspect + when "l" + ltype + when "i" + if $1 + format("%" + $1 + "d", indent) + else + indent.to_s + end + when "n" + if $1 + format("%" + $1 + "d", line_no) + else + line_no.to_s + end + when "%" + "%" + end + end + p + end + + def inspect + ary = [] + for iv in instance_variables + case iv + when "@signal_status" + ary.push format("%s=:%s", iv, @signal_status.id2name) + when "@context" + ary.push format("%s=%s", iv, eval(iv).__to_s__) + else + ary.push format("%s=%s", iv, eval(iv)) + end + end + format("#<%s: %s>", type, ary.join(", ")) + end + end + + # Singleton method + def @CONF.inspect + IRB.version unless self[:VERSION] + + array = [] + for k, v in sort{|a1, a2| a1[0].id2name <=> a2[0].id2name} + case k + when :MAIN_CONTEXT + next + when :PROMPT + s = v.collect{ + |kk, vv| + ss = vv.collect{|kkk, vvv| ":#{kkk.id2name}=>#{vvv.inspect}"} + format(":%s=>{%s}", kk.id2name, ss.join(", ")) + } + array.push format("CONF[:%s]={%s}", k.id2name, s.join(", ")) + else + array.push format("CONF[:%s]=%s", k.id2name, v.inspect) + end + end + array.join("\n") + end +end diff --git a/lib/irb/completion.rb b/lib/irb/completion.rb index cbd4012773..01dcbd2219 100644 --- a/lib/irb/completion.rb +++ b/lib/irb/completion.rb @@ -1,8 +1,19 @@ +# +# irb/completor.rb - +# $Release Version: 0.7.1$ +# $Revision$ +# $Date$ +# by Keiju ISHITSUKA(keiju@ishitsuka.com) +# From Original Idea of shugo@ruby-lang.org +# require "readline" module IRB - module InputCompletion + module InputCompletor + + @RCS_ID='-$Id$-' + ReservedWords = [ "BEGIN", "END", "alias", "and", @@ -20,28 +31,147 @@ module IRB "then", "true", "undef", "unless", "until", "when", "while", - "yield" + "yield", ] CompletionProc = proc { |input| + bind = IRB.conf[:MAIN_CONTEXT].workspace.binding case input - when /^([^.]+)\.([^.]*)$/ + when /^(\/[^\/]*\/)\.([^.]*)$/ + # Regexp + receiver = $1 + message = Regexp.quote($2) + + candidates = Regexp.instance_methods(true) + select_message(receiver, message, candidates) + + when /^([^\]]*\])\.([^.]*)$/ + # Array receiver = $1 - message = $2 - if eval("(local_variables|#{receiver}.type.constants).include?('#{receiver}')", - IRB.conf[:MAIN_CONTEXT].bind) - candidates = eval("#{receiver}.methods", IRB.conf[:MAIN_CONTEXT].bind) + message = Regexp.quote($2) + + candidates = Array.instance_methods(true) + select_message(receiver, message, candidates) + + when /^([^\}]*\})\.([^.]*)$/ + # Proc or Hash + receiver = $1 + message = Regexp.quote($2) + + candidates = Proc.instance_methods(true) | Hash.instance_methods(true) + select_message(receiver, message, candidates) + + when /^(:[^:]*)$/ + # Symbol + if Symbol.respond_to?(:all_symbols) + sym = $1 + candidates = Symbol.all_symbols.collect{|s| s.id2name} + candidates.grep(/^#{sym}/) else + [] + end + + when /^::([A-Z][^:\.\(]*)$/ + # Absolute Constant or class methods + receiver = $1 + candidates = Object.constants + candidates.grep(/^#{receiver}/).collect{|e| "::" + e} + + when /^(((::)?[A-Z][^:.\(]*)+)::?([^:.]*)$/ + # Constant or class methods + receiver = $1 + message = Regexp.quote($4) + begin + candidates = eval("#{receiver}.constants | #{receiver}.methods", bind) + rescue Exception candidates = [] end - candidates.grep(/^#{Regexp.quote(message)}/).collect{|e| receiver + "." + e} + candidates.grep(/^#{message}/).collect{|e| receiver + "::" + e} + + when /^(:[^.]+)\.([^.]*)$/ + # Symbol + receiver = $1 + message = Regexp.quote($2) + + candidates = Symbol.instance_methods(true) + select_message(receiver, message, candidates) + + when /^([0-9_]+(\.[0-9_]+)?(e[0-9]+)?)\.([^.]*)$/ + # Numeric + receiver = $1 + message = Regexp.quote($4) + + begin + candidates = eval(receiver, bind).methods + rescue Exception + candidates + end + select_message(receiver, message, candidates) + +# when /^(\$?(\.?[^.]+)+)\.([^.]*)$/ + when /^((\.?[^.]+)+)\.([^.]*)$/ + # variable + receiver = $1 + message = Regexp.quote($3) + + gv = eval "global_variables", bind + lv = eval "local_variables", bind + cv = eval "type.constants", bind + + if (gv | lv | cv).include?(receiver) + # foo.func and foo is local var. + candidates = eval("#{receiver}.methods", bind) + elsif /^[A-Z]/ =~ receiver and /\./ !~ receiver + # Foo::Bar.func + begin + candidates = eval("#{receiver}.methods", bind) + rescue Exception + candidates = [] + end + else + # func1.func2 + candidates = [] + ObjectSpace.each_object(Module){|m| + next if /^(IRB|SLex|RubyLex|RubyToken)/ =~ m.name + candidates.concat m.instance_methods + } + candidates.sort! + candidates.uniq! + end + select_message(receiver, message, candidates) + + when /^\.([^.]*)$/ + # unknown(maybe String) + + receiver = "" + message = Regexp.quote($1) + + candidates = String.instance_methods(true) + select_message(receiver, message, candidates) + else - candidates = eval("methods | private_methods | local_variables | type.constants", - IRB.conf[:MAIN_CONTEXT].bind) + candidates = eval("methods | private_methods | local_variables | type.constants", bind) + (candidates|ReservedWords).grep(/^#{Regexp.quote(input)}/) end } + + Operators = ["%", "&", "*", "**", "+", "-", "/", + "<", "<<", "<=", "<=>", "==", "===", "=~", ">", ">=", ">>", + "[]", "[]=", "^",] + + def self.select_message(receiver, message, candidates) + candidates.grep(/^#{message}/).collect do |e| + case e + when /^[a-zA-Z_]/ + receiver + "." + e + when /^[0-9]/ + when *Operators + #receiver + " " + e + end + end + end end end -Readline.completion_proc = IRB::InputCompletion::CompletionProc +Readline.completion_proc = IRB::InputCompletor::CompletionProc diff --git a/lib/irb/context.rb b/lib/irb/context.rb new file mode 100644 index 0000000000..ffc77de875 --- /dev/null +++ b/lib/irb/context.rb @@ -0,0 +1,289 @@ +# +# irb/context.rb - irb context +# $Release Version: 0.7.3$ +# $Revision$ +# $Date$ +# by Keiju ISHITSUKA(keiju@ishitsuka.com) +# +# -- +# +# +# +module IRB + class Context + # + # Arguments: + # input_method: nil -- stdin or readline + # String -- File + # other -- using this as InputMethod + # + def initialize(irb, workspace = nil, input_method = nil) + @irb = irb + if workspace + @workspace = workspace + else + @workspace = WorkSpace.new unless workspace + end + @thread = Thread.current if defined? Thread + @irb_level = 0 + + # copy of default configuration + @ap_name = IRB.conf[:AP_NAME] + @rc = IRB.conf[:RC] + @load_modules = IRB.conf[:LOAD_MODULES] + + self.math_mode = IRB.conf[:MATH_MODE] + @use_readline = IRB.conf[:USE_READLINE] + @inspect_mode = IRB.conf[:INSPECT_MODE] + self.use_tracer = IRB.conf[:USE_TRACER] +# @use_loader = IRB.conf[:USE_LOADER] + + self.prompt_mode = IRB.conf[:PROMPT_MODE] + + @ignore_sigint = IRB.conf[:IGNORE_SIGINT] + @ignore_eof = IRB.conf[:IGNORE_EOF] + + @back_trace_limit = IRB.conf[:BACK_TRACE_LIMIT] + + debug_level = IRB.conf[:DEBUG_LEVEL] + @verbose = IRB.conf[:VERBOSE] + + @tracer_initialized = false + + if IRB.conf[:SINGLE_IRB] or !defined?(JobManager) + @irb_name = IRB.conf[:IRB_NAME] + else + @irb_name = "irb#"+IRB.JobManager.n_jobs.to_s + end + @irb_path = "(" + @irb_name + ")" + + case input_method + when nil + if (use_readline.nil? && IRB.conf[:PROMPT_MODE] != :INF_RUBY || + use_readline?) + @io = ReadlineInputMethod.new + else + @io = StdioInputMethod.new + end + when String + @io = FileInputMethod.new(input_method) + @irb_name = File.basename(input_method) + @irb_path = input_method + else + @io = input_method + end + end + + def main + @workspace.main + end + + attr_accessor :workspace + attr_reader :thread + attr_accessor :io + + attr_reader :_ + + attr_accessor :irb + attr_accessor :ap_name + attr_accessor :rc + attr_accessor :load_modules + attr_accessor :irb_name + attr_accessor :irb_path + + attr_accessor :math_mode + attr_accessor :use_readline + attr_reader :inspect_mode + attr_reader :use_tracer +# attr :use_loader + + attr_reader :debug_level + attr_accessor :verbose + + attr_reader :prompt_mode + attr_accessor :prompt_i + attr_accessor :prompt_s + attr_accessor :prompt_c + attr_accessor :auto_indent_mode + attr_accessor :return_format + + attr_accessor :ignore_sigint + attr_accessor :ignore_eof + + attr_accessor :back_trace_limit + +# alias use_loader? use_loader + alias use_tracer? use_tracer + alias use_readline? use_readline + alias rc? rc + alias math? math_mode + alias verbose? verbose + alias ignore_sigint? ignore_sigint + alias ignore_eof? ignore_eof + + def _=(value) + @_ = value + @workspace.evaluate "_ = IRB.conf[:MAIN_CONTEXT]._" + end + + def irb_name + if @irb_level == 0 + @irb_name + elsif @irb_name =~ /#[0-9]*$/ + @irb_name + "." + @irb_level.to_s + else + @irb_name + "#0." + @irb_level.to_s + end + end + + def prompt_mode=(mode) + @prompt_mode = mode + pconf = IRB.conf[:PROMPT][mode] + @prompt_i = pconf[:PROMPT_I] + @prompt_s = pconf[:PROMPT_S] + @prompt_c = pconf[:PROMPT_C] + @return_format = pconf[:RETURN] + if ai = pconf.include?(:AUTO_INDENT) + @auto_indent_mode = ai + else + @auto_indent_mode = IRB.conf[:AUTO_INDENT] + end + end + + def inspect? + @inspect_mode.nil? && !@math_mode or @inspect_mode + end + + def file_input? + @io.type == FileInputMethod + end + + def use_tracer=(opt) + if opt + IRB.initialize_tracer + unless @tracer_initialized + Tracer.set_get_line_procs(@irb_path) { + |line_no| + @io.line(line_no) + } + @tracer_initialized = true + end + elsif !opt && @use_tracer + Tracer.off + end + @use_tracer=opt + end + + def use_loader + IRB.conf[:USE_LOADER] + end + + def use_loader=(opt) + IRB.conf[:USE_LOADER] = opt + if opt + IRB.initialize_loader + end + print "Switch to load/require#{unless use_loader; ' non';end} trace mode.\n" if verbose? + opt + end + + def inspect_mode=(opt) + if opt + @inspect_mode = opt + else + @inspect_mode = !@inspect_mode + end + print "Switch to#{unless @inspect_mode; ' non';end} inspect mode.\n" if verbose? + @inspect_mode + end + + def math_mode=(opt) + if @math_mode == true && opt == false + IRB.fail CantRetuenNormalMode + return + end + + @math_mode = opt + if math_mode + IRB.initialize_mathn + main.instance_eval("include Math") + print "start math mode\n" if verbose? + end + end + + def use_readline=(opt) + @use_readline = opt + print "use readline module\n" if @use_readline + end + + def debug_level=(value) + @debug_level = value + RubyLex.debug_level = value + SLex.debug_level = value + end + + def debug? + @debug_level > 0 + end + + def change_binding(*_main) + back = @workspace + @workspace = WorkSpace.new(*_main) + unless _main.empty? + begin + main.extend ExtendCommand + rescue + print "can't change binding to: ", main.inspect, "\n" + @workspace = back + return nil + end + end + @irb_level += 1 + begin + catch(:SU_EXIT) do + @irb.eval_input + end + ensure + @irb_level -= 1 + @workspace = back + end + end + alias change_workspace change_binding + + + alias __exit__ exit + def exit(ret = 0) + if @irb_level == 0 + IRB.irb_exit(@irb, ret) + else + throw :SU_EXIT, ret + end + end + + NOPRINTING_IVARS = ["@_"] + NO_INSPECTING_IVARS = ["@irb", "@io"] + IDNAME_IVARS = ["@prompt_mode"] + + alias __inspect__ inspect + def inspect + array = [] + for ivar in instance_variables.sort{|e1, e2| e1 <=> e2} + name = ivar.sub(/^@(.*)$/){$1} + val = instance_eval(ivar) + case ivar + when *NOPRINTING_IVARS + next + when *NO_INSPECTING_IVARS + array.push format("conf.%s=%s", name, val.to_s) + when *IDNAME_IVARS + array.push format("conf.%s=:%s", name, val.id2name) + else + array.push format("conf.%s=%s", name, val.inspect) + end + end + array.join("\n") + end + alias __to_s__ to_s + alias to_s inspect + end +end diff --git a/lib/irb/extend-command.rb b/lib/irb/extend-command.rb new file mode 100644 index 0000000000..3f92707d01 --- /dev/null +++ b/lib/irb/extend-command.rb @@ -0,0 +1,126 @@ +# +# irb/extend-command.rb - irb command extend +# $Release Version: 0.7.3$ +# $Revision$ +# $Date$ +# by Keiju ISHITSUKA(keiju@ishitsuka.com) +# +# -- +# +# +# +module IRB + # + # IRB extended command + # + module ExtendCommand +# include Loader + + def irb_exit(ret = 0) + irb_context.exit(ret) + end + alias irb_quit irb_exit + + def irb_fork(&block) + pid = send ExtendCommand.irb_original_method_name("fork") + unless pid + class<<self + alias_method :exit, ExtendCommand.irb_original_method_name('exit') + end + if iterator? + begin + yield + ensure + exit + end + end + end + pid + end + + def irb_change_binding(*main) + irb_context.change_binding(*main) + end + alias irb_change_workspace irb_change_binding + + def irb_source(file) + irb_context.source(file) + end + + def irb(*obj) + require "irb/multi-irb" + IRB.irb(nil, *obj) + end + + def irb_context + IRB.conf[:MAIN_CONTEXT] + end + + def irb_jobs + require "irb/multi-irb" + IRB.JobManager + end + + def irb_fg(key) + require "irb/multi-irb" + IRB.JobManager.switch(key) + end + + def irb_kill(*keys) + require "irb/multi-irb" + IRB.JobManager.kill(*keys) + end + + # extend command functions + def ExtendCommand.extend_object(obj) + super + unless (class<<obj;ancestors;end).include?(ExtendCommand) + obj.install_aliases + end + end + + OVERRIDE_NOTHING = 0 + OVERRIDE_PRIVATE_ONLY = 0x01 + OVERRIDE_ALL = 0x02 + + def install_aliases(override = OVERRIDE_NOTHING) + + install_alias_method(:exit, :irb_exit, override | OVERRIDE_PRIVATE_ONLY) + install_alias_method(:quit, :irb_quit, override | OVERRIDE_PRIVATE_ONLY) + install_alias_method(:fork, :irb_fork, override | OVERRIDE_PRIVATE_ONLY) + install_alias_method(:kill, :irb_kill, override | OVERRIDE_PRIVATE_ONLY) + + install_alias_method(:irb_cb, :irb_change_binding, override) + install_alias_method(:irb_ws, :irb_change_workspace, override) + install_alias_method(:source, :irb_source, override) + install_alias_method(:conf, :irb_context, override) + install_alias_method(:jobs, :irb_jobs, override) + install_alias_method(:fg, :irb_fg, override) + end + + # override = {OVERRIDE_NOTHING, OVERRIDE_PRIVATE_ONLY, OVERRIDE_ALL} + def install_alias_method(to, from, override = OVERRIDE_NOTHING) + to = to.id2name unless to.kind_of?(String) + from = from.id2name unless from.kind_of?(String) + + if override == OVERRIDE_ALL or + (override == OVERRIDE_PRIVATE_ONLY) && !respond_to?(to) or + (override == OVERRIDE_NOTHING) && !respond_to?(to, true) + target = self + (class<<self;self;end).instance_eval{ + if target.respond_to?(to, true) && + !target.respond_to?(ExtendCommand.irb_original_method_name(to), true) + alias_method(ExtendCommand.irb_original_method_name(to), to) + end + alias_method to, from + } + else + print "irb: warn: can't alias #{to} from #{from}.\n" + end + end + + def self.irb_original_method_name(method_name) + "irb_" + method_name + "_org" + end + end +end diff --git a/lib/irb/frame.rb b/lib/irb/frame.rb index dcfa5c1d13..1090cd9684 100644 --- a/lib/irb/frame.rb +++ b/lib/irb/frame.rb @@ -1,6 +1,6 @@ # # frame.rb - -# $Release Version: 0.6$ +# $Release Version: 0.7.1$ # $Revision$ # $Date$ # by Keiju ISHITSUKA(Nihon Rational Software Co.,Ltd) diff --git a/lib/irb/help.rb b/lib/irb/help.rb new file mode 100644 index 0000000000..0821f68d13 --- /dev/null +++ b/lib/irb/help.rb @@ -0,0 +1,33 @@ +# +# irb/help.rb - print usase module +# $Release Version: 0.7.3$ +# $Revision$ +# $Date$ +# by Keiju ISHITSUKA(keiju@ishitsuka.com) +# +# -- +# +# +# + +module IRB + def IRB.print_usage + lc = IRB.conf[:LC_MESSAGES] + path = lc.find("irb/help-message") + space_line = false + File.foreach(path) do + |l| + if /^\s*$/ =~ l + lc.puts l unless space_line + space_line = true + next + end + space_line = false + + l.sub!(/#.*$/, "") + next if /^\s*$/ =~ l + lc.puts l + end + end +end + diff --git a/lib/irb/init.rb b/lib/irb/init.rb new file mode 100644 index 0000000000..f34a51b345 --- /dev/null +++ b/lib/irb/init.rb @@ -0,0 +1,232 @@ +# +# irb/init.rb - irb initialize module +# $Release Version: 0.7.3$ +# $Revision$ +# $Date$ +# by Keiju ISHITSUKA(keiju@ishitsuka.com) +# +# -- +# +# +# + +module IRB + + # initialize config + def IRB.initialize(ap_path) + IRB.init_config(ap_path) + IRB.init_error + IRB.run_config + end + + # @CONF default setting + def IRB.init_config(ap_path) + # class instance variables + @TRACER_INITIALIZED = false + @MATHN_INITIALIZED = false + + # default configurations + unless ap_path and @CONF[:AP_NAME] + ap_path = File.join(File.dirname(File.dirname(__FILE__)), "irb.rb") + end + @CONF[:AP_NAME] = File::basename(ap_path, ".rb") + + @CONF[:IRB_NAME] = "irb" + @CONF[:IRB_LIB_PATH] = File.dirname(__FILE__) + + @CONF[:RC] = true + @CONF[:LOAD_MODULES] = [] + @CONF[:IRB_RC] = nil + + @CONF[:MATH_MODE] = false + @CONF[:USE_READLINE] = false unless defined?(ReadlineInputMethod) + @CONF[:INSPECT_MODE] = nil + @CONF[:USE_TRACER] = false + @CONF[:USE_LOADER] = false + @CONF[:IGNORE_SIGINT] = true + @CONF[:IGNORE_EOF] = false + + @CONF[:BACK_TRACE_LIMIT] = 16 + + @CONF[:PROMPT] = { + :NULL => { + :PROMPT_I => nil, + :PROMPT_S => nil, + :PROMPT_C => nil, + :RETURN => "%s\n" + }, + :DEFAULT => { + :PROMPT_I => "%N(%m):%03n:%i> ", + :PROMPT_S => "%N(%m):%03n:%i%l ", + :PROMPT_C => "%N(%m):%03n:%i* ", + :RETURN => "%s\n" + }, + :SIMPLE => { + :PROMPT_I => ">> ", + :PROMPT_S => nil, + :PROMPT_C => "?> ", + :RETURN => "=> %s\n" + }, + :INF_RUBY => { + :PROMPT_I => "%N(%m):%03n:%i> ", + :PROMPT_S => nil, + :PROMPT_C => nil, + :RETURN => "%s\n", + :AUTO_INDENT => true + }, + :XMP => { + :PROMPT_I => nil, + :PROMPT_S => nil, + :PROMPT_C => nil, + :RETURN => " ==>%s\n" + } + } + + @CONF[:PROMPT_MODE] = :DEFAULT + @CONF[:AUTO_INDENT] = false + + @CONF[:CONTEXT_MODE] = 3 # use binding in function on TOPLEVEL_BINDING + @CONF[:SINGLE_IRB] = false + +# @CONF[:LC_MESSAGES] = "en" + @CONF[:LC_MESSAGES] = Locale.new + + @CONF[:DEBUG_LEVEL] = 1 + @CONF[:VERBOSE] = true + end + + def IRB.init_error + @CONF[:LC_MESSAGES].load("irb/error.rb") + end + + # option analyzing + def IRB.parse_opts + while opt = ARGV.shift + case opt + when "-f" + opt = ARGV.shift + @CONF[:RC] = false + when "-m" + @CONF[:MATH_MODE] = true + when "-d" + $DEBUG = true + when "-r" + opt = ARGV.shift + @CONF[:LOAD_MODULES].push opt if opt + when "--inspect" + @CONF[:INSPECT_MODE] = true + when "--noinspect" + @CONF[:INSPECT_MODE] = false + when "--readline" + @CONF[:USE_READLINE] = true + when "--noreadline" + @CONF[:USE_READLINE] = false + + when "--prompt-mode", "--prompt" + prompt_mode = ARGV.shift.upcase.tr("-", "_").intern + IRB.fail(UndefinedPromptMode, + prompt_mode.id2name) unless @CONF[:PROMPT][prompt_mode] + @CONF[:PROMPT_MODE] = prompt_mode + when "--noprompt" + @CONF[:PROMPT_MODE] = :NULL + when "--inf-ruby-mode" + @CONF[:PROMPT_MODE] = :INF_RUBY + when "--sample-book-mode", "--simple-prompt" + @CONF[:PROMPT_MODE] = :SIMPLE + + when "--tracer" + @CONF[:USE_TRACER] = true + when "--back-trace-limit" + @CONF[:BACK_TRACE_LIMIT] = ARGV.shift.to_i + when "--context-mode" + @CONF[:CONTEXT_MODE] = ARGV.shift.to_i + when "--single-irb" + @CONF[:SINGLE_IRB] = true + when "--irb_debug" + @CONF[:DEBUG_LEVEL] = ARGV.shift.to_i + when "-v", "--version" + print IRB.version, "\n" + exit 0 + when "-h", "--help" + require "irb/help" + IRB.print_usage + exit 0 + when /^-/ + IRB.fail UnrecognizedSwitch, opt + else + @CONF[:USE_READLINE] = false + @CONF[:SCRIPT] = opt + $0 = opt + break + end + end + end + + # running config + def IRB.run_config + if @CONF[:RC] + rcs = [] + rcs.push File.expand_path("~/.irbrc") if ENV.key?("HOME") + rcs.push ".irbrc" + rcs.push "irb.rc" + rcs.push "_irbrc" + rcs.push "$irbrc" + catch(:EXIT) do + for rc in rcs + begin + load rc + throw :EXIT + rescue LoadError, Errno::ENOENT + rescue + print "load error: #{rc}\n" + print $!.type, ": ", $!, "\n" + for err in $@[0, $@.size - 2] + print "\t", err, "\n" + end + throw :EXIT + end + end + end + end + end + + # loading modules + def IRB.load_modules + for m in @CONF[:LOAD_MODULES] + begin + require m + rescue + print $@[0], ":", $!.type, ": ", $!, "\n" + end + end + end + + # initialize tracing function + def IRB.initialize_tracer + unless @TRACER_INITIALIZED + require("tracer") + Tracer.verbose = false + Tracer.add_filter { + |event, file, line, id, binding| + File::dirname(file) != @CONF[:IRB_LIB_PATH] + } + @TRACER_INITIALIZED = true + end + end + + # initialize mathn function + def IRB.initialize_mathn + unless @MATHN_INITIALIZED + require "mathn" + @MATHN_INITIALIZED = true + end + end + + # initialize loader function + def IRB.initialize_loader + unless @LOADER_INITIALIZED + require "irb/loader" + @LOADER_INITIALIZED = true + end + end +end diff --git a/lib/irb/input-method.rb b/lib/irb/input-method.rb index 19df0eb073..ffbc6d9edc 100644 --- a/lib/irb/input-method.rb +++ b/lib/irb/input-method.rb @@ -1,9 +1,9 @@ # -# input-method.rb - input methods using irb -# $Release Version: 0.6$ +# irb/input-method.rb - input methods using irb +# $Release Version: 0.7.3$ # $Revision$ # $Date$ -# by Keiju ISHITSUKA(Nippon Rational Inc.) +# by Keiju ISHITSUKA(keiju@ishitsuka.com) # # -- # @@ -23,9 +23,9 @@ module IRB def initialize(file = STDIN_FILE_NAME) @file_name = file end - attr :file_name + attr_reader :file_name - attr :prompt, true + attr_accessor :prompt def gets IRB.fail NotImplementError, "gets" @@ -67,7 +67,7 @@ module IRB super @io = open(file) end - attr :file_name + attr_reader :file_name def eof? @io.eof? diff --git a/lib/irb/lc/error.rb b/lib/irb/lc/error.rb new file mode 100644 index 0000000000..de38f29978 --- /dev/null +++ b/lib/irb/lc/error.rb @@ -0,0 +1,30 @@ +# +# irb/lc/error.rb - +# $Release Version: 0.7.3$ +# $Revision$ +# $Date$ +# by Keiju ISHITSUKA(keiju@ishitsuka.com) +# +# -- +# +# +# +require "e2mmap" + +module IRB + + # exceptions + extend Exception2MessageMapper + def_exception :UnrecognizedSwitch, "Unrecognized switch: %s" + def_exception :NotImplementError, "Need to define `%s'" + def_exception :CantRetuenNormalMode, "Can't return normal mode." + def_exception :IllegalParameter, "Illegal parameter(%s)." + def_exception :IrbAlreadyDead, "Irb is already dead." + def_exception :IrbSwitchToCurrentThread, "Change to current thread." + def_exception :NoSuchJob, "No such job(%s)." + def_exception :CanNotGoMultiIrbMode, "Can't go multi irb mode." + def_exception :CanNotChangeBinding, "Can't change binding to (%s)." + def_exception :UndefinedPromptMode, "Undefined prompt mode(%s)." + +end + diff --git a/lib/irb/lc/help-message b/lib/irb/lc/help-message new file mode 100644 index 0000000000..8a3d63803b --- /dev/null +++ b/lib/irb/lc/help-message @@ -0,0 +1,34 @@ +# +# irb/lc/help-message.rb - +# $Release Version: 0.7.3$ +# $Revision$ +# $Date$ +# by Keiju ISHITSUKA(keiju@ishitsuka.com) +# +# -- +# +# +# +Usage: irb.rb [options] [programfile] [arguments] + -f suppress read ~/.irbrc + -m bc mode (load mathn, fraction or matrix are available) + -d set $DEBUG to true (same as `ruby -d') + -r load-module same as `ruby -r' + --inspect uses `inspect' for output (the default except bc mode) + --noinspect doesn't uses inspect for output + --readline uses Readline extension module + --noreadline doesn't use Readline extension module + --prompt prompt-mode + --prompt-mode prompt-mode + switches prompt mode. Pre-defined prompt modes are + `defalut', `simple', `xmp' and `inf-ruby' + --inf-ruby-mode uses prompt appreciate for inf-ruby-mode on emacs. + Suppresses --readline. + --simple-prompt simple prompt mode + --noprompt no prompt + --tracer display trace for each execution of commands. + --back-trace-limit n + displayes backtrace top n and tail n. The default + value is 16. + --irb_debug n sets internal debug level to n (It shouldn't be used) + -v, --version prints the version of irb diff --git a/lib/irb/lc/ja/error.rb b/lib/irb/lc/ja/error.rb new file mode 100644 index 0000000000..d5aef0a7c8 --- /dev/null +++ b/lib/irb/lc/ja/error.rb @@ -0,0 +1,29 @@ +# +# irb/lc/ja/error.rb - +# $Release Version: 0.7.3$ +# $Revision$ +# $Date$ +# by Keiju ISHITSUKA(keiju@ishitsuka.com) +# +# -- +# +# +# +require "e2mmap" + +module IRB + # exceptions + extend Exception2MessageMapper + def_exception :UnrecognizedSwitch, '$B%9%$%C%A(B(%s)$B$,J,$j$^$;$s(B' + def_exception :NotImplementError, '`%s\'$B$NDj5A$,I,MW$G$9(B' + def_exception :CantRetuenNormalMode, 'Normal$B%b!<%I$KLa$l$^$;$s(B.' + def_exception :IllegalParameter, '$B%Q%i%a!<%?(B(%s)$B$,4V0c$C$F$$$^$9(B.' + def_exception :IrbAlreadyDead, 'Irb$B$O4{$K;`$s$G$$$^$9(B.' + def_exception :IrbSwitchToCurrentThread, 'Change to current thread.' + def_exception :NoSuchJob, '$B$=$N$h$&$J%8%g%V(B(%s)$B$O$"$j$^$;$s(B.' + def_exception :CanNotGoMultiIrbMode, 'multi-irb mode$B$K0\$l$^$;$s(B.' + def_exception :CanNotChangeBinding, '$B%P%$%s%G%#%s%0(B(%s)$B$KJQ99$G$-$^$;$s(B.' + def_exception :UndefinedPromptMode, '$B%W%m%s%W%H%b!<%I(B(%s)$B$ODj5A$5$l$F$$$^$;$s(B.' +end + + diff --git a/lib/irb/lc/ja/help-message b/lib/irb/lc/ja/help-message new file mode 100644 index 0000000000..59f5c7a72b --- /dev/null +++ b/lib/irb/lc/ja/help-message @@ -0,0 +1,35 @@ +# +# irb/lc/ja/help-message.rb - +# $Release Version: 0.7.3$ +# $Revision$ +# $Date$ +# by Keiju ISHITSUKA(keiju@ishitsuka.com) +# +# -- +# +# +# +Usage: irb.rb [options] [programfile] [arguments] + -f ~/.irbrc $B$rFI$_9~$^$J$$(B. + -m bc$B%b!<%I(B($BJ,?t(B, $B9TNs$N7W;;$,$G$-$k(B) + -d $DEBUG $B$r(Btrue$B$K$9$k(B(ruby -d $B$HF1$8(B) + -r load-module ruby -r $B$HF1$8(B. + --inspect $B7k2L=PNO$K(Binspect$B$rMQ$$$k(B(bc$B%b!<%I0J30$O%G%U%)%k%H(B). + --noinspect $B7k2L=PNO$K(Binspect$B$rMQ$$$J$$(B. + --readline readline$B%i%$%V%i%j$rMxMQ$9$k(B. + --noreadline readline$B%i%$%V%i%j$rMxMQ$7$J$$(B. + --prompt prompt-mode/--prompt-mode prompt-mode + $B%W%m%s%W%H%b!<%I$r@ZBX$($^$9(B. $B8=:_Dj5A$5$l$F$$$k%W(B + $B%m%s%W%H%b!<%I$O(B, defalut, simple, xmp, inf-ruby$B$,(B + $BMQ0U$5$l$F$$$^$9(B. + --inf-ruby-mode emacs$B$N(Binf-ruby-mode$BMQ$N%W%m%s%W%HI=<($r9T$J$&(B. $BFC(B + $B$K;XDj$,$J$$8B$j(B, readline$B%i%$%V%i%j$O;H$o$J$/$J$k(B. + --simple-prompt $BHs>o$K%7%s%W%k$J%W%m%s%W%H$rMQ$$$k%b!<%I$G$9(B. + --noprompt $B%W%m%s%W%HI=<($r9T$J$o$J$$(B. + --tracer $B%3%^%s%I<B9T;~$K%H%l!<%9$r9T$J$&(B. + --back-trace-limit n + $B%P%C%/%H%l!<%9I=<($r%P%C%/%H%l!<%9$NF,$+$i(B n, $B8e$m(B + $B$+$i(Bn$B$@$19T$J$&(B. $B%G%U%)%k%H$O(B16 + --irb_debug n irb$B$N%G%P%C%0%G%P%C%0%l%Y%k$r(Bn$B$K@_Dj$9$k(B($BMxMQ$7$J(B + $B$$J}$,L5Fq$G$7$g$&(B). + -v, --version irb$B$N%P!<%8%g%s$rI=<($9$k(B diff --git a/lib/irb/loader.rb b/lib/irb/loader.rb index 83b10a55a0..6e7a89e454 100644 --- a/lib/irb/loader.rb +++ b/lib/irb/loader.rb @@ -1,9 +1,9 @@ # -# irb-loader.rb - -# $Release Version: 0.6$ +# irb/loader.rb - irb loader +# $Release Version: 0.7.3$ # $Revision$ # $Date$ -# by Keiju ISHITSUKA(Nippon Rational Inc.) +# by Keiju ISHITSUKA(keiju@ishitsuka.com) # # -- # diff --git a/lib/irb/locale.rb b/lib/irb/locale.rb new file mode 100644 index 0000000000..ef92ea1377 --- /dev/null +++ b/lib/irb/locale.rb @@ -0,0 +1,187 @@ +# +# irb/locale.rb - internationalization module +# $Release Version: 0.7.3$ +# $Revision$ +# $Date$ +# by Keiju ISHITSUKA(keiju@ishitsuka.com) +# +# -- +# +# +# + +require "kconv" + +module IRB + class Locale + @RCS_ID='-$Id$-' + + JPDefaultLocale = "ja" + LOCALE_DIR = "/lc/" + + LC2KCONV = { +# "ja" => Kconv::JIS, +# "ja_JP" => Kconv::JIS, + "ja_JP.ujis" => Kconv::EUC, + "ja_JP.euc" => Kconv::EUC, + "ja_JP.eucJP" => Kconv::EUC, + "ja_JP.sjis" => Kconv::SJIS, + "ja_JP.SJIS" => Kconv::SJIS, + } + + def initialize(locale = nil) + @lang = locale || ENV["IRB_LANG"] || ENV["LC_MESSAGES"] || ENV["LC_ALL"] || ENV["LANG"] + @lang = "C" unless @lang + end + + attr_reader :lang + + def String(mes) + mes = super(mes) + case @lang + when /^ja/ + mes = Kconv::kconv(mes, LC2KCONV[@lang]) + else + mes + end + mes + end + + def format(*opts) + String(super(*opts)) + end + + def gets(*rs) + String(super(*rs)) + end + + def readline(*rs) + String(super(*rs)) + end + + def print(*opts) + ary = opts.collect{|opt| String(opt)} + super *ary + end + + def printf(*opts) + s = format(*opts) + print s + end + + def puts(*opts) + ary = opts.collect{|opt| String(opts)} + super *ary + end + + autoload :Tempfile, "tempfile" + + def require(file, priv = nil) + rex = Regexp.new("lc/#{Regexp.quote(file)}\.(so|o|sl|rb)?") + return false if $".find{|f| f =~ rex} + + case file + when /\.rb$/ + begin + load(file, priv) + $".push file + return true + rescue LoadError + end + when /\.(so|o|sl)$/ + return super + end + + begin + load(f = file + ".rb") + $".push f #" + return true + rescue LoadError + return ruby_require(file) + end + end + + alias toplevel_load load + + def load(file, priv=nil) + dir = File.dirname(file) + dir = "" if dir == "." + base = File.basename(file) + + if /^ja(_JP)?$/ =~ @lang + back, @lang = @lang, "C" + end + begin + if dir[0] == ?/ #/ + lc_path = search_file(dir, base) + return real_load(lc_path, priv) if lc_path + end + + for path in $: + lc_path = search_file(path + "/" + dir, base) + return real_load(lc_path, priv) if lc_path + end + ensure + @lang = back if back + end + raise LoadError, "No such file to load -- #{file}" + end + + def real_load(path, priv) + tmp_base = path.tr("./:", "___") + lc_file = Tempfile.new(tmp_base) + File.foreach(path) do |line| + line = self.String(line) + lc_file.print(line) + end + lc_file.close + toplevel_load lc_file.path, priv + end + private :real_load + + def find(file , paths = $:) + dir = File.dirname(file) + dir = "" if dir == "." + base = File.basename(file) + if dir[0] == ?/ #/ + return lc_path = search_file(dir, base) + else + for path in $: + if lc_path = search_file(path + "/" + dir, base) + return lc_path + end + end + end + nil + end + + def search_file(path, file) + if File.exists?(p1 = path + lc_path(file, "C")) + if File.exists?(p2 = path + lc_path(file)) + return p2 + else + end + return p1 + else + end + nil + end + private :search_file + + def lc_path(file = "", lc = @lang) + case lc + when "C" + LOCALE_DIR + file + when /^ja/ + LOCALE_DIR + "ja/" + file + else + LOCALE_DIR + @lang + "/" + file + end + end + private :lc_path + end +end + + + + diff --git a/lib/irb/main.rb b/lib/irb/main.rb deleted file mode 100644 index 4c7dac240b..0000000000 --- a/lib/irb/main.rb +++ /dev/null @@ -1,867 +0,0 @@ -# -# main.rb - irb main module -# $Release Version: 0.6 $ -# $Revision$ -# $Date$ -# by Keiju ISHITSUKA(Nippon Rational Inc.) -# -# -- -# -# -# -require "e2mmap" -require "irb/ruby-lex" -require "irb/input-method" -require "irb/workspace-binding" - -STDOUT.sync = true - -module IRB - @RCS_ID='-$Id$-' - - # exceptions - extend Exception2MessageMapper - def_exception :UnrecognizedSwitch, "Unrecognized switch: %s" - def_exception :NotImplementError, "Need to define `%s'" - def_exception :CantRetuenNormalMode, "Can't return normal mode." - def_exception :IllegalParameter, "Illegal parameter(%s)." - def_exception :IrbAlreadyDead, "Irb is already dead." - def_exception :IrbSwitchToCurrentThread, "Change to current thread." - def_exception :NoSuchJob, "No such job(%s)." - def_exception :CanNotGoMultiIrbMode, "Can't go multi irb mode." - def_exception :CanNotChangeBinding, "Can't change binding to (%s)." - def_exception :UndefinedPromptMode, "Undefined prompt mode(%s)." - - class Abort < Exception;end - - # initialize IRB and start TOP_LEVEL irb - def IRB.start(ap_path = nil) - $0 = File::basename(ap_path, ".rb") if ap_path - - IRB.initialize(ap_path) - IRB.parse_opts - IRB.load_modules - - bind = workspace_binding - main = eval("self", bind) - - if @CONF[:SCRIPT] - irb = Irb.new(main, bind, @CONF[:SCRIPT]) - else - irb = Irb.new(main, bind) - end - - @CONF[:IRB_RC].call(irb.context) if @CONF[:IRB_RC] - @CONF[:MAIN_CONTEXT] = irb.context - - trap("SIGINT") do - irb.signal_handle - end - - catch(:IRB_EXIT) do - irb.eval_input - end - print "\n" - end - - # initialize config - def IRB.initialize(ap_path) - IRB.init_config(ap_path) - IRB.run_config - end - - # - # @CONF functions - # - @CONF = {} - # @CONF default setting - def IRB.init_config(ap_path) - # class instance variables - @TRACER_INITIALIZED = false - @MATHN_INITIALIZED = false - - # default configurations - unless ap_path and @CONF[:AP_NAME] - ap_path = File.join(File.dirname(File.dirname(__FILE__)), "irb.rb") - end - @CONF[:AP_NAME] = File::basename(ap_path, ".rb") - - @CONF[:IRB_NAME] = "irb" - @CONF[:IRB_LIB_PATH] = File.dirname(__FILE__) - - @CONF[:RC] = true - @CONF[:LOAD_MODULES] = [] - @CONF[:IRB_RC] = nil - - @CONF[:MATH_MODE] = false - @CONF[:USE_READLINE] = false unless defined?(ReadlineInputMethod) - @CONF[:INSPECT_MODE] = nil - @CONF[:USE_TRACER] = false - @CONF[:USE_LOADER] = false - @CONF[:IGNORE_SIGINT] = true - @CONF[:IGNORE_EOF] = false - - @CONF[:BACK_TRACE_LIMIT] = 16 - - @CONF[:PROMPT] = { - :NULL => { - :PROMPT_I => nil, - :PROMPT_S => nil, - :PROMPT_C => nil, - :RETURN => "%s\n" - }, - :DEFAULT => { - :PROMPT_I => "%N(%m):%03n:%i> ", - :PROMPT_S => "%N(%m):%03n:%i%l ", - :PROMPT_C => "%N(%m):%03n:%i* ", - :RETURN => "%s\n" - }, - :SIMPLE => { - :PROMPT_I => ">> ", - :PROMPT_S => nil, - :PROMPT_C => "?> ", - :RETURN => "=> %s\n" - }, - :INF_RUBY => { - :PROMPT_I => "%N(%m):%03n:%i> ", - :PROMPT_S => nil, - :PROMPT_C => nil, - :RETURN => "%s\n", - :AUTO_INDENT => true - }, - :XMP => { - :PROMPT_I => nil, - :PROMPT_S => nil, - :PROMPT_C => nil, - :RETURN => " ==>%s\n" - } - } - - @CONF[:PROMPT_MODE] = :DEFAULT - @CONF[:AUTO_INDENT] = false - - @CONF[:CONTEXT_MODE] = 3 - @CONF[:SINGLE_IRB] = false - - @CONF[:DEBUG_LEVEL] = 1 - @CONF[:VERBOSE] = true - end - - # IRB version method - def IRB.version - if v = @CONF[:VERSION] then return v end - - require "irb/version" - rv = @RELEASE_VERSION.sub(/\.0/, "") - @CONF[:VERSION] = format("irb %s(%s)", rv, @LAST_UPDATE_DATE) - end - - def IRB.conf - @CONF - end - - # option analyzing - def IRB.parse_opts - while opt = ARGV.shift - case opt - when "-f" - opt = ARGV.shift - @CONF[:RC] = false - when "-m" - @CONF[:MATH_MODE] = true - when "-d" - $DEBUG = true - when "-r" - opt = ARGV.shift - @CONF[:LOAD_MODULES].push opt if opt - when "--inspect" - @CONF[:INSPECT_MODE] = true - when "--noinspect" - @CONF[:INSPECT_MODE] = false - when "--readline" - @CONF[:USE_READLINE] = true - when "--noreadline" - @CONF[:USE_READLINE] = false - when "--prompt-mode", "--prompt" - prompt_mode = ARGV.shift.upcase.tr("-", "_").intern - IRB.fail(UndefinedPromptMode, - prompt_mode.id2name) unless @CONF[:PROMPT][prompt_mode] - @CONF[:PROMPT_MODE] = prompt_mode - when "--noprompt" - @CONF[:PROMPT_MODE] = :NULL - when "--inf-ruby-mode" - @CONF[:PROMPT_MODE] = :INF_RUBY - when "--sample-book-mode", "--simple-prompt" - @CONF[:PROMPT_MODE] = :SIMPLE - when "--tracer" - @CONF[:USE_TRACER] = true - when "--back-trace-limit" - @CONF[:BACK_TRACE_LIMIT] = ARGV.shift.to_i - when "--context-mode" - @CONF[:CONTEXT_MODE] = ARGV.shift.to_i - when "--single-irb" - @CONF[:SINGLE_IRB] = true - when "--irb_debug" - @CONF[:DEBUG_LEVEL] = ARGV.shift.to_i - when "-v", "--version" - print IRB.version, "\n" - exit(0) - when /^-/ - IRB.fail UnrecognizedSwitch, opt - else - @CONF[:USE_READLINE] = false - @CONF[:SCRIPT] = opt - $0 = opt - break - end - end - end - - # running config - def IRB.run_config - if @CONF[:RC] - rcs = [] - rcs.push File.expand_path("~/.irbrc") if ENV.key?("HOME") - rcs.push ".irbrc" - rcs.push "irb.rc" - rcs.push "_irbrc" - rcs.push "$irbrc" - catch(:EXIT) do - for rc in rcs - begin - load rc - throw :EXIT - rescue LoadError, Errno::ENOENT - rescue - print "load error: #{rc}\n" - print $!.type, ": ", $!, "\n" - for err in $@[0, $@.size - 2] - print "\t", err, "\n" - end - throw :EXIT - end - end - end - end - end - - # loading modules - def IRB.load_modules - for m in @CONF[:LOAD_MODULES] - begin - require m - rescue - print $@[0], ":", $!.type, ": ", $!, "\n" - end - end - end - - # initialize tracing function - def IRB.initialize_tracer - unless @TRACER_INITIALIZED - require("tracer") - Tracer.verbose = false - Tracer.add_filter { - |event, file, line, id, binding| - File::dirname(file) != @CONF[:IRB_LIB_PATH] - } - @TRACER_INITIALIZED = true - end - end - - # initialize mathn function - def IRB.initialize_mathn - unless @MATHN_INITIALIZED - require "mathn" - @MATHN_INITIALIZED = true - end - end - - # initialize loader function - def IRB.initialize_loader - unless @LOADER_INITIALIZED - require "irb/loader" - @LOADER_INITIALIZED = true - end - end - - def IRB.irb_exit(irb, ret) - throw :IRB_EXIT, ret - end - - def IRB.irb_abort(irb, exception = Abort) - if defined? Thread - irb.context.thread.raise exception, "abort then interrupt!!" - else - raise exception, "abort then interrupt!!" - end - end - - # - # irb interpriter main routine - # - class Irb - def initialize(main, bind, input_method = nil) - @context = Context.new(self, main, bind, input_method) - main.extend ExtendCommand - @signal_status = :IN_IRB - - @scanner = RubyLex.new - @scanner.exception_on_syntax_error = false - end - attr :context - attr :scanner, true - - def eval_input -# @scanner = RubyLex.new - @scanner.set_input(@context.io) do - signal_status(:IN_INPUT) do - unless l = @context.io.gets - if @context.ignore_eof? and @context.io.readable_atfer_eof? - l = "\n" - if @context.verbose? - printf "Use \"exit\" to leave %s\n", @context.ap_name - end - end - end - l - end - end - - @scanner.set_prompt do - |ltype, indent, continue, line_no| - if ltype - f = @context.prompt_s - elsif continue - f = @context.prompt_c - else @context.prompt_i - f = @context.prompt_i - end - f = "" unless f - @context.io.prompt = p = prompt(f, ltype, indent, line_no) - if @context.auto_indent_mode - unless ltype - ind = prompt(@context.prompt_i, ltype, indent, line_no).size + - indent * 2 - p.size - ind += 2 if continue - @context.io.prompt = p + " " * ind if ind > 0 - end - end - end - - @scanner.each_top_level_statement do - |line, line_no| - signal_status(:IN_EVAL) do - begin - trace_in do - @context._ = eval(line, @context.bind, @context.irb_path, line_no) -# @context._ = irb_eval(line, @context.bind, @context.irb_path, line_no) - end - - if @context.inspect? - printf @context.return_format, @context._.inspect - else - printf @context.return_format, @context._ - end - rescue StandardError, ScriptError, Abort - $! = RuntimeError.new("unknown exception raised") unless $! - print $!.type, ": ", $!, "\n" - if $@[0] =~ /irb(2)?(\/.*|-.*|\.rb)?:/ && $!.type.to_s !~ /^IRB/ - irb_bug = true - else - irb_bug = false - end - - messages = [] - lasts = [] - levels = 0 - for m in $@ - if m !~ /irb2?(\/.*|-.*|\.rb)?:/ or irb_bug - if messages.size < @context.back_trace_limit - messages.push m - else - lasts.push m - if lasts.size > @context.back_trace_limit - lasts.shift - levels += 1 - end - end - end - end - print messages.join("\n"), "\n" - unless lasts.empty? - printf "... %d levels...\n", levels if levels > 0 - print lasts.join("\n") - end - print "Maybe IRB bug!!\n" if irb_bug - end - end - end - end - -# def irb_eval(line, bind, path, line_no) -# id, str = catch(:IRB_TOPLEVEL_EVAL){ -# return eval(line, bind, path, line_no) -# } -# case id -# when :EVAL_TOPLEVEL -# eval(str, bind, "(irb_internal)", 1) -# when :EVAL_CONTEXT -# @context.instance_eval(str) -# else -# IRB.fail IllegalParameter -# end -# end - - def signal_handle - unless @context.ignore_sigint? - print "\nabort!!\n" if @context.verbose? - exit - end - - case @signal_status - when :IN_INPUT - print "^C\n" - @scanner.initialize_input - print @context.io.prompt - when :IN_EVAL - IRB.irb_abort(self) - when :IN_LOAD - IRB.irb_abort(self, LoadAbort) - when :IN_IRB - # ignore - else - # ignore - end - end - - def signal_status(status) - return yield if @signal_status == :IN_LOAD - - signal_status_back = @signal_status - @signal_status = status - begin - yield - ensure - @signal_status = signal_status_back - end - end - - def trace_in - Tracer.on if @context.use_tracer? - begin - yield - ensure - Tracer.off if @context.use_tracer? - end - end - - def prompt(prompt, ltype, indent, line_no) - p = prompt.dup - p.gsub!(/%([0-9]+)?([a-zA-Z])/) do - case $2 - when "N" - @context.irb_name - when "m" - @context.main.to_s - when "M" - @context.main.inspect - when "l" - ltype - when "i" - if $1 - format("%" + $1 + "d", indent) - else - indent.to_s - end - when "n" - if $1 - format("%" + $1 + "d", line_no) - else - line_no.to_s - end - when "%" - "%" - end - end - p - end - - def inspect - ary = [] - for iv in instance_variables - case iv - when "@signal_status" - ary.push format("%s=:%s", iv, @signal_status.id2name) - when "@context" - ary.push format("%s=%s", iv, eval(iv).__to_s__) - else - ary.push format("%s=%s", iv, eval(iv)) - end - end - format("#<%s: %s>", type, ary.join(", ")) - end - end - - # - # irb context - # - class Context - # - # Arguments: - # input_method: nil -- stdin or readline - # String -- File - # other -- using this as InputMethod - # - def initialize(irb, main, bind, input_method = nil) - @irb = irb - @main = main - @bind = bind - @thread = Thread.current if defined? Thread - @irb_level = 0 - - # copy of default configuration - @ap_name = IRB.conf[:AP_NAME] - @rc = IRB.conf[:RC] - @load_modules = IRB.conf[:LOAD_MODULES] - - self.math_mode = IRB.conf[:MATH_MODE] - @use_readline = IRB.conf[:USE_READLINE] - @inspect_mode = IRB.conf[:INSPECT_MODE] - @use_tracer = IRB.conf[:USE_TRACER] -# @use_loader = IRB.conf[:USE_LOADER] - - self.prompt_mode = IRB.conf[:PROMPT_MODE] - - @ignore_sigint = IRB.conf[:IGNORE_SIGINT] - @ignore_eof = IRB.conf[:IGNORE_EOF] - - @back_trace_limit = IRB.conf[:BACK_TRACE_LIMIT] - - debug_level = IRB.conf[:DEBUG_LEVEL] - @verbose = IRB.conf[:VERBOSE] - - @tracer_initialized = false - - if IRB.conf[:SINGLE_IRB] or !defined?(JobManager) - @irb_name = IRB.conf[:IRB_NAME] - else - @irb_name = "irb#"+IRB.JobManager.n_jobs.to_s - end - @irb_path = "(" + @irb_name + ")" - - case input_method - when nil - if (use_readline.nil? && IRB.conf[:PROMPT_MODE] != :INF_RUBY || - use_readline?) - @io = ReadlineInputMethod.new - else - @io = StdioInputMethod.new - end - when String - @io = FileInputMethod.new(input_method) - @irb_name = File.basename(input_method) - @irb_path = input_method - else - @io = input_method - end - end - - attr :bind, true - attr :main, true - attr :thread - attr :io, true - - attr :_ - - attr :irb - attr :ap_name - attr :rc - attr :load_modules - attr :irb_name - attr :irb_path - - attr :math_mode, true - attr :use_readline, true - attr :inspect_mode - attr :use_tracer -# attr :use_loader - - attr :debug_level - attr :verbose, true - - attr :prompt_mode - attr :prompt_i, true - attr :prompt_s, true - attr :prompt_c, true - attr :auto_indent_mode, true - attr :return_format, true - - attr :ignore_sigint, true - attr :ignore_eof, true - - attr :back_trace_limit - -# alias use_loader? use_loader - alias use_tracer? use_tracer - alias use_readline? use_readline - alias rc? rc - alias math? math_mode - alias verbose? verbose - alias ignore_sigint? ignore_sigint - alias ignore_eof? ignore_eof - - def _=(value) - @_ = value - eval "_ = IRB.conf[:MAIN_CONTEXT]._", @bind - end - - def irb_name - if @irb_level == 0 - @irb_name - elsif @irb_name =~ /#[0-9]*$/ - @irb_name + "." + @irb_level.to_s - else - @irb_name + "#0." + @irb_level.to_s - end - end - - def prompt_mode=(mode) - @prompt_mode = mode - pconf = IRB.conf[:PROMPT][mode] - @prompt_i = pconf[:PROMPT_I] - @prompt_s = pconf[:PROMPT_S] - @prompt_c = pconf[:PROMPT_C] - @return_format = pconf[:RETURN] - if ai = pconf.include?(:AUTO_INDENT) - @auto_indent_mode = ai - else - @auto_indent_mode = IRB.conf[:AUTO_INDENT] - end - end - - def inspect? - @inspect_mode.nil? && !@math_mode or @inspect_mode - end - - def file_input? - @io.type == FileInputMethod - end - - def use_tracer=(opt) - if opt - IRB.initialize_tracer - unless @tracer_initialized - Tracer.set_get_line_procs(@irb_path) { - |line_no| - @io.line(line_no) - } - @tracer_initialized = true - end - elsif !opt && @use_tracer - Tracer.off - end - @use_tracer=opt - end - - def use_loader - IRB.conf[:USE_LOADER] - end - - def use_loader=(opt) - IRB.conf[:USE_LOADER] = opt - if opt - IRB.initialize_loader - end - print "Switch to load/require#{unless use_loader; ' non';end} trace mode.\n" if verbose? - opt - end - - def inspect_mode=(opt) - if opt - @inspect_mode = opt - else - @inspect_mode = !@inspect_mode - end - print "Switch to#{unless @inspect_mode; ' non';end} inspect mode.\n" if verbose? - @inspect_mode - end - - def math_mode=(opt) - if @math_mode == true && opt == false - IRB.fail CantRetuenNormalMode - return - end - - @math_mode = opt - if math_mode - IRB.initialize_mathn - @main.instance_eval("include Math") - print "start math mode\n" if verbose? - end - end - - def use_readline=(opt) - @use_readline = opt - print "use readline module\n" if @use_readline - end - - def debug_level=(value) - @debug_level = value - RubyLex.debug_level = value - SLex.debug_level = value - end - - def debug? - @debug_level > 0 - end - - def change_binding(*main) - back = [@bind, @main] - @bind = IRB.workspace_binding(*main) - unless main.empty? - @main = eval("self", @bind) - begin - @main.extend ExtendCommand - rescue - print "can't change binding to: ", @main.inspect, "\n" - @bind, @main = back - return nil - end - end - @irb_level += 1 - begin - catch(:SU_EXIT) do - @irb.eval_input - end - ensure - @irb_level -= 1 - @bind, @main = back - end - end - - alias __exit__ exit - def exit(ret = 0) - if @irb_level == 0 - IRB.irb_exit(@irb, ret) - else - throw :SU_EXIT, ret - end - end - - NOPRINTING_IVARS = ["@_"] - NO_INSPECTING_IVARS = ["@irb", "@io"] - IDNAME_IVARS = ["@prompt_mode"] - - alias __inspect__ inspect - def inspect - array = [] - for ivar in instance_variables.sort{|e1, e2| e1 <=> e2} - name = ivar.sub(/^@(.*)$/){$1} - val = instance_eval(ivar) - case ivar - when *NOPRINTING_IVARS - next - when *NO_INSPECTING_IVARS - array.push format("conf.%s=%s", name, val.to_s) - when *IDNAME_IVARS - array.push format("conf.%s=:%s", name, val.id2name) - else - array.push format("conf.%s=%s", name, val.inspect) - end - end - array.join("\n") - end - alias __to_s__ to_s - alias to_s inspect - - end - - # - # IRB extended command - # - module Loader; end - module ExtendCommand - include Loader - - alias irb_exit_org exit - def irb_exit(ret = 0) - irb_context.exit(ret) - end - alias exit irb_exit - alias quit irb_exit - - alias irb_fork fork - def fork(&block) - unless irb_fork - eval "alias exit irb_exit_org" - instance_eval "alias exit irb_exit_org" - if iterator? - yield - exit - end - end - end - - def irb_change_binding(*main) - irb_context.change_binding(*main) - end - alias cb irb_change_binding - - def irb_source(file) - irb_context.source(file) - end - alias source irb_source - - def irb(*obj) - require "irb/multi-irb" - IRB.irb(nil, *obj) - end - - def irb_context - IRB.conf[:MAIN_CONTEXT] - end - alias conf irb_context - - def irb_jobs - require "irb/multi-irb" - IRB.JobManager - end - alias jobs irb_jobs - - def irb_fg(key) - require "irb/multi-irb" - IRB.JobManager.switch(key) - end - alias fg irb_fg - - def irb_kill(*keys) - require "irb/multi-irb" - IRB.JobManager.kill(*keys) - end - alias kill irb_kill - end - - # Singleton method - def @CONF.inspect - IRB.version unless self[:VERSION] - - array = [] - for k, v in sort{|a1, a2| a1[0].id2name <=> a2[0].id2name} - case k - when :MAIN_CONTEXT - next - when :PROMPT - s = v.collect{ - |kk, vv| - ss = vv.collect{|kkk, vvv| ":#{kkk.id2name}=>#{vvv.inspect}"} - format(":%s=>{%s}", kk.id2name, ss.join(", ")) - } - array.push format("CONF[:%s]={%s}", k.id2name, s.join(", ")) - else - array.push format("CONF[:%s]=%s", k.id2name, v.inspect) - end - end - array.join("\n") - end -end diff --git a/lib/irb/multi-irb.rb b/lib/irb/multi-irb.rb index 39dbcbae3c..6e97512e27 100644 --- a/lib/irb/multi-irb.rb +++ b/lib/irb/multi-irb.rb @@ -1,9 +1,9 @@ # -# multi-irb.rb - multiple irb module -# $Release Version: 0.6$ +# irb/multi-irb.rb - multiple irb module +# $Release Version: 0.7.3$ # $Revision$ # $Date$ -# by Keiju ISHITSUKA(Nippon Rational Inc.) +# by Keiju ISHITSUKA(keiju@ishitsuka.com) # # -- # @@ -23,7 +23,7 @@ module IRB @current_job = nil end - attr :current_job, true + attr_accessor :current_job def n_jobs @jobs.size @@ -31,7 +31,7 @@ module IRB def thread(key) th, irb = search(key) - irb + th end def irb(key) @@ -74,7 +74,7 @@ module IRB when Integer @jobs[key] when Irb - @jobs.find{|k, v| v.equal?(irb)} + @jobs.find{|k, v| v.equal?(key)} when Thread @jobs.assoc(key) else @@ -140,20 +140,15 @@ module IRB @JobManager end - # invoke multiple irb + # invoke multi-irb def IRB.irb(file = nil, *main) - workspace = IRB.workspace_binding(*main) - if main.empty? - main = eval("self", workspace) - else - main = main[0] - end + workspace = WorkSpace.new(*main) parent_thread = Thread.current Thread.start do begin - irb = Irb.new(main, workspace, file) + irb = Irb.new(workspace, file) rescue - print "Subirb can't start with context(self): ", main.inspect, "\n" + print "Subirb can't start with context(self): ", workspace.main.inspect, "\n" print "return to main irb\n" Thread.pass Thread.main.wakeup @@ -161,6 +156,7 @@ module IRB end @CONF[:IRB_RC].call(irb.context) if @CONF[:IRB_RC] @JobManager.insert(irb) + @JobManager.current_job = irb begin system_exit = false catch(:IRB_EXIT) do @@ -190,7 +186,7 @@ module IRB class Context def _=(value) @_ = value - eval "_ = IRB.JobManager.irb(Thread.current).context._", @bind + @workspace.evaluate "_ = IRB.JobManager.irb(Thread.current).context._" end end @@ -198,15 +194,39 @@ module IRB def irb_context IRB.JobManager.irb(Thread.current).context end - alias conf irb_context +# alias conf irb_context end @CONF[:SINGLE_IRB_MODE] = false @JobManager.insert(@CONF[:MAIN_CONTEXT].irb) @JobManager.current_job = @CONF[:MAIN_CONTEXT].irb + class Irb + def signal_handle + unless @context.ignore_sigint? + print "\nabort!!\n" if @context.verbose? + exit + end + + case @signal_status + when :IN_INPUT + print "^C\n" + IRB.JobManager.thread(self).raise RubyLex::TerminateLineInput + when :IN_EVAL + IRB.irb_abort(self) + when :IN_LOAD + IRB.irb_abort(self, LoadAbort) + when :IN_IRB + # ignore + else + # ignore + end + end + end + trap("SIGINT") do @JobManager.current_job.signal_handle + Thread.stop end end diff --git a/lib/irb/ruby-lex.rb b/lib/irb/ruby-lex.rb index 4c7a3b1002..3a862002a6 100644 --- a/lib/irb/ruby-lex.rb +++ b/lib/irb/ruby-lex.rb @@ -1,9 +1,9 @@ # -# ruby-lex.rb - ruby lexcal analizer -# $Release Version: 0.6$ +# irb/ruby-lex.rb - ruby lexcal analizer +# $Release Version: 0.7.3$ # $Revision$ # $Date$ -# by Keiju ISHITSUKA(Nippon Rational Inc.) +# by Keiju ISHITSUKA(keiju@ishitsuka.com) # # -- # @@ -24,11 +24,13 @@ class RubyLex def_exception(:TkReading2TokenDuplicateError, "key duplicate(token_n='%s', key='%s')") def_exception(:SyntaxError, "%s") + + def_exception(:TerminateLineInput, "Terminate Line Input") include RubyToken class << self - attr :debug_level, TRUE + attr_accessor :debug_level def debug? @debug_level > 0 end @@ -54,14 +56,14 @@ class RubyLex @exception_on_syntax_error = true end - attr :skip_space, true - attr :readed_auto_clean_up, true - attr :exception_on_syntax_error, true + attr_accessor :skip_space + attr_accessor :readed_auto_clean_up + attr_accessor :exception_on_syntax_error - attr :seek - attr :char_no - attr :line_no - attr :indent + attr_reader :seek + attr_reader :char_no + attr_reader :line_no + attr_reader :indent # io functions def set_input(io, p = nil) @@ -202,8 +204,8 @@ class RubyLex @space_seen = false @here_header = false + @continue = false prompt - @continue = FALSE @line = "" @exp_line_no = @line_no @@ -211,27 +213,35 @@ class RubyLex def each_top_level_statement initialize_input - loop do - @continue = FALSE - prompt - unless l = lex - break if @line == '' - else - # p l - @line.concat l - if @ltype or @continue or @indent > 0 - next + catch(:TERM_INPUT) do + loop do + begin + @continue = false + prompt + unless l = lex + throw :TERM_INPUT if @line == '' + else + #p l + @line.concat l + if @ltype or @continue or @indent > 0 + next + end + end + if @line != "\n" + yield @line, @exp_line_no + end + break unless l + @line = '' + @exp_line_no = @line_no + + @indent = 0 + prompt + rescue TerminateLineInput + initialize_input + prompt + get_readed end end - if @line != "\n" - yield @line, @exp_line_no - end - break unless l - @line = '' - @exp_line_no = @line_no - - @indent = 0 - prompt end end @@ -239,8 +249,8 @@ class RubyLex until (((tk = token).kind_of?(TkNL) || tk.kind_of?(TkEND_OF_SCRIPT)) && !@continue or tk.nil?) - # p tk - # p self + #p tk + #p self end line = get_readed # print self.inspect @@ -315,7 +325,7 @@ class RubyLex end @OP.def_rules(" ", "\t", "\f", "\r", "\13") do - @space_seen = TRUE + @space_seen = true while getc =~ /[ \t\f\r\13]/; end ungetc Token(TkSPACE) @@ -333,7 +343,7 @@ class RubyLex until peek_equal?("=end") && peek(4) =~ /\s/ until getc == "\n"; end end - getc; getc; getc; getc + gets @ltype = nil Token(TkRD_COMMENT) end @@ -342,9 +352,9 @@ class RubyLex print "\\n\n" if RubyLex.debug? case @lex_state when EXPR_BEG, EXPR_FNAME, EXPR_DOT - @continue = TRUE + @continue = true else - @continue = FALSE + @continue = false @lex_state = EXPR_BEG end @here_header = false @@ -458,7 +468,7 @@ class RubyLex ungetc identify_number else - # for obj.if + # for "obj.if" etc. @lex_state = EXPR_DOT Token(TkDOT) end @@ -608,7 +618,7 @@ class RubyLex identify_quotation elsif peek(0) == '=' getc - Token(OP_ASGIN, "%") + Token(TkOPASGN, :%) elsif @lex_state == EXPR_ARG and @space_seen and peek(0) !~ /\s/ identify_quotation else @@ -691,7 +701,7 @@ class RubyLex if ch == "!" or ch == "?" token.concat getc end - # fix token + # almost fix token case token when /^\$/ @@ -752,6 +762,7 @@ class RubyLex def identify_here_document ch = getc +# if lt = PERCENT_LTYPE[ch] if ch == "-" ch = getc indent = true @@ -835,8 +846,8 @@ class RubyLex end type = TkINTEGER - allow_point = TRUE - allow_e = TRUE + allow_point = true + allow_e = true while ch = getc case ch when /[0-9_]/ @@ -954,7 +965,7 @@ class RubyLex read_escape(chrs) end else - # other characters + # other characters end end end diff --git a/lib/irb/ruby-token.rb b/lib/irb/ruby-token.rb index 1532dc78eb..2e5715c9f7 100644 --- a/lib/irb/ruby-token.rb +++ b/lib/irb/ruby-token.rb @@ -1,9 +1,9 @@ # -# ruby-token.rb - ruby tokens -# $Release Version: 0.6$ +# irb/ruby-token.rb - ruby tokens +# $Release Version: 0.7.3$ # $Revision$ # $Date$ -# by Keiju ISHITSUKA(Nippon Rational Inc.) +# by Keiju ISHITSUKA(keiju@ishitsuka.com) # # -- # @@ -17,6 +17,11 @@ module RubyToken EXPR_FNAME = :EXPR_FNAME EXPR_DOT = :EXPR_DOT EXPR_CLASS = :EXPR_CLASS + + # for ruby 1.4X + if !defined?(Symbol) + Symbol = Integer + end class Token def initialize(seek, line_no, char_no) @@ -241,7 +246,7 @@ module RubyToken TkSymbol2Token = {} def RubyToken.def_token(token_n, super_token = Token, reading = nil, *opts) - token_n = token_n.id2name unless token_n.kind_of?(String) + token_n = token_n.id2name if token_n.kind_of?(Symbol) if RubyToken.const_defined?(token_n) IRB.fail AlreadyDefinedToken, token_n end diff --git a/lib/irb/slex.rb b/lib/irb/slex.rb index 85aa92bd73..26008906e5 100644 --- a/lib/irb/slex.rb +++ b/lib/irb/slex.rb @@ -1,9 +1,9 @@ # -# irb-slex.rb - symple lex analizer -# $Release Version: 0.6$ +# irb/slex.rb - symple lex analizer +# $Release Version: 0.7.3$ # $Revision$ # $Date$ -# by Keiju ISHITSUKA(Nippon Rational Inc.) +# by Keiju ISHITSUKA(keiju@ishituska.com) # # -- # @@ -20,7 +20,7 @@ class SLex def_exception :ErrNodeAlreadyExists, "node already exists" class << self - attr :debug_level, TRUE + attr_accessor :debug_level def debug? debug_level > 0 end @@ -88,16 +88,16 @@ class SLex # #---------------------------------------------------------------------- class Node - # if postproc no exist, this node is abstract node. - # if postproc isn't nil, this node is real node. + # if postproc is nil, this node is an abstract node. + # if postproc is non-nil, this node is a real node. def initialize(preproc = nil, postproc = nil) @Tree = {} @preproc = preproc @postproc = postproc end - attr :preproc, TRUE - attr :postproc, TRUE + attr_accessor :preproc + attr_accessor :postproc def search(chrs, opt = nil) return self if chrs.empty? @@ -159,8 +159,8 @@ class SLex # # chrs: String # character array - # io It must have getc()/ungetc(), and ungetc() can be - # called any number of times. + # io must have getc()/ungetc(); and ungetc() must be + # able to be called arbitrary number of times. # def match(chrs, op = "") print "match>: ", chrs, "op:", op, "\n" if SLex.debug? @@ -265,7 +265,7 @@ if $0 == __FILE__ print "0: ", tr.inspect, "\n" tr.def_rule("=") {print "=\n"} print "1: ", tr.inspect, "\n" - tr.def_rule("==", proc{FALSE}) {print "==\n"} + tr.def_rule("==", proc{false}) {print "==\n"} print "2: ", tr.inspect, "\n" print "case 1:\n" diff --git a/lib/irb/version.rb b/lib/irb/version.rb index 7179d1c163..367cc21046 100644 --- a/lib/irb/version.rb +++ b/lib/irb/version.rb @@ -1,9 +1,9 @@ # -# version.rb - irb version definition file -# $Release Version: 0.6.1$ +# irb/version.rb - irb version definition file +# $Release Version: 0.7.4$ # $Revision$ # $Date$ -# by Keiju ISHITSUKA(Nihon Rational Software Co.,Ltd) +# by Keiju ISHITSUKA(keiju@ishitsuka.com) # # -- # @@ -11,6 +11,6 @@ # module IRB - @RELEASE_VERSION = "0.6.1" - @LAST_UPDATE_DATE = "99/09/16" + @RELEASE_VERSION = "0.7.4" + @LAST_UPDATE_DATE = "01/05/08" end diff --git a/lib/irb/workspace-binding-2.rb b/lib/irb/workspace-binding-2.rb deleted file mode 100644 index d005296f6e..0000000000 --- a/lib/irb/workspace-binding-2.rb +++ /dev/null @@ -1,15 +0,0 @@ -# -# bind.rb - -# $Release Version: $ -# $Revision$ -# $Date$ -# by Keiju ISHITSUKA(Nihon Rational Software Co.,Ltd) -# -# -- -# -# -# - -while true - IRB::BINDING_QUEUE.push b = binding -end diff --git a/lib/irb/workspace-binding.rb b/lib/irb/workspace-binding.rb deleted file mode 100644 index d58088d9dd..0000000000 --- a/lib/irb/workspace-binding.rb +++ /dev/null @@ -1,77 +0,0 @@ -# -# workspace-binding.rb - -# $Release Version: $ -# $Revision$ -# $Date$ -# by Keiju ISHITSUKA(Nihon Rational Software Co.,Ltd) -# -# -- -# -# -# - - -module IRB - # create new workspace. - def IRB.workspace_binding(*main) - if @CONF[:SINGLE_IRB] - bind = TOPLEVEL_BINDING - else - case @CONF[:CONTEXT_MODE] - when 0 - bind = eval("proc{binding}.call", - TOPLEVEL_BINDING, - "(irb_local_binding)", - 1) - when 1 - require "tempfile" - f = Tempfile.open("irb-binding") - f.print <<EOF - $binding = binding -EOF - f.close - load f.path - bind = $binding - - when 2 - unless defined? BINDING_QUEUE - require "thread" - - IRB.const_set("BINDING_QUEUE", SizedQueue.new(1)) - Thread.abort_on_exception = true - Thread.start do - eval "require \"irb/workspace-binding-2\"", TOPLEVEL_BINDING, __FILE__, __LINE__ - end - Thread.pass - - end - - bind = BINDING_QUEUE.pop - - when 3 - bind = eval("def irb_binding; binding; end; irb_binding", - TOPLEVEL_BINDING, - __FILE__, - __LINE__ - 3) - end - end - unless main.empty? - @CONF[:__MAIN__] = main[0] - case main[0] - when Module - bind = eval("IRB.conf[:__MAIN__].module_eval('binding')", bind) - else - begin - bind = eval("IRB.conf[:__MAIN__].instance_eval('binding')", bind) - rescue TypeError - IRB.fail CanNotChangeBinding, main[0].inspect - end - end - end - eval("_=nil", bind) - bind - end - - def IRB.delete_caller - end -end diff --git a/lib/irb/workspace.rb b/lib/irb/workspace.rb new file mode 100644 index 0000000000..68559a1173 --- /dev/null +++ b/lib/irb/workspace.rb @@ -0,0 +1,106 @@ +# +# irb/workspace-binding.rb - +# $Release Version: 0.7.3$ +# $Revision$ +# $Date$ +# by Keiju ISHITSUKA(keiju@ishitsuka.com) +# +# -- +# +# +# +module IRB + class WorkSpace + # create new workspace. + # set self to main if specified, otherwise inherit main + # from TOPLEVEL_BINDING. + def initialize(*main) + if IRB.conf[:SINGLE_IRB] + @binding = TOPLEVEL_BINDING + else + case IRB.conf[:CONTEXT_MODE] + when 0 # binding in proc on TOPLEVEL_BINDING + @binding = eval("proc{binding}.call", + TOPLEVEL_BINDING, + __FILE__, + __LINE__) + when 1 # binding in loaded file + require "tempfile" + f = Tempfile.open("irb-binding") + f.print <<EOF + $binding = binding +EOF + f.close + load f.path + @binding = $binding + + when 2 # binding in loaded file(thread use) + unless defined? BINDING_QUEUE + require "thread" + + IRB.const_set("BINDING_QUEUE", SizedQueue.new(1)) + Thread.abort_on_exception = true + Thread.start do + eval "require \"irb/ws-for-case-2\"", TOPLEVEL_BINDING, __FILE__, __LINE__ + end + Thread.pass + end + @binding = BINDING_QUEUE.pop + + when 3 # binging in function on TOPLEVEL_BINDING(default) + @binding = eval("def irb_binding; binding; end; irb_binding", + TOPLEVEL_BINDING, + __FILE__, + __LINE__ - 3) + end + end + if main.empty? + @main = eval "self", @binding + else + @main = main[0] + IRB.conf[:__MAIN__] = @main + case @main + when Module + @binding = eval("IRB.conf[:__MAIN__].module_eval('binding', __FILE__, __LINE__)", @binding, __FILE__, __LINE__) + else + begin + @binding = eval("IRB.conf[:__MAIN__].instance_eval('binding', __FILE__, __LINE__)", @binding, __FILE__, __LINE__) + rescue TypeError + IRB.fail CanNotChangeBinding, @main.inspect + end + end + end + eval("_=nil", @binding) + end + + attr_reader :binding + attr_reader :main + + def evaluate(statements, file = __FILE__, line = __LINE__) + eval statements, @binding, file, line + end + + # error message manupilator + def filter_backtrace(bt) + case IRB.conf[:CONTEXT_MODE] + when 0 + return nil if bt =~ /\(irb_local_binding\)/ + when 1 + if(bt =~ %r!/tmp/irb-binding! or + bt =~ %r!irb/.*\.rb! or + bt =~ /irb\.rb/) + return nil + end + when 2 + return nil if bt =~ /irb\/.*\.rb/ + when 3 + return nil if bt =~ /irb\/.*\.rb/ + bt.sub!(/:\s*in `irb_binding'/){""} + end + bt + end + + def IRB.delete_caller + end + end +end diff --git a/lib/irb/ws-for-case-2.rb b/lib/irb/ws-for-case-2.rb new file mode 100644 index 0000000000..8cfa87ae3d --- /dev/null +++ b/lib/irb/ws-for-case-2.rb @@ -0,0 +1,15 @@ +# +# irb/ws-for-case-2.rb - +# $Release Version: 0.7.3$ +# $Revision$ +# $Date$ +# by Keiju ISHITSUKA(keiju@ishitsuka.com) +# +# -- +# +# +# + +while true + IRB::BINDING_QUEUE.push b = binding +end diff --git a/lib/irb/xmp.rb b/lib/irb/xmp.rb index fc745a2757..e0bcee4bdb 100644 --- a/lib/irb/xmp.rb +++ b/lib/irb/xmp.rb @@ -1,6 +1,6 @@ # # xmp.rb - irb version of gotoken xmp -# $Release Version: 0.6$ +# $Release Version: 0.7.1$ # $Revision$ # $Date$ # by Keiju ISHITSUKA(Nippon Rational Inc.) diff --git a/lib/mkmf.rb b/lib/mkmf.rb index 317200ba79..e9c78dace7 100644 --- a/lib/mkmf.rb +++ b/lib/mkmf.rb @@ -12,12 +12,14 @@ SRC_EXT = ["c", "cc", "m", "cxx", "cpp", "C"] $config_cache = CONFIG["compile_dir"]+"/ext/config.cache" $srcdir = CONFIG["srcdir"] -$libdir = CONFIG["libdir"]+"/ruby/"+CONFIG["MAJOR"]+"."+CONFIG["MINOR"] -$archdir = $libdir+"/"+CONFIG["arch"] -$sitelibdir = CONFIG["sitedir"]+"/"+CONFIG["MAJOR"]+"."+CONFIG["MINOR"] -$sitearchdir = $sitelibdir+"/"+CONFIG["arch"] - -if File.exist? $archdir + "/ruby.h" +$libdir = CONFIG["libdir"] +$rubylibdir = CONFIG["rubylibdir"] +$archdir = CONFIG["archdir"] +$sitedir = CONFIG["sitedir"] +$sitelibdir = CONFIG["sitelibdir"] +$sitearchdir = CONFIG["sitearchdir"] + +if File.exist? Config::CONFIG["archdir"] + "/ruby.h" $hdrdir = $archdir elsif File.exist? $srcdir + "/ruby.h" $hdrdir = $srcdir @@ -43,7 +45,7 @@ else $null = open('test.log', 'w') end -LINK = "#{CONFIG['CC']} -o conftest -I#{$hdrdir} #{CFLAGS} -I#{CONFIG['includedir']} %s #{CONFIG['LDFLAGS']} %s conftest.c %s %s #{CONFIG['LIBS']}" +LINK = "#{CONFIG['CC']} -o conftest -I#{$hdrdir} #{CFLAGS} -I#{CONFIG['includedir']} %s %s #{CONFIG['LDFLAGS']} %s conftest.c %s %s #{CONFIG['LIBS']}" CPP = "#{CONFIG['CPP']} -E %s -I#{$hdrdir} #{CFLAGS} -I#{CONFIG['includedir']} %s %s conftest.c" def rm_f(*files) @@ -60,6 +62,7 @@ end $orgerr = $stderr.dup $orgout = $stdout.dup def xsystem command + Config.expand(command) if $DEBUG print command, "\n" return system(command) @@ -155,7 +158,9 @@ def install_rb(mfile, dest, srcdir = nil) mfile.printf "\t@$(RUBY) -r ftools -e 'File::makedirs(*ARGV)' %s/%s\n", dest, f end for f in path - mfile.printf "\t@$(RUBY) -r ftools -e 'File::install(ARGV[0], ARGV[1], 0644, true)' lib/%s %s/%s\n", f, dest, f + d = '/' + File::dirname(f) + d = '' if d == '/.' + mfile.printf "\t@$(RUBY) -r ftools -e 'File::install(ARGV[0], ARGV[1], 0644, true)' %s/%s %s%s\n", libdir, f, dest, d end end @@ -334,24 +339,32 @@ def dir_config(target, idefault=nil, ldefault=nil) idefault = default + "/include" ldefault = default + "/lib" end - dir = with_config("%s-dir"%target, default) - if dir - idir = " -I"+dir+"/include" - ldir = dir+"/lib" - end - unless idir - dir = with_config("%s-include"%target, idefault) - idir = " -I"+dir if dir + + dir = with_config(target + "-dir", default) + + idir, ldir = if dir then [ + dir + "/include", + dir + "/lib" + ] else [ + with_config(target + "-include", idefault), + with_config(target + "-lib", ldefault) + ] end + + if idir + idircflag = "-I" + idir + $CPPFLAGS += " " + idircflag unless $CPPFLAGS.split.include?(idircflag) end - unless ldir - ldir = with_config("%s-lib"%target, ldefault) + + if ldir + $LIBPATH << ldir unless $LIBPATH.include?(ldir) end - $CPPFLAGS += idir if idir - $LIBPATH |= [ldir] if ldir + [idir, ldir] end def create_makefile(target, srcdir = File.dirname($0)) + save_libs = $libs.dup + save_libpath = $LIBPATH.dup print "creating Makefile\n" rm_f "conftest*" STDOUT.flush @@ -370,15 +383,16 @@ def create_makefile(target, srcdir = File.dirname($0)) end $DLDFLAGS = CONFIG["DLDFLAGS"] - if $configure_args['--enable-shared'] or CONFIG['LIBRUBY'] != CONFIG['LIBRUBY_A'] - $libs = CONFIG["LIBRUBYARG"] + " " + $libs - $LIBPATH |= ["$(topdir)", CONFIG["libdir"]] - end + $libs = CONFIG["LIBRUBYARG"] + " " + $libs + $configure_args['--enable-shared'] or $LIBPATH |= [$topdir] + $LIBPATH |= [CONFIG["libdir"]] defflag = '' if RUBY_PLATFORM =~ /cygwin|mingw/ - open(target + '.def', 'wb') do |f| - f.print "EXPORTS\n", "Init_", target, "\n" + if not File.exist? target + '.def' + open(target + '.def', 'wb') do |f| + f.print "EXPORTS\n", "Init_", target, "\n" + end end defflag = "--def=" + target + ".def" end @@ -427,6 +441,8 @@ LIBPATH = #{libpath} RUBY_INSTALL_NAME = #{CONFIG["RUBY_INSTALL_NAME"]} RUBY_SO_NAME = #{CONFIG["RUBY_SO_NAME"]} +arch = #{CONFIG["arch"]} +ruby_version = #{Config::CONFIG["ruby_version"]} #{ if destdir = CONFIG["prefix"].scan(drive)[0] and !destdir.empty? "\nDESTDIR = " + destdir @@ -435,11 +451,13 @@ else end } prefix = $(DESTDIR)#{CONFIG["prefix"].sub(drive, '')} -exec_prefix = $(DESTDIR)#{CONFIG["exec_prefix"].sub(drive, '')} -libdir = $(DESTDIR)#{$libdir.sub(drive, '')}#{target_prefix} -archdir = $(DESTDIR)#{$archdir.sub(drive, '')}#{target_prefix} -sitelibdir = $(DESTDIR)#{$sitelibdir.sub(drive, '')}#{target_prefix} -sitearchdir = $(DESTDIR)#{$sitearchdir.sub(drive, '')}#{target_prefix} +exec_prefix = #{CONFIG["exec_prefix"].sub(drive, '')} +libdir = #{$libdir.sub(drive, '')}#{target_prefix} +rubylibdir = #{$rubylibdir.sub(drive, '')}#{target_prefix} +archdir = #{$archdir.sub(drive, '')}#{target_prefix} +sitedir = #{$sitedir.sub(drive, '')}#{target_prefix} +sitelibdir = #{$sitelibdir.sub(drive, '')}#{target_prefix} +sitearchdir = #{$sitearchdir.sub(drive, '')}#{target_prefix} #### End of system configuration section. #### @@ -471,10 +489,10 @@ install: $(archdir)/$(DLLIB) site-install: $(sitearchdir)/$(DLLIB) $(archdir)/$(DLLIB): $(DLLIB) - @$(RUBY) -r ftools -e 'File::makedirs(*ARGV)' $(libdir) $(archdir) + @$(RUBY) -r ftools -e 'File::makedirs(*ARGV)' $(rubylibdir) $(archdir) @$(RUBY) -r ftools -e 'File::install(ARGV[0], ARGV[1], 0555, true)' $(DLLIB) $(archdir)/$(DLLIB) EOMF - install_rb(mfile, "$(libdir)") + install_rb(mfile, "$(rubylibdir)", srcdir) mfile.printf "\n" mfile.printf <<EOMF @@ -482,21 +500,42 @@ $(sitearchdir)/$(DLLIB): $(DLLIB) @$(RUBY) -r ftools -e 'File::makedirs(*ARGV)' $(libdir) $(sitearchdir) @$(RUBY) -r ftools -e 'File::install(ARGV[0], ARGV[1], 0555, true)' $(DLLIB) $(sitearchdir)/$(DLLIB) EOMF - install_rb(mfile, "$(sitelibdir)") + install_rb(mfile, "$(sitelibdir)", srcdir) mfile.printf "\n" if /mswin32/ !~ RUBY_PLATFORM mfile.print " .c.#{$OBJEXT}: $(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $< + +.cc.#{$OBJEXT}: + $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c -o $@ $< +.cpp.#{$OBJEXT}: + $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c -o $@ $< +.cxx.#{$OBJEXT}: + $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c -o $@ $< +.C.#{$OBJEXT}: + $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c -o $@ $< " elsif /nmake/i =~ $make mfile.print " {$(srcdir)}.c.#{$OBJEXT}: $(CC) $(CFLAGS) -I$(<D) $(CPPFLAGS) -c $(<:/=\\) - .c.#{$OBJEXT}: $(CC) $(CFLAGS) -I$(<D) $(CPPFLAGS) -c $(<:/=\\) + +{$(srcdir)}.cc{}.#{$OBJEXT}: + $(CXX) -I. -I$(<D) $(CXXFLAGS) $(CPPFLAGS) -c $(<:/=\\) +.cc.#{$OBJEXT}: + $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c $(<:/=\\) +{$(srcdir)}.cpp{}.#{$OBJEXT}: + $(CXX) -I. -I$(<D) $(CXXFLAGS) $(CPPFLAGS) -c $(<:/=\\) +.cpp.#{$OBJEXT}: + $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c $(<:/=\\) +{$(srcdir)}.cxx{}.#{$OBJEXT}: + $(CXX) -I. -I$(<D) $(CXXFLAGS) $(CPPFLAGS) -c $(<:/=\\) +.cxx.#{$OBJEXT}: + $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c $(<:/=\\) " else mfile.print " @@ -504,6 +543,9 @@ EOMF .c.#{$OBJEXT}: $(CC) $(CFLAGS) $(CPPFLAGS) -c $(subst /,\\\\,$<) + +.cc.#{$OBJEXT} .cpp.#{$OBJEXT} .cxx.#{$OBJEXT} .C.#{$OBJEXT}: + $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c $(subst /,\\\\,$<) " end @@ -536,6 +578,8 @@ EOMF dfile.close end mfile.close + $libs = save_libs + $LIBPATH = save_libpath end $OBJEXT = CONFIG["OBJEXT"] @@ -550,20 +594,10 @@ $LOCAL_LIBS = "" $defs = [] $make = with_config("make-prog", ENV["MAKE"] || "make") -dir = with_config("opt-dir") -if dir - idir = "-I"+dir+"/include" - ldir = dir+"/lib" -end -unless idir - dir = with_config("opt-include") - idir = "-I"+dir if dir -end -unless ldir - ldir = with_config("opt-lib") -end $CFLAGS = with_config("cflags", "") -$CPPFLAGS = [with_config("cppflags", ""), idir].compact.join(" ") +$CPPFLAGS = with_config("cppflags", "") $LDFLAGS = with_config("ldflags", "") -$LIBPATH = [ldir].compact +$LIBPATH = [] + +dir_config("opt") diff --git a/lib/monitor.rb b/lib/monitor.rb index 75d9c35821..721c51a9f5 100644 --- a/lib/monitor.rb +++ b/lib/monitor.rb @@ -1,27 +1,44 @@ =begin -monitor.rb -Author: Shugo Maeda <shugo@netlab.co.jp> -Version: 1.2.1 += monitor.rb -USAGE: +Copyright (C) 2001 Shugo Maeda <shugo@ruby-lang.org> - foo = Foo.new - foo.extend(MonitorMixin) - cond = foo.new_cond +This library is distributed under the terms of the Ruby license. +You can freely distribute/modify this library. - thread1: - foo.synchronize { - ... - cond.wait_until { foo.done? } - ... - } +== example - thread2: - foo.synchronize { - foo.do_something - cond.signal - } +This is a simple example. + + require 'monitor.rb' + + buf = [] + buf.extend(MonitorMixin) + empty_cond = buf.new_cond + + # consumer + Thread.start do + loop do + buf.synchronize do + empty_cond.wait_while { buf.empty? } + print buf.shift + end + end + end + + # producer + while line = ARGF.gets + buf.synchronize do + buf.push(line) + empty_cond.signal + end + end + +The consumer thread waits for the producer thread to push a line +to buf while buf.empty?, and the producer thread (main thread) +reads a line from ARGF and push it to buf, then call +empty_cond.signal. =end @@ -52,6 +69,15 @@ module MonitorMixin raise ThreadError, "current thread not owner" end + if timeout + ct = Thread.current + timeout_thread = Thread.start { + Thread.pass + sleep(timeout) + ct.raise(Timeout.new) + } + end + Thread.critical = true count = @monitor.mon_count @monitor.mon_count = 0 @@ -63,34 +89,28 @@ module MonitorMixin end t.wakeup if t @waiters.push(Thread.current) - - if timeout - t = Thread.current - timeout_thread = Thread.start { - sleep(timeout) - t.raise(Timeout.new) - } - end + begin Thread.stop rescue Timeout - @waiters.delete(Thread.current) ensure + Thread.critical = true if timeout && timeout_thread.alive? Thread.kill(timeout_thread) end + if @waiters.include?(Thread.current) # interrupted? + @waiters.delete(Thread.current) + end + while @monitor.mon_owner && + @monitor.mon_owner != Thread.current + @monitor.mon_waiting_queue.push(Thread.current) + Thread.stop + Thread.critical = true + end + @monitor.mon_owner = Thread.current + @monitor.mon_count = count + Thread.critical = false end - - Thread.critical = true - while @monitor.mon_owner && - @monitor.mon_owner != Thread.current - @monitor.mon_waiting_queue.push(Thread.current) - Thread.stop - Thread.critical = true - end - @monitor.mon_owner = Thread.current - @monitor.mon_count = count - Thread.critical = false end def wait_while diff --git a/lib/net/http.rb b/lib/net/http.rb index 3900ed6c68..26e5285525 100644 --- a/lib/net/http.rb +++ b/lib/net/http.rb @@ -1,8 +1,9 @@ =begin -= net/http.rb version 1.1.32 += net/http.rb version 1.1.34 + +written by Minero Aoki <aamine@dp.u-netsurf.ne.jp> -maintained by Minero Aoki <aamine@dp.u-netsurf.ne.jp> This file is derived from "http-access.rb". This program is free software. @@ -14,9 +15,34 @@ You can get it from RAA (Ruby Application Archive: http://www.ruby-lang.org/en/raa.html). -= class HTTP +== http.rb version 1.2 features + +You can use 1.2 features by calling HTTP.version_1_2. And +calling Net::HTTP.version_1_1 allows to use 1.1 features. + + # example + HTTP.start {|http1| ...(http1 has 1.1 features)... } + + HTTP.version_1_2 + HTTP.start {|http2| ...(http2 has 1.2 features)... } + + HTTP.version_1_1 + HTTP.start {|http3| ...(http3 has 1.1 features)... } + +Changes are: -== Class Methods + * HTTP#get, head, post does not raise ProtocolError + * HTTP#get, head, post returns only one object, a HTTPResponse object + * HTTPResponseReceiver is joined into HTTPResponse + * request object: HTTP::Get, Head, Post; and HTTP#request(req) + +WARNING: These features are not definite yet. +They will change without notice! + + +== class HTTP + +=== Class Methods : new( address = 'localhost', port = 80, proxy_addr = nil, proxy_port = nil ) creates a new Net::HTTP object. @@ -49,7 +75,7 @@ You can get it from RAA HTTP default port (80). -== Methods +=== Methods : start : start {|http| .... } @@ -72,7 +98,7 @@ You can get it from RAA get data from "path" on connecting host. "header" must be a Hash like { 'Accept' => '*/*', ... }. Data is written to "dest" by using "<<" method. - This method returns Net::HTTPResponse object, and "dest". + This method returns HTTPResponse object, and "dest". # example response, body = http.get( '/index.html' ) @@ -95,7 +121,7 @@ You can get it from RAA : head( path, header = nil ) gets only header from "path" on connecting host. "header" is a Hash like { 'Accept' => '*/*', ... }. - This method returns a Net::HTTPResponse object. + This method returns a HTTPResponse object. You can http header from this object like: response['content-length'] #-> '2554' @@ -109,7 +135,7 @@ You can get it from RAA If the body exists, also gets entity body. Data is written to "dest" by using "<<" method. "header" must be a Hash like { 'Accept' => '*/*', ... }. - This method returns Net::HTTPResponse object and "dest". + This method returns HTTPResponse object and "dest". If called with block, gives a part of entity body string. @@ -181,21 +207,21 @@ You can get it from RAA response.body -= class HTTPResponse +== class HTTPResponse HTTP response object. All "key" is case-insensitive. -== Methods +=== Methods : body - the entity body. ("dest" argument for HTTP#get, post, put) + the entity body (String). : self[ key ] returns header field for "key". for HTTP, value is a string like 'text/plain'(for Content-Type), - '2045'(for Content-Length), 'bytes 0-1024/10024'(for Content-Range). - Multiple header had be joined by HTTP1.1 scheme. + '2045'(for Content-Length), 'bytes 0-1023/10024'(for Content-Range). + If there's some fields which has same name, they are joined with ','. : self[ key ] = val set field value for "key". @@ -204,88 +230,92 @@ All "key" is case-insensitive. true if key exists : each {|name,value| .... } - iterates for each field name and value pair + iterates for each field name and value pair. : code - HTTP result code string. For example, '302' + HTTP result code string. For example, '302'. : message - HTTP result message. For example, 'Not Found' + HTTP result message. For example, 'Not Found'. -= class HTTPResponseReceiver +== class HTTPResponseReceiver -== Methods +=== Methods : header : response - Net::HTTPResponse object + HTTPResponse object -: body( dest = '' ) -: entity( dest = '' ) - entity body. A body is written to "dest" using "<<" method. +: read_body( dest = '' ) + reads entity body into DEST by calling "<<" method and + returns DEST. -: body {|str| ... } - gets entity body with block. - If this method is called twice, block is not executed and - returns first "dest". +: read_body {|string| ... } + reads entity body little by little and gives it to block + until entity ends. +: body +: entity + entity body. If #read_body is called already, returns its + argument DEST. Else returns entity body as String. -= http.rb version 1.2 features + Calling this method any times causes returning same + object (does not read entity again). -You can use 1.2 features by calling HTTP.version_1_2. And -calling Net::HTTP.version_1_1 allows to use 1.1 features. +=end - # example - HTTP.start {|http1| ...(http1 has 1.1 features)... } +require 'net/protocol' - HTTP.version_1_2 - HTTP.start {|http2| ...(http2 has 1.2 features)... } - HTTP.version_1_1 - HTTP.start {|http3| ...(http3 has 1.1 features)... } +module Net -== Method (only diff to 1.1) + class HTTPBadResponse < StandardError; end + class HTTPHeaderSyntaxError < StandardError; end -: get( path, u_header = nil ) -: get( path, u_header = nil ) {|str| .... } - gets document from "path". - returns HTTPResponse object. -: head( path, u_header = nil ) - gets only document header from "path". - returns HTTPResponse object. + class HTTP < Protocol -: post( path, data, u_header = nil ) -: post( path, data, u_header = nil ) {|str| .... } - posts "data" to "path" entity and gets document. - returns HTTPResponse object. + HTTPVersion = '1.1' -=end + # + # connection + # -require 'net/protocol' + protocol_param :port, '80' -module Net + def initialize( addr = nil, port = nil ) + super - class HTTPBadResponse < StandardError; end + @proxy_address = nil + @proxy_port = nil + @curr_http_version = HTTPVersion + @seems_1_0_server = false + end - class HTTP < Protocol - protocol_param :port, '80' - protocol_param :command_type, '::Net::NetPrivate::HTTPCommand' + private + def conn_command( sock ) + end + + def do_finish + end + + + # + # proxy + # + + public - ### - ### proxy - ### class << self def Proxy( p_addr, p_port = nil ) - ::Net::NetPrivate::HTTPProxy.create_proxy_class( - p_addr, p_port || self.port ) + ProxyMod.create_proxy_class( p_addr, p_port || self.port ) end alias orig_new new @@ -293,7 +323,7 @@ module Net def new( address = nil, port = nil, p_addr = nil, p_port = nil ) c = p_addr ? self::Proxy(p_addr, p_port) : self i = c.orig_new( address, port ) - setimplv i + setvar i i end @@ -332,385 +362,230 @@ module Net end - ### - ### 1.2 implementation - ### + module ProxyMod - @@newimpl = false + class << self - #class << self + def create_proxy_class( p_addr, p_port ) + mod = self + klass = Class.new( HTTP ) + klass.module_eval { + include mod + @proxy_address = p_addr + @proxy_port = p_port + } + def klass.proxy_class? + true + end - def self.version_1_2 - @@newimpl = true - end + def klass.proxy_address + @proxy_address + end - def self.version_1_1 - @@newimpl = false - end + def klass.proxy_port + @proxy_port + end - #private + klass + end - def self.setimplv( obj ) - f = @@newimpl - obj.instance_eval { @newimpl = f } end - #end - - - ### - ### http operations - ### - - def get( path, u_header = nil, dest = nil, &block ) - resp = get2( path, u_header ) {|f| f.body( dest, &block ) } - if @newimpl then - resp - else - resp.value - return resp, resp.body + def initialize( addr, port ) + super + @proxy_address = type.proxy_address + @proxy_port = type.proxy_port end - end - - def get2( path, u_header = nil, &block ) - common_oper( u_header, true, block ) {|uh| - @command.get edit_path(path), uh - } - end + + attr_reader :proxy_address, :proxy_port + alias proxyaddr proxy_address + alias proxyport proxy_port - def head( path, u_header = nil ) - resp = head2( path, u_header ) - unless @newimpl then - resp.value + def proxy? + true end - resp - end - - def head2( path, u_header = nil, &block ) - common_oper( u_header, false, block ) {|uh| - @command.head edit_path(path), uh - } - end - - - def post( path, data, u_header = nil, dest = nil, &block ) - resp = post2( path, data, u_header ) {|f| f.body( dest, &block ) } - if @newimpl then - resp - else - resp.value - return resp, resp.body + + private + + def conn_socket( addr, port ) + super @proxy_address, @proxy_port end - end - - def post2( path, data, u_header = nil, &block ) - common_oper( u_header, true, block ) {|uh| - @command.post edit_path(path), uh, data - } - end - - # not tested because I could not setup apache (__;;; - def put( path, src, u_header = nil ) - resp = put2( path, src, u_header ) {|f| f.body } - if @newimpl then - resp - else - resp.value - return resp, resp.body + def edit_path( path ) + 'http://' + addr_port + path end - end + + end # module ProxyMod - def put2( path, src, u_header = nil, &block ) - common_oper( u_header, true, block ) {|uh| - @command.put path, uh, src - } - end - - - private - - - def common_oper( u_header, body_exist, block ) - header = procheader( u_header ) - recv = err = nil - - connecting( header ) { - recv = HTTPResponseReceiver.new( @command, body_exist ) - yield header - begin - block.call recv if block - rescue Exception => err - ; - end - recv.terminate - - recv.response - } - raise err if err - recv.response - end + # + # for backward compatibility + # - def connecting( header ) - if not @socket then - header['Connection'] = 'close' - start - elsif @socket.closed? then - @socket.reopen - end + @@newimpl = false - resp = yield + class << self - unless keep_alive? header, resp then - @socket.close + def version_1_2 + @@newimpl = true end - end - def keep_alive?( header, resp ) - if resp.key? 'connection' then - if /keep-alive/i === resp['connection'] then - return true - end - elsif resp.key? 'proxy-connection' then - if /keep-alive/i === resp['proxy-connection'] then - return true - end - elsif header.key? 'Connection' then - if /keep-alive/i === header['Connection'] then - return true - end - else - if @command.http_version == '1.1' then - return true - end + def version_1_1 + @@newimpl = false end - false - end - - def procheader( h ) - ret = {} - ret[ 'Host' ] = address + - ((port == HTTP.port) ? '' : ":#{port}") - ret[ 'Connection' ] = 'Keep-Alive' - ret[ 'Accept' ] = '*/*' + private - return ret unless h - tmp = {} - h.each do |k,v| - key = k.split('-').collect {|i| i.capitalize }.join('-') - if tmp[key] then - $stderr.puts "'#{key}' http header appered twice" if $VERBOSE - end - tmp[key] = v + def setvar( obj ) + f = @@newimpl + obj.instance_eval { @newimpl = f } end - ret.update tmp - ret end - def do_finish - end + # + # http operations + # - end - - HTTPSession = HTTP - - - module NetPrivate - - module HTTPProxy - - class << self - - def create_proxy_class( p_addr, p_port ) - klass = Class.new( HTTP ) - klass.module_eval { - include HTTPProxy - @proxy_address = p_addr - @proxy_port = p_port - } - def klass.proxy_class? - true - end + public - def klass.proxy_address - @proxy_address + def self.def_http_method( nm, hasdest, hasdata ) + name = nm.id2name.downcase + cname = nm.id2name + lineno = __LINE__ + 2 + src = <<" ----" + + def #{name}( path, #{hasdata ? 'data,' : ''} + u_header = nil #{hasdest ? ',dest = nil, &block' : ''} ) + resp = nil + request( + #{cname}.new( path, u_header ) #{hasdata ? ',data' : ''} + ) do |resp| + resp.read_body( #{hasdest ? 'dest, &block' : ''} ) + end + if @newimpl then + resp + else + resp.value + #{hasdest ? 'return resp, resp.body' : 'resp'} + end end - def klass.proxy_port - @proxy_port + def #{name}2( path, #{hasdata ? 'data,' : ''} + u_header = nil, &block ) + request( #{cname}.new(path, u_header), + #{hasdata ? 'data,' : ''} &block ) end - - klass - end - - end - - - def initialize( addr, port ) - super - @proxy_address = type.proxy_address - @proxy_port = type.proxy_port + ---- + module_eval src, __FILE__, lineno end - attr_reader :proxy_address, :proxy_port + def_http_method :Get, true, false + def_http_method :Head, false, false + def_http_method :Post, true, true + def_http_method :Put, false, true - alias proxyaddr proxy_address - alias proxyport proxy_port - - def proxy? - true - end - - def connect( addr = nil, port = nil ) - super @proxy_address, @proxy_port - end - - def edit_path( path ) - 'http://' + address + (port == type.port ? '' : ":#{port}") + path + def request( req, *args ) + common_oper( req ) { + req.__send__( :exec, + @socket, @curr_http_version, edit_path(req.path), *args ) + yield req.response if block_given? + } + req.response end - - end - - end # net private + private - class HTTPResponseReceiver - - def initialize( command, body_exist ) - @command = command - @body_exist = body_exist - @header = @body = nil - end - - def inspect - "#<#{type}>" - end - def read_header - unless @header then - stream_check - @header = @command.get_response + def common_oper( req ) + req['connection'] ||= 'keep-alive' + if not @socket then + start + req['connection'] = 'close' + elsif @socket.closed? then + re_connect end - @header - end - - alias header read_header - alias response read_header - - def read_body( dest = nil, &block ) - unless @body then - read_header - - to = procdest( dest, block ) - stream_check - - if @body_exist and @header.code_type.body_exist? then - @command.get_body @header, to - @header.body = @body = to - else - @command.no_body - @header.body = nil - @body = 1 - end + if not req.body_exist? or @seems_1_0_server then + req['connection'] = 'close' end - @body == 1 ? nil : @body - end - - alias body read_body - alias entity read_body + req['host'] = addr_port - def terminate - read_header - read_body - @command = nil - end + yield req + req.response.__send__ :terminate + @curr_http_version = req.response.http_version - - private - - def stream_check - unless @command then - raise IOError, 'receiver was used out of block' - end - end - - def procdest( dest, block ) - if dest and block then - raise ArgumentError, - 'both of arg and block are given for HTTP method' - end - if block then - NetPrivate::ReadAdapter.new block + if not req.response.body then + @socket.close + elsif keep_alive? req, req.response then + D 'Conn keep-alive' + if @socket.closed? then # (only) read stream had been closed + D 'Conn (but seems 1.0 server)' + @seems_1_0_server = true + @socket.close + end else - dest or '' + D 'Conn close' + @socket.close end - end - - end - - HTTPReadAdapter = HTTPResponseReceiver - - class HTTPResponse < Response - - def initialize( code_type, code, msg ) - super - @data = {} - @body = nil + req.response end - attr_accessor :body + def keep_alive?( req, res ) + /close/i === req['connection'].to_s and return false + @seems_1_0_server and return false - def inspect - "#<#{type.name} #{code}>" - end + /keep-alive/i === res['connection'].to_s and return true + /close/i === res['connection'].to_s and return false + /keep-alive/i === res['proxy-connection'].to_s and return true + /close/i === res['proxy-connection'].to_s and return false - def []( key ) - @data[ key.downcase ] + @curr_http_version == '1.1' and return true + false end - def []=( key, val ) - @data[ key.downcase ] = val - end - def each( &block ) - @data.each( &block ) - end + # + # utils + # - def each_key( &block ) - @data.each_key( &block ) - end + public - def each_value( &block ) - @data.each_value( &block ) + def self.get( addr, path, port = nil ) + req = Get.new( path ) + resp = nil + new( addr, port || HTTP.port ).start {|http| + resp = http.request( req ) + } + resp.body end - def delete( key ) - @data.delete key.downcase + def self.get_print( addr, path, port = nil ) + print get( addr, path, port ) end - def key?( key ) - @data.key? key.downcase - end - def to_hash - @data.dup + private + + def addr_port + address + (port == HTTP.port ? '' : ":#{port}") end - def value - unless SuccessCode === self then - error! self + def D( msg ) + if @dout then + @dout << msg + @dout << "\n" end end end + HTTPSession = HTTP + + class Code @@ -776,101 +651,246 @@ module Net HTTPVersionNotSupported = HTTPServerErrorCode.http_mkchild - module NetPrivate + ### + ### header + ### - class HTTPCommand < Command + net_private { - HTTPVersion = '1.1' + module HTTPHeader - def initialize( sock ) - @http_version = HTTPVersion - super sock + def size + @header.size end - attr_reader :http_version + alias length size - def inspect - "#<Net::HTTPCommand>" + def []( key ) + @header[ key.downcase ] end + def []=( key, val ) + @header[ key.downcase ] = val + end - ### - ### request - ### + def each( &block ) + @header.each( &block ) + end - public + def each_key( &block ) + @header.each_key( &block ) + end - def get( path, u_header ) - return unless begin_critical - request sprintf('GET %s HTTP/%s', path, HTTPVersion), u_header + def each_value( &block ) + @header.each_value( &block ) end - - def head( path, u_header ) - return unless begin_critical - request sprintf('HEAD %s HTTP/%s', path, HTTPVersion), u_header + + def delete( key ) + @header.delete key.downcase + end + + def key?( key ) + @header.key? key.downcase + end + + def to_hash + @header.dup end - def post( path, u_header, data ) - return unless begin_critical - u_header[ 'Content-Length' ] = data.size.to_s - request sprintf('POST %s HTTP/%s', path, HTTPVersion), u_header - @socket.write data + def canonical_each + @header.each do |k,v| + yield canonical(k), v + end end - def put( path, u_header, src ) - return unless begin_critical - request sprintf('PUT %s HTTP/%s', path, HTTPVersion), u_header - @socket.write_bin src + def canonical( k ) + k.split('-').collect {|i| i.capitalize }.join('-') end - # def delete + def range + s = @header['range'] + s or return nil - # def trace + arr = [] + s.split(',').each do |spec| + m = /bytes\s*=\s*(\d+)?\s*-\s*(\d+)?/i.match( spec ) + m or raise HTTPHeaderSyntaxError, "wrong Range: #{spec}" - # def options + d1 = m[1].to_i + d2 = m[2].to_i + if m[1] and m[2] then arr.push (d1 .. d2) + elsif m[1] then arr.push (d1 .. -1) + elsif m[2] then arr.push (-d2 .. -1) + else + raise HTTPHeaderSyntaxError, 'range is not specified' + end + end - def quit + return *arr end + def range=( r, fin = nil ) + if fin then + r = r ... r+fin + end - private + case r + when Numeric + s = r > 0 ? "0-#{r - 1}" : "-#{-r}" + when Range + first = r.first + last = r.last + if r.exclude_end? then + last -= 1 + end - def request( req, u_header ) - @socket.writeline req - u_header.each do |n,v| - @socket.writeline n + ': ' + v + if last == -1 then + s = first > 0 ? "#{first}-" : "-#{-first}" + else + first >= 0 or raise HTTPHeaderSyntaxError, 'range.first is negative' + last > 0 or raise HTTPHeaderSyntaxError, 'range.last is negative' + first < last or raise HTTPHeaderSyntaxError, 'must be .first < .last' + s = "#{first}-#{last}" + end + else + raise TypeError, 'Range/Integer is required' end - @socket.writeline '' + + @header['range'] = "bytes=#{s}" + r end + alias set_range range= - ### - ### response line & header - ### + def content_length + s = @header['content-length'] + s or return nil - public + m = /\d+/.match(s) + m or raise HTTPHeaderSyntaxError, 'wrong Content-Length format' + m[0].to_i + end - def get_response - resp = get_resp0 - resp = get_resp0 while ContinueCode === resp - resp + def chunked? + s = @header['transfer-encoding'] + (s and /(?:\A|[^\-\w])chunked(?:[^\-\w]|\z)/i === s) ? true : false + end + + def content_range + s = @header['content-range'] + s or return nil + + m = %r<bytes\s+(\d+)-(\d+)/(?:\d+|\*)>i.match( s ) + m or raise HTTPHeaderSyntaxError, 'wrong Content-Range format' + + m[1].to_i .. m[2].to_i + 1 + end + + def range_length + r = content_range + r and r.length + end + + def basic_auth( acc, pass ) + @header['authorization'] = + 'Basic ' + ["#{acc}:#{pass}"].pack('m').gsub(/\s+/, '') + end + + end + + } + + + ### + ### request + ### + + net_private { + + class HTTPRequest + + include ::Net::NetPrivate::HTTPHeader + + def initialize( path, uhead = nil ) + @path = path + @header = tmp = {} + return unless uhead + uhead.each do |k,v| + key = k.downcase + if tmp.key? key then + $stderr.puts "WARNING: duplicated HTTP header: #{k}" if $VERBOSE + end + tmp[ key ] = v.strip + end + tmp['accept'] ||= '*/*' + + @socket = nil + @response = nil + end + + attr_reader :path + attr_reader :response + + def inspect + "\#<#{type}>" + end + + def body_exist? + type::HAS_BODY end private - def get_resp0 - resp = get_reply + # + # write + # + + def exec( sock, ver, path ) + ready( sock ) { + request ver, path + } + @response + end + + def ready( sock ) + @response = nil + @socket = sock + yield + @response = get_response + @socket = nil + end + + def request( ver, path ) + @socket.writeline sprintf('%s %s HTTP/%s', type::METHOD, path, ver) + canonical_each do |k,v| + @socket.writeline k + ': ' + v + end + @socket.writeline '' + end + + # + # read + # + + def get_response + begin + resp = read_response + end while ContinueCode === resp + resp + end + + def read_response + resp = get_resline while true do - line = @socket.readline + line = @socket.readuntil( "\n", true ) # ignore EOF + line.sub!( /\s+\z/, '' ) # don't use chop! break if line.empty? m = /\A([^:]+):\s*/.match( line ) - unless m then - raise HTTPBadResponse, 'wrong header line format' - end + m or raise HTTPBadResponse, 'wrong header line format' nm = m[1] line = m.post_match if resp.key? nm then @@ -883,23 +903,117 @@ module Net resp end - def get_reply + def get_resline str = @socket.readline - m = /\AHTTP\/(\d+\.\d+)?\s+(\d\d\d)\s*(.*)\z/i.match( str ) - unless m then - raise HTTPBadResponse, "wrong status line: #{str}" - end - @http_version = m[1] + m = /\AHTTP(?:\/(\d+\.\d+))?\s+(\d\d\d)\s*(.*)\z/i.match( str ) + m or raise HTTPBadResponse, "wrong status line: #{str}" + httpver = m[1] status = m[2] discrip = m[3] - code = HTTPCODE_TO_OBJ[status] || - HTTPCODE_CLASS_TO_OBJ[status[0,1]] || - UnknownCode - HTTPResponse.new( code, status, discrip ) + ::Net::NetPrivate::HTTPResponse.new( + status, discrip, @socket, type::HAS_BODY, httpver ) + end + + end + + + class HTTPRequestWithBody < HTTPRequest + + private + + def exec( sock, ver, path, str = nil ) + check_arg str, block_given? + + if block_given? then + ac = Accumulator.new + yield ac # must be yield, DO NOT USE block.call + data = ac.terminate + else + data = str + end + @header['content-length'] = data.size.to_s + @header.delete 'transfer-encoding' + + ready( sock ) { + request ver, path + @socket.write data + } + @response + end + + def check_arg( data, blkp ) + if data and blkp then + raise ArgumentError, 'both of data and block given' + end + unless data or blkp then + raise ArgumentError, 'str or block required' + end + end + + end + + + class Accumulator + + def initialize + @buf = '' + end + + def write( s ) + @buf.concat s + end + + alias << write + + def terminate + ret = @buf + @buf = nil + ret + end + + end + + } + + + class HTTP + + class Get < ::Net::NetPrivate::HTTPRequest + HAS_BODY = true + METHOD = 'GET' + end + + class Head < ::Net::NetPrivate::HTTPRequest + HAS_BODY = false + METHOD = 'HEAD' + end + + class Post < ::Net::NetPrivate::HTTPRequestWithBody + HAS_BODY = true + METHOD = 'POST' + end + + class Put < ::Net::NetPrivate::HTTPRequestWithBody + HAS_BODY = true + METHOD = 'PUT' end - HTTPCODE_CLASS_TO_OBJ = { + end + + + + ### + ### response + ### + + net_private { + + class HTTPResponse < Response + + include ::Net::NetPrivate::HTTPHeader + + CODE_CLASS_TO_OBJ = { '1' => HTTPInformationCode, '2' => HTTPSuccessCode, '3' => HTTPRedirectionCode, @@ -907,7 +1021,7 @@ module Net '5' => HTTPServerErrorCode } - HTTPCODE_TO_OBJ = { + CODE_TO_OBJ = { '100' => ContinueCode, '101' => HTTPSwitchProtocol, @@ -951,22 +1065,87 @@ module Net '505' => HTTPVersionNotSupported } + def initialize( stat, msg, sock, be, hv ) + code = CODE_TO_OBJ[stat] || + CODE_CLASS_TO_OBJ[stat[0,1]] || + UnknownCode + super code, stat, msg + @socket = sock + @body_exist = be + @http_version = hv - ### - ### body - ### + @header = {} + @body = nil + @read = false + end - public + attr_reader :http_version + + def inspect + "#<#{type} #{code}>" + end + + def value + SuccessCode === self or error! self + end - def get_body( resp, dest ) - if chunked? resp then + + # + # header (for backward compatibility) + # + + def read_header + self + end + + alias header read_header + alias response read_header + + # + # body + # + + def read_body( dest = nil, &block ) + if @read and (dest or block) then + raise IOError, "#{type}\#read_body called twice with argument" + end + + unless @read then + to = procdest( dest, block ) + stream_check + + if @body_exist and code_type.body_exist? then + read_body_0 to + @body = to + else + @body = nil + end + @read = true + end + + @body + end + + alias body read_body + alias entity read_body + + + private + + + def terminate + read_body + end + + def read_body_0( dest ) + if chunked? then read_chunked dest else - clen = content_length( resp ) + clen = content_length if clen then - @socket.read clen, dest + @socket.read clen, dest, true # ignore EOF else - clen = range_length( resp ) + clen = range_length if clen then @socket.read clen, dest else @@ -974,16 +1153,8 @@ module Net end end end - end_critical - end - - def no_body - end_critical end - - private - def read_chunked( dest ) len = nil total = 0 @@ -991,9 +1162,7 @@ module Net while true do line = @socket.readline m = /[0-9a-fA-F]+/.match( line ) - unless m then - raise HTTPBadResponse, "wrong chunk size line: #{line}" - end + m or raise HTTPBadResponse, "wrong chunk size line: #{line}" len = m[0].hex break if len == 0 @socket.read( len, dest ); total += len @@ -1004,44 +1173,27 @@ module Net end end - def content_length( resp ) - if resp.key? 'content-length' then - m = /\d+/.match( resp['content-length'] ) - unless m then - raise HTTPBadResponse, 'wrong Content-Length format' - end - m[0].to_i - else - nil - end + def stream_check + @socket.closed? and raise IOError, 'try to read body out of block' end - def chunked?( resp ) - tmp = resp['transfer-encoding'] - tmp and /(?:\A|\s+)chunked(?:\s+|\z)/i === tmp - end - - def range_length( resp ) - if resp.key? 'content-range' then - m = %r<bytes\s+(\d+)-(\d+)/\d+>.match( resp['content-range'] ) - unless m then - raise HTTPBadResponse, 'wrong Content-Range format' - end - l = m[2].to_i - u = m[1].to_i - if l > u then - nil - else - u - l - end + def procdest( dest, block ) + if dest and block then + raise ArgumentError, 'both of arg and block are given for HTTP method' + end + if block then + ::Net::NetPrivate::ReadAdapter.new block else - nil + dest || '' end end end + } + - end # module Net::NetPrivate + HTTPResponse = NetPrivate::HTTPResponse + HTTPResponseReceiver = NetPrivate::HTTPResponse end # module Net diff --git a/lib/net/imap.rb b/lib/net/imap.rb index 34d324ed12..ea064d2552 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -7,9 +7,10 @@ Copyright (C) 2000 Shugo Maeda <shugo@ruby-lang.org> This library is distributed under the terms of the Ruby license. You can freely distribute/modify this library. -== class Net::IMAP +== Net::IMAP Net::IMAP implements Internet Message Access Protocol (IMAP) clients. +(The protocol is described in ((<[IMAP]>)).) === Super Class @@ -102,21 +103,24 @@ Object : list(refname, mailbox) Sends a LIST command, and returns a subset of names from the complete set of all names available to the client. + The return value is an array of ((<Net::IMAP::MailboxList>)). ex). imap.create("foo/bar") imap.create("foo/baz") p imap.list("", "foo/%") - #=> [#<Net::IMAP::MailboxList attr=[:NoSelect], delim="/", name="foo/">, #<Net::IMAP::MailboxList attr=[:NoInferiors, :Marked], delim="/", name="foo/bar">, #<Net::IMAP::MailboxList attr=[:NoInferiors], delim="/", name="foo/baz">] + #=> [#<Net::IMAP::MailboxList attr=[:Noselect], delim="/", name="foo/">, #<Net::IMAP::MailboxList attr=[:Noinferiors, :Marked], delim="/", name="foo/bar">, #<Net::IMAP::MailboxList attr=[:Noinferiors], delim="/", name="foo/baz">] : lsub(refname, mailbox) Sends a LSUB command, and returns a subset of names from the set of names that the user has declared as being "active" or "subscribed". + The return value is an array of ((<Net::IMAP::MailboxList>)). : status(mailbox, attr) Sends a STATUS command, and returns the status of the indicated mailbox. + The return value is a hash of attributes. ex). p imap.status("inbox", ["MESSAGES", "RECENT"]) @@ -166,6 +170,7 @@ Object in the mailbox. the set parameter is a number or an array of numbers or a Range object. the number is a message sequence number (fetch) or a unique identifier (uid_fetch). + The return value is an array of ((<Net::IMAP::FetchData>)). ex). p imap.fetch(6..8, "UID") @@ -188,6 +193,7 @@ Object in the mailbox. the set parameter is a number or an array of numbers or a Range object. the number is a message sequence number (store) or a unique identifier (uid_store). + The return value is an array of ((<Net::IMAP::FetchData>)). ex). p imap.store(6..8, "+FLAGS", [:Deleted]) @@ -210,6 +216,443 @@ Object p imap.sort(["DATE"], ["SUBJECT", "hello"], "US-ASCII") #=> [6, 7, 8, 1] +== Net::IMAP::ContinuationRequest + +Net::IMAP::ContinuationRequest represents command continuation requests. + +The command continuation request response is indicated by a "+" token +instead of a tag. This form of response indicates that the server is +ready to accept the continuation of a command from the client. The +remainder of this response is a line of text. + + continue_req ::= "+" SPACE (resp_text / base64) + +=== Super Class + +Struct + +=== Methods + +: data + Returns the data (Net::IMAP::ResponseText). + +: raw_data + Returns the raw data string. + +== Net::IMAP::UntaggedResponse + +Net::IMAP::UntaggedResponse represents untagged responses. + +Data transmitted by the server to the client and status responses +that do not indicate command completion are prefixed with the token +"*", and are called untagged responses. + + response_data ::= "*" SPACE (resp_cond_state / resp_cond_bye / + mailbox_data / message_data / capability_data) + +=== Super Class + +Struct + +=== Methods + +: name + Returns the name such as "FLAGS", "LIST", "FETCH".... + +: data + Returns the data such as an array of flag symbols, + a ((<Net::IMAP::MailboxList>)) object.... + +: raw_data + Returns the raw data string. + +== Net::IMAP::TaggedResponse + +Net::IMAP::TaggedResponse represents tagged responses. + +The server completion result response indicates the success or +failure of the operation. It is tagged with the same tag as the +client command which began the operation. + + response_tagged ::= tag SPACE resp_cond_state CRLF + + tag ::= 1*<any ATOM_CHAR except "+"> + + resp_cond_state ::= ("OK" / "NO" / "BAD") SPACE resp_text + +=== Super Class + +Struct + +=== Methods + +: tag + Returns the tag. + +: name + Returns the name. the name is one of "OK", "NO", "BAD". + +: data + Returns the data. See ((<Net::IMAP::ResponseText>)). + +: raw_data + Returns the raw data string. + +== Net::IMAP::ResponseText + +Net::IMAP::ResponseText represents texts of responses. +The text may be prefixed by the response code. + + resp_text ::= ["[" resp_text_code "]" SPACE] (text_mime2 / text) + ;; text SHOULD NOT begin with "[" or "=" + +=== Super Class + +Struct + +=== Methods + +: code + Returns the response code. See ((<Net::IMAP::ResponseCode>)). + +: text + Returns the text. + +== Net::IMAP::ResponseCode + +Net::IMAP::ResponseCode represents response codes. + + resp_text_code ::= "ALERT" / "PARSE" / + "PERMANENTFLAGS" SPACE "(" #(flag / "\*") ")" / + "READ-ONLY" / "READ-WRITE" / "TRYCREATE" / + "UIDVALIDITY" SPACE nz_number / + "UNSEEN" SPACE nz_number / + atom [SPACE 1*<any TEXT_CHAR except "]">] + +=== SuperClass + +Struct + +=== Methods + +: name + Returns the name such as "ALERT", "PERMANENTFLAGS", "UIDVALIDITY".... + +: data + Returns the data if it exists. + +== Net::IMAP::MailboxList + +Net::IMAP::MailboxList represents contents of the LIST response. + + mailbox_list ::= "(" #("\Marked" / "\Noinferiors" / + "\Noselect" / "\Unmarked" / flag_extension) ")" + SPACE (<"> QUOTED_CHAR <"> / nil) SPACE mailbox + +=== Super Class + +Struct + +=== Methods + +: attr + Returns the name attributes. Each name attribute is a symbol + capitalized by String#capitalize, such as :Noselect (not :NoSelect). + +: delim + Returns the hierarchy delimiter + +: name + Returns the mailbox name. + +== Net::IMAP::StatusData + +Net::IMAP::StatusData represents contents of the STATUS response. + +=== Super Class + +Object + +=== Methods + +: mailbox + Returns the mailbox name. + +: attr + Returns a hash. Each key is one of "MESSAGES", "RECENT", "UIDNEXT", + "UIDVALIDITY", "UNSEEN". Each value is a number. + +== Net::IMAP::FetchData + +Net::IMAP::FetchData represents contents of the FETCH response. + +=== Super Class + +Object + +=== Methods + +: seqno + Returns the message sequence number. + (Note: not the unique identifier, even for the UID command response.) + +: attr + Returns a hash. Each key is a data item name, and each value is + its value. + + The current data items are: + + : BODY + A form of BODYSTRUCTURE without extension data. + : BODY[<section>]<<origin_octet>> + A string expressing the body contents of the specified section. + : BODYSTRUCTURE + An object that describes the ((<[MIME-IMB]>)) body structure of a message. + See ((<Net::IMAP::BodyTypeBasic>)), ((<Net::IMAP::BodyTypeText>)), + ((<Net::IMAP::BodyTypeMessage>)), ((<Net::IMAP::BodyTypeMultipart>)). + : ENVELOPE + A ((<Net::IMAP::Envelope>)) object that describes the envelope + structure of a message. + : FLAGS + A array of flag symbols that are set for this message. flag symbols + are capitalized by String#capitalize. + : INTERNALDATE + A string representing the internal date of the message. + : RFC822 + Equivalent to BODY[]. + : RFC822.HEADER + Equivalent to BODY.PEEK[HEADER]. + : RFC822.SIZE + A number expressing the ((<[RFC-822]>)) size of the message. + : RFC822.TEXT + Equivalent to BODY[TEXT]. + : UID + A number expressing the unique identifier of the message. + +== Net::IMAP::Envelope + +Net::IMAP::Envelope represents envelope structures of messages. + +=== Super Class + +Struct + +=== Methods + +: date + Retunns a string that represents the date. + +: subject + Retunns a string that represents the subject. + +: from + Retunns an array of ((<Net::IMAP::Address>)) that represents the from. + +: sender + Retunns an array of ((<Net::IMAP::Address>)) that represents the sender. + +: reply_to + Retunns an array of ((<Net::IMAP::Address>)) that represents the reply-to. + +: to + Retunns an array of ((<Net::IMAP::Address>)) that represents the to. + +: cc + Retunns an array of ((<Net::IMAP::Address>)) that represents the cc. + +: bcc + Retunns an array of ((<Net::IMAP::Address>)) that represents the bcc. + +: in_reply_to + Retunns a string that represents the in-reply-to. + +: message_id + Retunns a string that represents the message-id. + +== Net::IMAP::Address + +((<Net::IMAP::Address>)) represents electronic mail addresses. + +=== Super Class + +Struct + +=== Methods + +: name + Returns the phrase from ((<[RFC-822]>)) mailbox. + +: route + Returns the route from ((<[RFC-822]>)) route-addr. + +: mailbox + nil indicates end of ((<[RFC-822]>)) group. + If non-nil and host is nil, returns ((<[RFC-822]>)) group name. + Otherwise, returns ((<[RFC-822]>)) local-part + +: host + nil indicates ((<[RFC-822]>)) group syntax. + Otherwise, returns ((<[RFC-822]>)) domain name. + +== Net::IMAP::ContentDisposition + +Net::IMAP::ContentDisposition represents Content-Disposition fields. + +=== Super Class + +Struct + +=== Methods + +: dsp_type + Returns the disposition type. + +: param + Returns a hash that represents parameters of the Content-Disposition + field. + +== Net::IMAP::BodyTypeBasic + +Net::IMAP::BodyTypeBasic represents basic body structures of messages. + +=== Super Class + +Struct + +=== Methods + +: media_type + Returns the content media type name as defined in ((<[MIME-IMB]>)). + +: subtype + Returns the content subtype name as defined in ((<[MIME-IMB]>)). + +: param + Returns a hash that represents parameters as defined in + ((<[MIME-IMB]>)). + +: content_id + Returns a string giving the content id as defined in ((<[MIME-IMB]>)). + +: description + Returns a string giving the content description as defined in + ((<[MIME-IMB]>)). + +: encoding + Returns a string giving the content transfer encoding as defined in + ((<[MIME-IMB]>)). + +: size + Returns a number giving the size of the body in octets. + +: md5 + Returns a string giving the body MD5 value as defined in ((<[MD5]>)). + +: disposition + Returns a ((<Net::IMAP::ContentDisposition>)) object giving + the content disposition. + +: language + Returns a string or an array of strings giving the body + language value as defined in [LANGUAGE-TAGS]. + +: extension + Returns extension data. + +: multipart? + Returns false. + +== Net::IMAP::BodyTypeText + +Net::IMAP::BodyTypeText represents TEXT body structures of messages. + +=== Super Class + +Struct + +=== Methods + +: lines + Returns the size of the body in text lines. + +And Net::IMAP::BodyTypeText has all methods of ((<Net::IMAP::BodyTypeBasic>)). + +== Net::IMAP::BodyTypeMessage + +Net::IMAP::BodyTypeMessage represents MESSAGE/RFC822 body structures of messages. + +=== Super Class + +Struct + +=== Methods + +: envelope + Returns a ((<Net::IMAP::Envelope>)) giving the envelope structure. + +: body + Returns an object giving the body structure. + +And Net::IMAP::BodyTypeMessage has all methods of ((<Net::IMAP::BodyTypeText>)). + +== Net::IMAP::BodyTypeText + +=== Super Class + +Struct + +=== Methods + +: media_type + Returns the content media type name as defined in ((<[MIME-IMB]>)). + +: subtype + Returns the content subtype name as defined in ((<[MIME-IMB]>)). + +: parts + Returns multiple parts. + +: param + Returns a hash that represents parameters as defined in + ((<[MIME-IMB]>)). + +: disposition + Returns a ((<Net::IMAP::ContentDisposition>)) object giving + the content disposition. + +: language + Returns a string or an array of strings giving the body + language value as defined in [LANGUAGE-TAGS]. + +: extension + Returns extension data. + +: multipart? + Returns true. + +== References + +: [IMAP] + M. Crispin, "INTERNET MESSAGE ACCESS PROTOCOL - VERSION 4rev1", + RFC 2060, December 1996. + +: [LANGUAGE-TAGS] + Alvestrand, H., "Tags for the Identification of + Languages", RFC 1766, March 1995. + +: [MD5] + Myers, J., and M. Rose, "The Content-MD5 Header Field", RFC + 1864, October 1995. + +: [MIME-IMB] + Freed, N., and N. Borenstein, "MIME (Multipurpose Internet + Mail Extensions) Part One: Format of Internet Message Bodies", RFC + 2045, November 1996. + +: [RFC-822] + Crocker, D., "Standard for the Format of ARPA Internet Text + Messages", STD 11, RFC 822, University of Delaware, August 1982. + =end require "socket" @@ -702,7 +1145,7 @@ module Net Address = Struct.new(:name, :route, :mailbox, :host) ContentDisposition = Struct.new(:dsp_type, :param) - class BodyTypeBasic < Struct.new(:media_type, :media_subtype, + class BodyTypeBasic < Struct.new(:media_type, :subtype, :param, :content_id, :description, :encoding, :size, :md5, :disposition, :language, @@ -710,9 +1153,15 @@ module Net def multipart? return false end + + def media_subtype + $stderr.printf("warning: media_subtype is obsolete.\n") + $stderr.printf(" use subtype instead.\n") + return subtype + end end - class BodyTypeText < Struct.new(:media_type, :media_subtype, + class BodyTypeText < Struct.new(:media_type, :subtype, :param, :content_id, :description, :encoding, :size, :lines, @@ -721,9 +1170,15 @@ module Net def multipart? return false end + + def media_subtype + $stderr.printf("warning: media_subtype is obsolete.\n") + $stderr.printf(" use subtype instead.\n") + return subtype + end end - class BodyTypeMessage < Struct.new(:media_type, :media_subtype, + class BodyTypeMessage < Struct.new(:media_type, :subtype, :param, :content_id, :description, :encoding, :size, :envelope, :body, :lines, @@ -732,15 +1187,27 @@ module Net def multipart? return false end + + def media_subtype + $stderr.printf("warning: media_subtype is obsolete.\n") + $stderr.printf(" use subtype instead.\n") + return subtype + end end - class BodyTypeMultipart < Struct.new(:media_type, :media_subtype, + class BodyTypeMultipart < Struct.new(:media_type, :subtype, :parts, :param, :disposition, :language, :extension) def multipart? return true end + + def media_subtype + $stderr.printf("warning: media_subtype is obsolete.\n") + $stderr.printf(" use subtype instead.\n") + return subtype + end end class ResponseParser @@ -1475,6 +1942,12 @@ module Net when /\A(?:UIDVALIDITY|UIDNEXT|UNSEEN)\z/n match(T_SPACE) result = ResponseCode.new(name, number) + else + match(T_SPACE) + @lex_state = EXPR_CTEXT + token = match(T_TEXT) + @lex_state = EXPR_BEG + result = ResponseCode.new(name, token.value) end match(T_RBRA) @lex_state = EXPR_RTEXT @@ -1579,7 +2052,7 @@ module Net if @str.index(/\(([^)]*)\)/ni, @pos) @pos = $~.end(0) return $1.scan(FLAG_REGEXP).collect { |flag, atom| - atom || flag.intern + atom || flag.capitalize.intern } else parse_error("invalid flag list") diff --git a/lib/net/pop.rb b/lib/net/pop.rb index 4f6eb930a4..8f3f978e8c 100644 --- a/lib/net/pop.rb +++ b/lib/net/pop.rb @@ -1,6 +1,6 @@ =begin -= net/pop.rb version 1.1.32 += net/pop.rb version 1.1.34 written by Minero Aoki <aamine@dp.u-netsurf.ne.jp> @@ -184,6 +184,7 @@ module Net protocol_param :port, '110' protocol_param :command_type, '::Net::NetPrivate::POP3Command' + protocol_param :apop_command_type, '::Net::NetPrivate::APOPCommand' protocol_param :mail_type, '::Net::POPMail' @@ -206,9 +207,10 @@ module Net end - def initialize( addr = nil, port = nil ) - super + def initialize( addr = nil, port = nil, apop = false ) + super addr, port @mails = nil + @apop = false end attr :mails @@ -238,6 +240,11 @@ module Net private + def conn_command( sock ) + @command = + (@apop ? type.apop_command_type : type.command_type).new(sock) + end + def do_start( acnt, pwd ) @command.auth( acnt, pwd ) diff --git a/lib/net/protocol.rb b/lib/net/protocol.rb index 161024cfe2..343721add3 100644 --- a/lib/net/protocol.rb +++ b/lib/net/protocol.rb @@ -1,6 +1,6 @@ =begin -= net/protocol.rb version 1.1.32 += net/protocol.rb version 1.1.34 written by Minero Aoki <aamine@dp.u-netsurf.ne.jp> @@ -59,13 +59,22 @@ Object =end require 'socket' +require 'timeout' module Net + module NetPrivate + end + + def self.net_private( &block ) + ::Net::NetPrivate.module_eval( &block ) + end + + class Protocol - Version = '1.1.32' + Version = '1.1.34' class << self @@ -116,8 +125,12 @@ module Net @command = nil @socket = nil - @active = false - @pipe = nil + @active = false + + @open_timeout = nil + @read_timeout = nil + + @dout = nil end attr_reader :address @@ -126,10 +139,26 @@ module Net attr_reader :command attr_reader :socket + attr_accessor :open_timeout + attr_accessor :read_timeout + + def active? + @active + end + + def set_debug_output( arg ) # un-documented + @dout = arg + end + + alias set_pipe set_debug_output + def inspect "#<#{type} #{address}:#{port} open=#{active?}>" end + # + # open session + # def start( *args ) return false if active? @@ -146,45 +175,59 @@ module Net end end + private + def _start( args ) connect do_start( *args ) @active = true end - private :_start - - def finish - return false unless active? - do_finish unless @command.critical? - disconnect - @active = false - true + def connect + conn_socket @address, @port + conn_command @socket + on_connect end - def active? - @active + def re_connect + @socket.reopen @open_timeout + on_connect end - def set_pipe( arg ) # un-documented - @pipe = arg + def conn_socket( addr, port ) + @socket = type.socket_type.open( + addr, port, @open_timeout, @read_timeout, @dout ) end + def conn_command( sock ) + @command = type.command_type.new( sock ) + end - private - + def on_connect + end def do_start end - def do_finish - @command.quit + # + # close session + # + + public + + def finish + return false unless active? + + do_finish if @command and not @command.critical? + disconnect + @active = false + true end + private - def connect( addr = @address, port = @port ) - @socket = type.socket_type.open( addr, port, @pipe ) - @command = type.command_type.new( @socket ) + def do_finish + @command.quit end def disconnect @@ -192,7 +235,11 @@ module Net if @socket and not @socket.closed? then @socket.close end - @socket = nil + @socket = nil + on_disconnect + end + + def on_disconnect end end @@ -200,6 +247,7 @@ module Net Session = Protocol + net_private { class Response @@ -223,6 +271,8 @@ module Net end + } + class ProtocolError < StandardError; end class ProtoSyntaxError < ProtocolError; end @@ -294,8 +344,7 @@ module Net - module NetPrivate - + net_private { class WriteAdapter @@ -311,7 +360,11 @@ module Net def write( str ) @sock.__send__ @mid, str end - alias << write + + def <<( str ) + @sock.__send__ @mid, str + self + end end @@ -407,6 +460,7 @@ module Net @critical = false end + private def critical @@ -431,22 +485,30 @@ module Net class Socket - def initialize( addr, port, pipe = nil ) + def initialize( addr, port, otime = nil, rtime = nil, dout = nil ) @addr = addr @port = port - @pipe = pipe - @prepipe = nil - @closed = true - @ipaddr = '' + @read_timeout = rtime + + @debugout = dout + + @socket = nil @sending = '' @buffer = '' - @socket = TCPsocket.new( addr, port ) - @closed = false - @ipaddr = @socket.addr[3] + connect otime + D 'opened' end + def connect( otime ) + D "opening connection to #{@addr}..." + timeout( otime ) { + @socket = TCPsocket.new( @addr, @port ) + } + end + private :connect + attr :pipe, true class << self @@ -454,27 +516,31 @@ module Net end def inspect - "#<#{type} open=#{!@closed}>" + "#<#{type} #{closed? ? 'closed' : 'opened'}>" end - def reopen - unless closed? then - close - @buffer = '' - end - @socket = TCPsocket.new( @addr, @port ) - @closed = false + def reopen( otime = nil ) + D 'reopening...' + close + connect otime + D 'reopened' end attr :socket, true def close - @socket.close - @closed = true + if @socket then + @socket.close + D 'closed' + else + D 'close call for already closed socket' + end + @socket = nil + @buffer = '' end def closed? - @closed + not @socket end def address @@ -486,7 +552,8 @@ module Net attr_reader :port def ip_address - @ipaddr.dup + @socket or return '' + @socket.addr[3] end alias ipaddr ip_address @@ -494,57 +561,64 @@ module Net attr_reader :sending - ### - ### read - ### + # + # read + # + + public CRLF = "\r\n" - def read( len, dest = '' ) - @pipe << "reading #{len} bytes...\n" if @pipe; pipeoff + def read( len, dest = '', ignerr = false ) + D_off "reading #{len} bytes..." rsize = 0 - while rsize + @buffer.size < len do - rsize += writeinto( dest, @buffer.size ) - fill_rbuf + begin + while rsize + @buffer.size < len do + rsize += rbuf_moveto( dest, @buffer.size ) + rbuf_fill + end + rbuf_moveto dest, len - rsize + rescue EOFError + raise unless igneof end - writeinto( dest, len - rsize ) - @pipe << "read #{len} bytes\n" if pipeon + D_on "read #{len} bytes" dest end - def read_all( dest = '' ) - @pipe << "reading all...\n" if @pipe; pipeoff + D_off 'reading all...' rsize = 0 begin while true do - rsize += writeinto( dest, @buffer.size ) - fill_rbuf + rsize += rbuf_moveto( dest, @buffer.size ) + rbuf_fill end rescue EOFError ; end - @pipe << "read #{rsize} bytes\n" if pipeon + D_on "read #{rsize} bytes" dest end - - def readuntil( target ) - while true do - idx = @buffer.index( target ) - break if idx - fill_rbuf - end - + def readuntil( target, igneof = false ) dest = '' - writeinto( dest, idx + target.size ) + begin + while true do + idx = @buffer.index( target ) + break if idx + rbuf_fill + end + rbuf_moveto dest, idx + target.size + rescue EOFError + raise unless igneof + rbuf_moveto dest, @buffer.size + end dest end - def readline ret = readuntil( "\n" ) @@ -552,9 +626,8 @@ module Net ret end - def read_pendstr( dest ) - @pipe << "reading text...\n" if @pipe; pipeoff + D_off 'reading text...' rsize = 0 while (str = readuntil("\r\n")) != ".\r\n" do @@ -563,14 +636,13 @@ module Net dest << str end - @pipe << "read #{rsize} bytes\n" if pipeon + D_on "read #{rsize} bytes" dest end - # private use only (can not handle 'break') def read_pendlist - @pipe << "reading list...\n" if @pipe; pipeoff + D_off 'reading list...' str = nil i = 0 @@ -580,55 +652,59 @@ module Net yield str end - @pipe << "read #{i} items\n" if pipeon + D_on "read #{i} items" end private - READ_BLOCK = 1024 * 8 + READ_SIZE = 1024 * 4 - def fill_rbuf - @buffer << @socket.sysread( READ_BLOCK ) + def rbuf_fill + unless IO.select [@socket], nil, nil, @read_timeout then + on_read_timeout + end + @buffer << @socket.sysread( READ_SIZE ) end - def writeinto( dest, len ) + def on_read_timeout + raise TimeoutError, "socket read timeout (#{@read_timeout} sec)" + end + + def rbuf_moveto( dest, len ) bsi = @buffer.size - dest << @buffer[ 0, len ] + s = @buffer[ 0, len ] + dest << s @buffer = @buffer[ len, bsi - len ] - @pipe << %{read "#{Net.quote dest}"\n} if @pipe + @debugout << %<read "#{Net.quote s}"\n> if @debugout len end - ### - ### write - ### + # + # write interfece + # public - def write( str ) writing { do_write str } end - def writeline( str ) writing { - do_write str - do_write "\r\n" + do_write str + "\r\n" } end - def write_bin( src, block ) writing { if block then - block.call WriteAdapter.new( self, :do_write ) + block.call ::Net::NetPrivate::WriteAdapter.new( self, :do_write ) else src.each do |bin| do_write bin @@ -637,19 +713,18 @@ module Net } end - def write_pendstr( src, block ) - @pipe << "writing text from #{src.type}\n" if @pipe; pipeoff + D_off "writing text from #{src.type}" wsize = use_each_crlf_line { if block then - block.call WriteAdapter.new( self, :wpend_in ) + block.call ::Net::NetPrivate::WriteAdapter.new( self, :wpend_in ) else wpend_in src end } - @pipe << "wrote #{wsize} bytes text\n" if pipeon + D_on "wrote #{wsize} bytes text" wsize end @@ -696,17 +771,17 @@ module Net beg = 0 buf = @wbuf while buf.index( /\n|\r\n|\r/, beg ) do - m = $~ + m = Regexp.last_match if m.begin(0) == buf.size - 1 and buf[-1] == ?\r then # "...\r" : can follow "\n..." break end - str = buf[ beg, m.begin(0) - beg ] + str = buf[ beg ... m.begin(0) ] str.concat "\r\n" yield str beg = m.end(0) end - @wbuf = buf[ beg, buf.size - beg ] + @wbuf = buf[ beg ... buf.size ] end end @@ -736,6 +811,7 @@ module Net yield end end + yield unless @wbuf.empty? end end @@ -746,17 +822,17 @@ module Net yield - if @pipe then - @pipe << 'write "' - @pipe << @sending - @pipe << "\"\n" + if @debugout then + @debugout << 'write "' + @debugout << @sending + @debugout << "\"\n" end @socket.flush @writtensize end def do_write( arg ) - if @pipe or @sending.size < 128 then + if @debugout or @sending.size < 128 then @sending << Net.quote( arg ) else @sending << '...' unless @sending[-1] == ?. @@ -768,22 +844,25 @@ module Net end - def pipeoff - @prepipe = @pipe - @pipe = nil - @prepipe + def D_off( msg ) + D msg + @savedo, @debugout = @debugout, nil end - def pipeon - @pipe = @prepipe - @prepipe = nil - @pipe + def D_on( msg ) + @debugout = @savedo + D msg end - end + def D( msg ) + @debugout or return + @debugout << msg + @debugout << "\n" + end + end - end # module Net::NetPrivate + } def Net.quote( str ) diff --git a/lib/net/smtp.rb b/lib/net/smtp.rb index 9679984e2c..befc1adf03 100644 --- a/lib/net/smtp.rb +++ b/lib/net/smtp.rb @@ -1,6 +1,6 @@ =begin -= net/smtp.rb version 1.1.32 += net/smtp.rb version 1.1.34 written by Minero Aoki <aamine@dp.u-netsurf.ne.jp> @@ -30,10 +30,8 @@ Net::Protocol === Methods -: start( helo_domain = Socket.gethostname, \ - account = nil, password = nil, authtype = nil ) -: start( helo_domain = Socket.gethostname, \ - account = nil, password = nil, authtype = nil ) {|smtp| .... } +: start( helo_domain = Socket.gethostname, account = nil, password = nil, authtype = nil ) +: start( helo_domain = Socket.gethostname, account = nil, password = nil, authtype = nil ) {|smtp| .... } opens TCP connection and starts SMTP session. If protocol had been started, do nothing and return false. @@ -53,10 +51,10 @@ Net::Protocol to_addrs must be a String(s) or an Array of String. Exceptions which SMTP raises are: - * Net::ProtoSyntaxError: syntax error (errno.500) - * Net::ProtoFatalError: fatal error (errno.550) - * Net::ProtoUnknownError: unknown error - * Net::ProtoServerBusy: temporary error (errno.420/450) + * Net::ProtoSyntaxError: syntax error (errno.500) + * Net::ProtoFatalError: fatal error (errno.550) + * Net::ProtoUnknownError: unknown error + * Net::ProtoServerBusy: temporary error (errno.420/450) # usage example @@ -153,12 +151,15 @@ module Net end end - if user and secret then + if user or secret then + (user and secret) or + raise ArgumentError, 'both of account and password are required' + mid = 'auth_' + (authtype || 'cram_md5').to_s - unless @command.respond_to? mid then - raise ArgumentError, "wrong auth type #{authtype.to_s}" - end - @command.send mid, user, secret + @command.respond_to? mid or + raise ArgumentError, "wrong auth type #{authtype.to_s}" + + @command.__send__ mid, user, secret end end diff --git a/lib/net/telnet.rb b/lib/net/telnet.rb index 87790c0300..380e834bea 100644 --- a/lib/net/telnet.rb +++ b/lib/net/telnet.rb @@ -4,7 +4,7 @@ net/telnet.rb - simple telnet client library -Version 1.6.2 +Version 1.6.3 Wakou Aoyama <wakou@fsinet.or.jp> @@ -239,10 +239,11 @@ module Net CR = "\015" LF = "\012" EOL = CR + LF - VERSION = "1.6.2" - RELEASE_DATE = "2000-12-25" - VERSION_CODE = 162 - RELEASE_CODE = 20001225 + VERSION = '1.6.3' + RELEASE_DATE = '2001-02-26' + VERSION_CODE = 163 + RELEASE_CODE = 20010226 + REVISION = '$Id$' def initialize(options) @options = options @@ -346,14 +347,13 @@ module Net attr :sock def telnetmode(mode = nil) - if mode - if (true == mode or false == mode) - @options["Telnetmode"] = mode - else - raise ArgumentError, "required true or false" - end - else + case mode + when nil @options["Telnetmode"] + when true, false + @options["Telnetmode"] = mode + else + raise ArgumentError, "required true or false" end end @@ -366,14 +366,13 @@ module Net end def binmode(mode = nil) - if mode - if (true == mode or false == mode) - @options["Binmode"] = mode - else - raise ArgumentError, "required true or false" - end - else + case mode + when nil @options["Binmode"] + when true, false + @options["Binmode"] = mode + else + raise ArgumentError, "required true or false" end end @@ -599,181 +598,7 @@ end == HISTORY -* Mon Dec 25 01:37:43 JST 2000 - wakou - * version 1.6.2 - * Regexp::last_match[1] --> $1 - -* Mon Dec 11 00:16:51 JST 2000 - wakou - * version 1.6.1 - * $1 --> Regexp::last_match[1] - -* 2000/09/12 05:37:35 - matz - * change: iterator? --> block_given? - -* Tue Sep 12 06:52:48 JST 2000 - wakou - * version 1.6.0 - * correct: document. - thanks to Kazuhiro NISHIYAMA <zn@mbf.nifty.com> - * add: Telnet#puts(). - -* Sun Jun 18 23:31:44 JST 2000 - wakou - * version 1.5.0 - * change: version syntax. old: x.yz, now: x.y.z - -* 2000/05/24 06:57:38 - wakou - * version 1.40 - * improve: binmode(), telnetmode() interface. - thanks to Dave Thomas <Dave@thomases.com> - -* 2000/05/09 22:02:56 - wakou - * version 1.32 - * require English.rb - -* 2000/05/02 21:48:39 - wakou - * version 1.31 - * Proxy option: can receive IO object. - -* 2000/04/03 18:27:02 - wakou - * version 1.30 - * telnet.rb --> net/telnet.rb - -* 2000/01/24 17:02:57 - wakou - * version 1.20 - * respond to "IAC WILL x" with "IAC DONT x" - * respond to "IAC WONT x" with "IAC DONT x" - * better dumplog format. - thanks to WATANABE Hirofumi <Hirofumi.Watanabe@jp.sony.com> - -* 2000/01/18 17:47:31 - wakou - * version 1.10 - * bug fix: write method - * respond to "IAC WILL BINARY" with "IAC DO BINARY" - -* 1999/10/04 22:51:26 - wakou - * version 1.00 - * bug fix: waitfor(preprocess) method. - thanks to Shin-ichiro Hara <sinara@blade.nagaokaut.ac.jp> - * add simple support for AO, DM, IP, NOP, SB, SE - * COUTION! TimeOut --> TimeoutError - -* 1999/09/21 21:24:07 - wakou - * version 0.50 - * add write method - -* 1999/09/17 17:41:41 - wakou - * version 0.40 - * bug fix: preprocess method - -* 1999/09/14 23:09:05 - wakou - * version 0.30 - * change prompt check order. - not IO::select([@sock], nil, nil, waittime) and prompt === line - --> prompt === line and not IO::select([@sock], nil, nil, waittime) - -* 1999/09/13 22:28:33 - wakou - * version 0.24 - * Telnet#login: if ommit password, then not require password prompt. - -* 1999/08/10 05:20:21 - wakou - * version 0.232 - * STATUS OUTPUT sample code typo. - thanks to Tadayoshi Funaba <tadf@kt.rim.or.jp> - host = Telnet.new({"Hosh" => "localhost"){|c| print c } - --> host = Telnet.new({"Host" => "localhost"){|c| print c } - -* 1999/07/16 13:39:42 - wakou - * version 0.231 - * TRUE --> true, FALSE --> false - -* 1999/07/15 22:32:09 - wakou - * version 0.23 - * waitfor: if end of file reached, then return nil. - -* 1999/06/29 09:08:51 - wakou - * version 0.22 - * new, waitfor, cmd: {"Timeout" => false} # ignore timeout - -* 1999/06/28 18:18:55 - wakou - * version 0.21 - * waitfor: not rescue (EOFError) - -* 1999/06/04 06:24:58 - wakou - * version 0.20 - * waitfor: support for divided telnet command - -* 1999/05/22 - wakou - * version 0.181 - * bug fix: print method - -* 1999/05/14 - wakou - * version 0.18 - * respond to "IAC WON'T SGA" with "IAC DON'T SGA" - * DON'T SGA : end of line --> CR + LF - * bug fix: preprocess method - -* 1999/04/30 - wakou - * version 0.17 - * bug fix: $! + "\n" --> $!.to_s + "\n" - -* 1999/04/11 - wakou - * version 0.163 - * STDOUT.write(message) --> yield(message) if iterator? - -* 1999/03/17 - wakou - * version 0.162 - * add "Proxy" option - * required timeout.rb - -* 1999/02/03 - wakou - * version 0.161 - * select --> IO::select - -* 1998/10/09 - wakou - * version 0.16 - * preprocess method change for the better - * add binmode method. - * change default Binmode. TRUE --> FALSE - -* 1998/10/04 - wakou - * version 0.15 - * add telnetmode method. - -* 1998/09/22 - wakou - * version 0.141 - * change default prompt. /[$%#>] $/ --> /[$%#>] \Z/ - -* 1998/09/01 - wakou - * version 0.14 - * IAC WILL SGA send EOL --> CR+NULL - * IAC WILL SGA IAC DO BIN send EOL --> CR - * NONE send EOL --> LF - * add Dump_log option. - -* 1998/08/25 - wakou - * version 0.13 - * add print method. - -* 1998/08/05 - wakou - * version 0.122 - * support for HP-UX 10.20. - thanks to WATANABE Tetsuya <tetsu@jpn.hp.com> - * socket.<< --> socket.write - -* 1998/07/15 - wakou - * version 0.121 - * string.+= --> string.concat - -* 1998/06/01 - wakou - * version 0.12 - * add timeout, waittime. - -* 1998/04/21 - wakou - * version 0.11 - * add realtime output. - -* 1998/04/13 - wakou - * version 0.10 - * first release. - -$Date$ +delete. see cvs log. + + =end diff --git a/lib/observer.rb b/lib/observer.rb index 08e75f5125..e1b249e885 100644 --- a/lib/observer.rb +++ b/lib/observer.rb @@ -5,7 +5,7 @@ module Observable def add_observer(observer) @observer_peers = [] unless defined? @observer_peers - unless defined? observer.update + unless observer.respond_to? :update raise NameError, "observer needs to respond to `update'" end @observer_peers.push observer diff --git a/lib/open3.rb b/lib/open3.rb index 58de740393..33701bbfc0 100644 --- a/lib/open3.rb +++ b/lib/open3.rb @@ -32,6 +32,7 @@ module Open3 exec(*cmd) } + exit! } pw[0].close diff --git a/lib/parsedate.rb b/lib/parsedate.rb index eee114acb2..7fc75cf0c2 100644 --- a/lib/parsedate.rb +++ b/lib/parsedate.rb @@ -1,5 +1,5 @@ -# parsedate.rb: Written by Tadayoshi Funaba 2000 -# $Id: parsedate.rb,v 1.2 2000-04-01 12:16:56+09 tadf Exp $ +# parsedate3.rb: Written by Tadayoshi Funaba 2000, 2001 +# $Id: parsedate3.rb,v 1.3 2001-01-18 12:09:47+09 tadf Exp $ module ParseDate @@ -46,7 +46,12 @@ module ParseDate hour = $1.to_i min = $2.to_i sec = $3.to_i if $3 - hour += 12 if $4 and $4.downcase == 'p' + if $4 + hour %= 12 + if $4.downcase == 'p' + hour += 12 + end + end zone = $5 end diff --git a/lib/ping.rb b/lib/ping.rb index 48657818cc..d698dd0c52 100644 --- a/lib/ping.rb +++ b/lib/ping.rb @@ -47,6 +47,8 @@ module Ping s = TCPsocket.new(host, service) s.close end + rescue Errno::ECONNREFUSED + return true rescue return false end diff --git a/lib/pstore.rb b/lib/pstore.rb index b3e1df8284..d5334efda4 100644 --- a/lib/pstore.rb +++ b/lib/pstore.rb @@ -41,11 +41,10 @@ class PStore def [](name) in_transaction - value = @table[name] - if value == nil + unless @table.key? name raise PStore::Error, format("undefined root name `%s'", name) end - value + @table[name] end def []=(name, value) in_transaction @@ -69,10 +68,12 @@ class PStore end def commit + in_transaction @abort = false throw :pstore_abort_transaction end def abort + in_transaction @abort = true throw :pstore_abort_transaction end diff --git a/lib/shell.rb b/lib/shell.rb new file mode 100644 index 0000000000..1d28834213 --- /dev/null +++ b/lib/shell.rb @@ -0,0 +1,274 @@ +# +# shell.rb - +# $Release Version: 0.6.0 $ +# $Revision: 1.8 $ +# $Date: 2001/03/19 09:01:11 $ +# by Keiju ISHITSUKA(Nippon Rational Inc.) +# +# -- +# +# +# + +require "e2mmap" +require "thread" + +require "shell/error" +require "shell/command-processor" +require "shell/process-controller" + +class Shell + @RCS_ID='-$Id: shell.rb,v 1.8 2001/03/19 09:01:11 keiju Exp keiju $-' + + include Error + extend Exception2MessageMapper + +# @cascade = true + # debug: true -> normal debug + # debug: 1 -> eval definition debug + # debug: 2 -> detail inspect debug + @debug = false + @verbose = true + + class << Shell + attr :cascade, true + attr :debug, true + attr :verbose, true + +# alias cascade? cascade + alias debug? debug + alias verbose? verbose + @verbose = true + + def debug=(val) + @debug = val + @verbose = val if val + end + + def cd(path) + sh = new + sh.cd path + sh + end + + def default_system_path + if @default_system_path + @default_system_path + else + ENV["PATH"].split(":") + end + end + + def default_system_path=(path) + @default_system_path = path + end + + def default_record_separator + if @default_record_separator + @default_record_separator + else + $/ + end + end + + def default_record_separator=(rs) + @default_record_separator = rs + end + end + + def initialize + @cwd = Dir.pwd + @dir_stack = [] + @umask = nil + + @system_path = Shell.default_system_path + @record_separator = Shell.default_record_separator + + @command_processor = CommandProcessor.new(self) + @process_controller = ProcessController.new(self) + + @verbose = Shell.verbose + @debug = Shell.debug + end + + attr_reader :system_path + + def system_path=(path) + @system_path = path + rehash + end + + attr :umask, true + attr :record_separator, true + + attr :verbose, true + attr :debug, true + + def debug=(val) + @debug = val + @verbose = val if val + end + + alias verbose? verbose + alias debug? debug + + attr_reader :command_processor + attr_reader :process_controller + + def expand_path(path) + if /^\// =~ path + File.expand_path(path) + else + File.expand_path(File.join(@cwd, path)) + end + end + + # Most Shell commands are defined via CommandProcessor + + # + # Dir related methods + # + # Shell#cwd/dir/getwd/pwd + # Shell#chdir/cd + # Shell#pushdir/pushd + # Shell#popdir/popd + # Shell#mkdir + # Shell#rmdir + + attr :cwd + alias dir cwd + alias getwd cwd + alias pwd cwd + + attr :dir_stack + alias dirs dir_stack + + # If called as iterator, it restores the current directory when the + # block ends. + def chdir(path = nil) + if iterator? + cwd_old = @cwd + begin + chdir(path) + yield + ensure + chdir(cwd_old) + end + else + path = "~" unless path + @cwd = expand_path(path) + notify "current dir: #{@cwd}" + rehash + self + end + end + alias cd chdir + + def pushdir(path = nil) + if iterator? + pushdir(path) + begin + yield + ensure + popdir + end + elsif path + @dir_stack.push @cwd + chdir path + notify "dir stack: [#{@dir_stack.join ', '}]" + self + else + if pop = @dir_stack.pop + @dir_stack.push @cwd + chdir pop + notify "dir stack: [#{@dir_stack.join ', '}]" + self + else + Shell.Fail DirStackEmpty + end + end + end + alias pushd pushdir + + def popdir + if pop = @dir_stack.pop + chdir pop + notify "dir stack: [#{@dir_stack.join ', '}]" + self + else + Shell.Fail DirStackEmpty + end + end + alias popd popdir + + + # + # process management + # + def jobs + @process_controller.jobs + end + + def kill(sig, command) + @process_controller.kill_job(sig, command) + end + + # + # command definitions + # + def Shell.def_system_command(command, path = command) + CommandProcessor.def_system_command(command, path) + end + + def Shell.undef_system_command(command) + CommandProcessor.undef_system_command(command) + end + + def Shell.alias_command(ali, command, *opts, &block) + CommandProcessor.alias_command(ali, command, *opts, &block) + end + + def Shell.unalias_command(ali) + CommandProcessor.unalias_command(ali) + end + + def Shell.install_system_commands(pre = "sys_") + CommandProcessor.install_system_commands(pre) + end + + # + def inspect + if debug.kind_of?(Integer) && debug > 2 + super + else + to_s + end + end + + def self.notify(*opts, &block) + Thread.exclusive do + if opts[-1].kind_of?(String) + yorn = verbose? + else + yorn = opts.pop + end + return unless yorn + + _head = true + print *opts.collect{|mes| + mes = mes.dup + yield mes if iterator? + if _head + _head = false + "shell: " + mes + else + " " + mes + end + }.join("\n")+"\n" + end + end + + CommandProcessor.initialize + CommandProcessor.run_config +end + diff --git a/lib/shell/builtin-command.rb b/lib/shell/builtin-command.rb new file mode 100644 index 0000000000..db1adfa48b --- /dev/null +++ b/lib/shell/builtin-command.rb @@ -0,0 +1,154 @@ +# +# shell/builtin-command.rb - +# $Release Version: 0.6.0 $ +# $Revision$ +# $Date$ +# by Keiju ISHITSUKA(Nihon Rational Software Co.,Ltd) +# +# -- +# +# +# + +require "shell/filter" + +class Shell + class BuiltInCommand<Filter + def wait? + false + end + def active? + true + end + end + + class Echo < BuiltInCommand + def initialize(sh, *strings) + super sh + @strings = strings + end + + def each(rs = nil) + rs = @shell.record_separator unless rs + for str in @strings + yield str + rs + end + end + end + + class Cat < BuiltInCommand + def initialize(sh, *filenames) + super sh + @cat_files = filenames + end + + def each(rs = nil) + if @cat_files.empty? + super + else + for src in @cat_files + @shell.foreach(src, rs){|l| yield l} + end + end + end + end + + class Glob < BuiltInCommand + def initialize(sh, pattern) + super sh + + @pattern = pattern + Thread.critical = true + back = Dir.pwd + begin + Dir.chdir @shell.cwd + @files = Dir[pattern] + ensure + Dir.chdir back + Thread.critical = false + end + end + + def each(rs = nil) + rs = @shell.record_separator unless rs + for f in @files + yield f+rs + end + end + end + +# class Sort < Cat +# def initialize(sh, *filenames) +# super +# end +# +# def each(rs = nil) +# ary = [] +# super{|l| ary.push l} +# for l in ary.sort! +# yield l +# end +# end +# end + + class AppendIO < BuiltInCommand + def initialize(sh, io, filter) + super sh + @input = filter + @io = io + end + + def input=(filter) + @input.input=filter + for l in @input + @io << l + end + end + + end + + class AppendFile < AppendIO + def initialize(sh, to_filename, filter) + @file_name = to_filename + io = sh.open(to_filename, "a") + super(sh, io, filter) + end + + def input=(filter) + begin + super + ensure + @io.close + end + end + end + + class Tee < BuiltInCommand + def initialize(sh, filename) + super sh + @to_filename = filename + end + + def each(rs = nil) + to = @shell.open(@to_filename, "w") + begin + super{|l| to << l; yield l} + ensure + to.close + end + end + end + + class Concat < BuiltInCommand + def initialize(sh, *jobs) + super(sh) + @jobs = jobs + end + + def each(rs = nil) + while job = @jobs.shift + job.each{|l| yield l} + end + end + end +end diff --git a/lib/shell/command-processor.rb b/lib/shell/command-processor.rb new file mode 100644 index 0000000000..fa253b3705 --- /dev/null +++ b/lib/shell/command-processor.rb @@ -0,0 +1,584 @@ +# +# shell/command-controller.rb - +# $Release Version: 0.6.0 $ +# $Revision$ +# $Date$ +# by Keiju ISHITSUKA(Nippon Rational Inc.) +# +# -- +# +# +# + +require "e2mmap" +require "ftools" +require "thread" + +require "shell/error" +require "shell/filter" +require "shell/system-command" +require "shell/builtin-command" + +class Shell + class CommandProcessor + + # + # initialize of Shell and related classes. + # + NoDelegateMethods = ["initialize", "expand_path"] + def self.initialize + + install_builtin_commands + + # define CommandProccessor#methods to Shell#methods and Filter#methods + for m in CommandProcessor.instance_methods - NoDelegateMethods + add_delegate_command_to_shell(m) + end + + def self.method_added(id) + add_delegate_command_to_shell(id) + end + end + + # + # include run file. + # + def self.run_config + begin + load File.expand_path("~/.rb_shell") if ENV.key?("HOME") + rescue LoadError, Errno::ENOENT + rescue + print "load error: #{rc}\n" + print $!.type, ": ", $!, "\n" + for err in $@[0, $@.size - 2] + print "\t", err, "\n" + end + end + end + + def initialize(shell) + @shell = shell + @system_commands = {} + end + + # + # CommandProcessor#expand_path(path) + # path: String + # return: String + # returns the absolute path for <path> + # + def expand_path(path) + @shell.expand_path(path) + end + + # + # File related commands + # Shell#foreach + # Shell#open + # Shell#unlink + # Shell#test + # + # - + # + # CommandProcessor#foreach(path, rs) + # path: String + # rs: String - record separator + # iterator + # Same as: + # File#foreach (when path is file) + # Dir#foreach (when path is directory) + # path is relative to pwd + # + def foreach(path = nil, *rs) + path = "." unless path + path = expand_path(path) + + if File.directory?(path) + Dir.foreach(path){|fn| yield fn} + else + IO.foreach(path, *rs){|l| yield l} + end + end + + # + # CommandProcessor#open(path, mode) + # path: String + # mode: String + # return: File or Dir + # Same as: + # File#open (when path is file) + # Dir#open (when path is directory) + # mode has an effect only when path is a file + # + def open(path, mode) + path = expand_path(path) + if File.directory?(path) + Dir.open(path) + else + effect_umask do + File.open(path, mode) + end + end + end + # public :open + + # + # CommandProcessor#unlink(path) + # same as: + # Dir#unlink (when path is directory) + # File#unlink (when path is file) + # + def unlink(path) + path = expand_path(path) + if File.directory?(path) + Dir.unlink(path) + else + IO.unlink(path) + end + end + + # + # CommandProcessor#test(command, file1, file2) + # CommandProcessor#[command, file1, file2] + # command: char or String or Symbol + # file1: String + # file2: String(optional) + # return: Boolean + # same as: + # test() (when command is char or length 1 string or sumbol) + # FileTest.command (others) + # example: + # sh[?e, "foo"] + # sh[:e, "foo"] + # sh["e", "foo"] + # sh[:exists?, "foo"] + # sh["exists?", "foo"] + # + def test(command, file1, file2=nil) + file1 = expand_path(file1) + file2 = expand_path(file2) if file2 + command = command.id2name if command.kind_of?(Symbol) + + case command + when Integer + top_level_test(command, file1, file2) + when String + if command.size == 1 + if file2 + top_level_test(command, file1, file2) + else + top_level_test(command, file1) + end + else + if file2 + FileTest.send(command, file1, file2) + else + FileTest.send(command, file1) + end + end + end + end + alias [] test + + # + # Dir related methods + # + # Shell#mkdir + # Shell#rmdir + # + #-- + # + # CommandProcessor#mkdir(*path) + # path: String + # same as Dir.mkdir() + # + def mkdir(*path) + for dir in path + Dir.mkdir(expand_path(dir)) + end + end + + # + # CommandProcessor#rmdir(*path) + # path: String + # same as Dir.rmdir() + # + def rmdir(*path) + for dir in path + Dir.rmdir(expand_path(path)) + end + end + + # + # CommandProcessor#system(command, *opts) + # command: String + # opts: String + # retuen: SystemCommand + # Same as system() function + # example: + # print sh.system("ls", "-l") + # sh.system("ls", "-l") | sh.head > STDOUT + # + def system(command, *opts) + SystemCommand.new(@shell, find_system_command(command), *opts) + end + + # + # ProcessCommand#rehash + # clear command hash table. + # + def rehash + @system_commands = {} + end + + # + # ProcessCommand#transact + # + def check_point + @shell.process_controller.wait_all_jobs_execution + end + alias finish_all_jobs check_point + + def transact(&block) + begin + @shell.instance_eval &block + ensure + check_point + end + end + + # + # internal commands + # + def out(dev = STDOUT, &block) + dev.print transact &block + end + + def echo(*strings) + Echo.new(@shell, *strings) + end + + def cat(*filenames) + Cat.new(@shell, *filenames) + end + + # def sort(*filenames) + # Sort.new(self, *filenames) + # end + + def glob(pattern) + Glob.new(@shell, pattern) + end + + def append(to, filter) + case to + when String + AppendFile.new(@shell, to, filter) + when IO + AppendIO.new(@shell, to, filter) + else + Shell.Fail CanNotMethodApply, "append", to.type + end + end + + def tee(file) + Tee.new(@shell, file) + end + + def concat(*jobs) + Concat.new(@shell, *jobs) + end + + # %pwd, %cwd -> @pwd + def notify(*opts, &block) + Thread.exclusive do + Shell.notify(*opts) {|mes| + yield mes if iterator? + + mes.gsub!("%pwd", "#{@cwd}") + mes.gsub!("%cwd", "#{@cwd}") + } + end + end + + # + # private functions + # + def effect_umask + if @shell.umask + Thread.critical = true + save = File.umask + begin + yield + ensure + File.umask save + Thread.critical = false + end + else + yield + end + end + private :effect_umask + + def find_system_command(command) + return command if /^\// =~ command + case path = @system_commands[command] + when String + if exists?(path) + return path + else + Shell.Fail CommandNotFound, command + end + when false + Shell.Fail CommandNotFound, command + end + + for p in @shell.system_path + path = join(p, command) + if FileTest.exists?(path) + @system_commands[command] = path + return path + end + end + @system_commands[command] = false + Shell.Fail CommandNotFound, command + end + + # + # CommandProcessor.def_system_command(command, path) + # command: String + # path: String + # define 'command()' method as method. + # + def self.def_system_command(command, path = command) + begin + eval ((d = %Q[def #{command}(*opts) + SystemCommand.new(@shell, '#{path}', *opts) + end]), nil, __FILE__, __LINE__ - 1) + rescue SyntaxError + Shell.notify "warn: Can't define #{command} path: #{path}." + end + Shell.notify "Define #{command} path: #{path}.", Shell.debug? + Shell.notify("Definition of #{command}: ", d, + Shell.debug.kind_of?(Integer) && Shell.debug > 1) + end + + def self.undef_system_command(command) + command = command.id2name if command.kind_of?(Symbol) + remove_method(command) + Shell.module_eval{remove_method(command)} + Filter.module_eval{remove_method(command)} + self + end + + # define command alias + # ex) + # def_alias_command("ls_c", "ls", "-C", "-F") + # def_alias_command("ls_c", "ls"){|*opts| ["-C", "-F", *opts]} + # + @alias_map = {} + def self.alias_map + @alias_map + end + def self.alias_command(ali, command, *opts, &block) + ali = ali.id2name if ali.kind_of?(Symbol) + command = command.id2name if command.kind_of?(Symbol) + begin + if iterator? + @alias_map[ali.intern] = proc + + eval ((d = %Q[def #{ali}(*opts) + @shell.__send__(:#{command}, + *(CommandProcessor.alias_map[:#{ali}].call *opts)) + end]), nil, __FILE__, __LINE__ - 1) + + else + args = opts.collect{|opt| '"' + opt + '"'}.join "," + eval ((d = %Q[def #{ali}(*opts) + @shell.__send__(:#{command}, #{args}, *opts) + end]), nil, __FILE__, __LINE__ - 1) + end + rescue SyntaxError + Shell.notify "warn: Can't alias #{ali} command: #{command}." + Shell.notify("Definition of #{ali}: ", d) + raise + end + Shell.notify "Define #{ali} command: #{command}.", Shell.debug? + Shell.notify("Definition of #{ali}: ", d, + Shell.debug.kind_of?(Integer) && Shell.debug > 1) + self + end + + def self.unalias_command(ali) + ali = ali.id2name if ali.kind_of?(Symbol) + @alias_map.delete ali.intern + undef_system_command(ali) + end + + # + # CommandProcessor.def_builtin_commands(delegation_class, command_specs) + # delegation_class: Class or Module + # command_specs: [[command_name, [argument,...]],...] + # command_name: String + # arguments: String + # FILENAME?? -> expand_path(filename??) + # *FILENAME?? -> filename??.collect{|f|expand_path(f)}.join(", ") + # define command_name(argument,...) as + # delegation_class.command_name(argument,...) + # + def self.def_builtin_commands(delegation_class, command_specs) + for meth, args in command_specs + arg_str = args.collect{|arg| arg.downcase}.join(", ") + call_arg_str = args.collect{ + |arg| + case arg + when /^(FILENAME.*)$/ + format("expand_path(%s)", $1.downcase) + when /^(\*FILENAME.*)$/ + # \*FILENAME* -> filenames.collect{|fn| expand_path(fn)}.join(", ") + $1.downcase + '.collect{|fn| expand_path(fn)}' + else + arg + end + }.join(", ") + d = %Q[def #{meth}(#{arg_str}) + #{delegation_class}.#{meth}(#{call_arg_str}) + end] + Shell.notify "Define #{meth}(#{arg_str})", Shell.debug? + Shell.notify("Definition of #{meth}: ", d, + Shell.debug.kind_of?(Integer) && Shell.debug > 1) + eval d + end + end + + # + # CommandProcessor.install_system_commands(pre) + # pre: String - command name prefix + # defines every command which belongs in default_system_path via + # CommandProcessor.command(). It doesn't define already defined + # methods twice. By default, "pre_" is prefixes to each method + # name. Characters that may not be used in a method name are + # all converted to '_'. Definition errors are just ignored. + # + def self.install_system_commands(pre = "sys_") + defined_meth = {} + for m in Shell.methods + defined_meth[m] = true + end + sh = Shell.new + for path in Shell.default_system_path + next unless sh.directory? path + sh.cd path + sh.foreach do + |cn| + if !defined_meth[pre + cn] && sh.file?(cn) && sh.executable?(cn) + command = (pre + cn).gsub(/\W/, "_").sub(/^([0-9])/, '_\1') + begin + def_system_command(command, sh.expand_path(cn)) + rescue + Shell.notify "warn: Can't define #{command} path: #{cn}" + end + defined_meth[command] = command + end + end + end + end + + #---------------------------------------------------------------------- + # + # class initializing methods - + # + #---------------------------------------------------------------------- + def self.add_delegate_command_to_shell(id) + id = id.intern if id.kind_of?(String) + name = id.id2name + if Shell.method_defined?(id) + Shell.notify "warn: override definnition of Shell##{name}." + Shell.notify "warn: alias Shell##{name} to Shell##{name}_org.\n" + Shell.module_eval "alias #{name}_org #{name}" + end + Shell.notify "method added: Shell##{name}.", Shell.debug? + Shell.module_eval(%Q[def #{name}(*args, &block) + begin + @command_processor.__send__(:#{name}, *args, &block) + rescue Exception + $@.delete_if{|s| /:in `__getobj__'$/ =~ s} #` + $@.delete_if{|s| /^\\(eval\\):/ =~ s} + raise + end + end], __FILE__, __LINE__) + + if Shell::Filter.method_defined?(id) + Shell.notify "warn: override definnition of Shell::Filter##{name}." + Shell.notify "warn: alias Shell##{name} to Shell::Filter##{name}_org." + Filter.module_eval "alias #{name}_org #{name}" + end + Shell.notify "method added: Shell::Filter##{name}.", Shell.debug? + Filter.module_eval(%Q[def #{name}(*args, &block) + begin + self | @shell.__send__(:#{name}, *args, &block) + rescue Exception + $@.delete_if{|s| /:in `__getobj__'$/ =~ s} #` + $@.delete_if{|s| /^\\(eval\\):/ =~ s} + raise + end + end], __FILE__, __LINE__) + end + + # + # define default builtin commands + # + def self.install_builtin_commands + # method related File. + # (exclude open/foreach/unlink) + normal_delegation_file_methods = [ + ["atime", ["FILENAME"]], + ["basename", ["fn", "*opts"]], + ["chmod", ["mode", "*FILENAMES"]], + ["chown", ["owner", "group", "*FILENAME"]], + ["ctime", ["FILENAMES"]], + ["delete", ["*FILENAMES"]], + ["dirname", ["FILENAME"]], + ["ftype", ["FILENAME"]], + ["join", ["*items"]], + ["link", ["FILENAME_O", "FILENAME_N"]], + ["lstat", ["FILENAME"]], + ["mtime", ["FILENAME"]], + ["readlink", ["FILENAME"]], + ["rename", ["FILENAME_FROM", "FILENAME_TO"]], + # ["size", ["FILENAME"]], + ["split", ["pathname"]], + ["stat", ["FILENAME"]], + ["symlink", ["FILENAME_O", "FILENAME_N"]], + ["truncate", ["FILENAME", "length"]], + ["utime", ["atime", "mtime", "*FILENAMES"]]] + + def_builtin_commands(File, normal_delegation_file_methods) + alias_method :rm, :delete + + # method related FileTest + def_builtin_commands(FileTest, + FileTest.singleton_methods.collect{|m| [m, ["FILENAME"]]}) + + # method related ftools + normal_delegation_ftools_methods = [ + ["syscopy", ["FILENAME_FROM", "FILENAME_TO"]], + ["copy", ["FILENAME_FROM", "FILENAME_TO"]], + ["move", ["FILENAME_FROM", "FILENAME_TO"]], + ["compare", ["FILENAME_FROM", "FILENAME_TO"]], + ["safe_unlink", ["*FILENAMES"]], + ["makedirs", ["*FILENAMES"]], + # ["chmod", ["mode", "*FILENAMES"]], + ["install", ["FILENAME_FROM", "FILENAME_TO", "mode"]], + ] + def_builtin_commands(File, + normal_delegation_ftools_methods) + alias_method :cmp, :compare + alias_method :mv, :move + alias_method :cp, :copy + alias_method :rm_f, :safe_unlink + alias_method :mkpath, :makedirs + end + + end +end diff --git a/lib/shell/error.rb b/lib/shell/error.rb new file mode 100644 index 0000000000..df5e669af6 --- /dev/null +++ b/lib/shell/error.rb @@ -0,0 +1,26 @@ +# +# shell/error.rb - +# $Release Version: 0.6.0 $ +# $Revision$ +# $Date$ +# by Keiju ISHITSUKA(Nihon Rational Software Co.,Ltd) +# +# -- +# +# +# + +require "e2mmap" + +class Shell + module Error + extend Exception2MessageMapper + def_e2message TypeError, "wrong argument type %s (expected %s)" + + def_exception :DirStackEmpty, "Directory stack empty." + def_exception :CanNotDefine, "Can't define method(%s, %s)." + def_exception :CanNotMethodApply, "This method(%s) can't apply this type(%s)." + def_exception :CommandNotFound, "Command not found(%s)." + end +end + diff --git a/lib/shell/filter.rb b/lib/shell/filter.rb new file mode 100644 index 0000000000..441cded221 --- /dev/null +++ b/lib/shell/filter.rb @@ -0,0 +1,111 @@ +# +# shell/filter.rb - +# $Release Version: 0.6.0 $ +# $Revision$ +# $Date$ +# by Keiju ISHITSUKA(Nihon Rational Software Co.,Ltd) +# +# -- +# +# +# + +class Shell + # + # Filter + # A method to require + # each() + # + class Filter + include Enumerable + include Error + + def initialize(sh) + @shell = sh # parent shell + @input = nil # input filter + end + + attr_reader :input + + def input=(filter) + @input = filter + end + + def each(rs = nil) + rs = @shell.record_separator unless rs + if @input + @input.each(rs){|l| yield l} + end + end + + def < (src) + case src + when String + cat = Cat.new(@shell, src) + cat | self + when IO + self.input = src + self + else + Filter.Fail CanNotMethodApply, "<", to.type + end + end + + def > (to) + case to + when String + dst = @shell.open(to, "w") + begin + each(){|l| dst << l} + ensure + dst.close + end + when IO + each(){|l| to << l} + else + Filter.Fail CanNotMethodApply, ">", to.type + end + self + end + + def >> (to) + begin + Shell.cd(@shell.pwd).append(to, self) + rescue CanNotMethodApply + Shell.Fail CanNotMethodApply, ">>", to.type + end + end + + def | (filter) + filter.input = self + if active? + @shell.process_controller.start_job filter + end + filter + end + + def + (filter) + Join.new(@shell, self, filter) + end + + def to_a + ary = [] + each(){|l| ary.push l} + ary + end + + def to_s + str = "" + each(){|l| str.concat l} + str + end + + def inspect + if @shell.debug.kind_of?(Integer) && @shell.debug > 2 + super + else + to_s + end + end + end +end diff --git a/lib/shell/process-controller.rb b/lib/shell/process-controller.rb new file mode 100644 index 0000000000..26fb1d9f08 --- /dev/null +++ b/lib/shell/process-controller.rb @@ -0,0 +1,258 @@ +# +# shell/process-controller.rb - +# $Release Version: 0.6.0 $ +# $Revision$ +# $Date$ +# by Keiju ISHITSUKA(Nihon Rational Software Co.,Ltd) +# +# -- +# +# +# + +require "mutex_m" +require "monitor" +require "sync" + +class Shell + class ProcessController + + @ProcessControllers = {} + @ProcessControllers.extend Mutex_m + + class<<self + + def process_controllers_exclusive + begin + @ProcessControllers.lock unless Thread.critical + yield + ensure + @ProcessControllers.unlock unless Thread.critical + end + end + + def activate(pc) + process_controllers_exclusive do + @ProcessControllers[pc] ||= 0 + @ProcessControllers[pc] += 1 + end + end + + def inactivate(pc) + process_controllers_exclusive do + if @ProcessControllers[pc] + if (@ProcessControllers[pc] -= 1) == 0 + @ProcessControllers.delete(pc) + end + end + end + end + + def each_active_object + process_controllers_exclusive do + for ref in @ProcessControllers.keys + yield ref + end + end + end + end + + def initialize(shell) + @shell = shell + @waiting_jobs = [] + @active_jobs = [] + @jobs_sync = Sync.new + + @job_monitor = Mutex.new + @job_condition = ConditionVariable.new + end + + def jobs + jobs = [] + @jobs_sync.synchronize(:SH) do + jobs.concat @waiting_jobs + jobs.concat @active_jobs + end + jobs + end + + def active_jobs + @active_jobs + end + + def waiting_jobs + @waiting_jobs + end + + def jobs_exist? + @jobs_sync.synchronize(:SH) do + @active_jobs.empty? or @waiting_jobs.empty? + end + end + + def active_jobs_exist? + @jobs_sync.synchronize(:SH) do + @active_jobs.empty? + end + end + + def waiting_jobs_exist? + @jobs_sync.synchronize(:SH) do + @waiting_jobs.empty? + end + end + + # schedule a command + def add_schedule(command) + @jobs_sync.synchronize(:EX) do + ProcessController.activate(self) + if @active_jobs.empty? + start_job command + else + @waiting_jobs.push(command) + end + end + end + + # start a job + def start_job(command = nil) + @jobs_sync.synchronize(:EX) do + if command + return if command.active? + @waiting_jobs.delete command + else + command = @waiting_jobs.shift + return unless command + end + @active_jobs.push command + command.start + + # start all jobs that input from the job + for job in @waiting_jobs + start_job(job) if job.input == command + end + end + end + + def waiting_job?(job) + @jobs_sync.synchronize(:SH) do + @waiting_jobs.include?(job) + end + end + + def active_job?(job) + @jobs_sync.synchronize(:SH) do + @active_jobs.include?(job) + end + end + + # terminate a job + def terminate_job(command) + @jobs_sync.synchronize(:EX) do + @active_jobs.delete command + ProcessController.inactivate(self) + if @active_jobs.empty? + start_job + end + end + end + + # kill a job + def kill_job(sig, command) + @jobs_sync.synchronize(:SH) do + if @waiting_jobs.delete command + ProcessController.inactivate(self) + return + elsif @active_jobs.include?(command) + begin + r = command.kill sig + ProcessController.inactivate(self) + rescue + print "Shell: Warn: $!\n" if @shell.verbose? + return nil + end + @active_jobs.delete command + r + end + end + end + + # wait for all jobs to terminate + def wait_all_jobs_execution + @job_monitor.synchronize do + begin + while !jobs.empty? + @job_condition.wait(@job_monitor) + end + ensure + redo unless jobs.empty? + end + end + end + + # simple fork + def sfork(command, &block) + pipe_me_in, pipe_peer_out = IO.pipe + pipe_peer_in, pipe_me_out = IO.pipe + Thread.critical = true + + STDOUT.flush + ProcessController.each_active_object do |pc| + for jobs in pc.active_jobs + jobs.flush + end + end + + pid = fork { + Thread.critical = true + + Thread.list.each do |th| + th.kill unless [Thread.main, Thread.current].include?(th) + end + + STDIN.reopen(pipe_peer_in) + STDOUT.reopen(pipe_peer_out) + + ObjectSpace.each_object(IO) do |io| + if ![STDIN, STDOUT, STDERR].include?(io) + io.close unless io.closed? + end + end + yield + } + + pipe_peer_in.close + pipe_peer_out.close + command.notify "job(%name:##{pid}) start", @shell.debug? + Thread.critical = false + + th = Thread.start { + Thread.critical = true + begin + _pid = nil + command.notify("job(%id) start to waiting finish.", @shell.debug?) + Thread.critical = false + _pid = Process.waitpid(pid, nil) + rescue Errno::ECHILD + command.notify "warn: job(%id) was done already waitipd." + _pid = true + ensure + # when the process ends, wait until the command termintes + if _pid + else + command.notify("notice: Process finishing...", + "wait for Job[%id] to finish.", + "You can use Shell#transact or Shell#check_point for more safe execution.") + redo + end + Thread.exclusive do + terminate_job(command) + @job_condition.signal + command.notify "job(%id) finish.", @shell.debug? + end + end + } + return pid, pipe_me_in, pipe_me_out + end + end +end diff --git a/lib/shell/system-command.rb b/lib/shell/system-command.rb new file mode 100644 index 0000000000..c22b9ac0a4 --- /dev/null +++ b/lib/shell/system-command.rb @@ -0,0 +1,168 @@ +# +# shell/system-command.rb - +# $Release Version: 0.6.0 $ +# $Revision$ +# $Date$ +# by Keiju ISHITSUKA(Nihon Rational Software Co.,Ltd) +# +# -- +# +# +# + +require "shell/filter" + +class Shell + class SystemCommand < Filter + def initialize(sh, command, *opts) + if t = opts.find{|opt| !opt.kind_of?(String) && opt.type} + Shell.Fail TypeError, t.type, "String" + end + super(sh) + @command = command + @opts = opts + + @input_queue = Queue.new + @pid = nil + + sh.process_controller.add_schedule(self) + end + + attr_reader :command + alias name command + + def wait? + @shell.process_controller.waiting_job?(self) + end + + def active? + @shell.process_controller.active_job?(self) + end + + def input=(inp) + super + if active? + start_export + end + end + + def start + @pid, @pipe_in, @pipe_out = @shell.process_controller.sfork(self) { + Dir.chdir @shell.pwd + exec(@command, *@opts) + } + if @input + start_export + end + start_import + end + + def flush + @pipe_out.flush if @pipe_out and !@pipe_out.closed? + end + + def terminate + begin + @pipe_in.close + rescue IOError + end + begin + @pipe_out.close + rescue IOError + end + end + + def kill(sig) + if @pid + Process.kill(sig, @pid) + end + end + + + def start_import +# Thread.critical = true + notify "Job(%id) start imp-pipe.", @shell.debug? + rs = @shell.record_separator unless rs + _eop = true +# Thread.critical = false + th = Thread.start { + Thread.critical = true + begin + Thread.critical = false + while l = @pipe_in.gets + @input_queue.push l + end + _eop = false + rescue Errno::EPIPE + _eop = false + ensure + if _eop + notify("warn: Process finishing...", + "wait for Job[%id] to finish pipe importing.", + "You can use Shell#transact or Shell#check_point for more safe execution.") +# Tracer.on + Thread.current.run + redo + end + Thread.exclusive do + notify "job(%id}) close imp-pipe.", @shell.debug? + @input_queue.push :EOF + @pipe_in.close + end + end + } + end + + def start_export + notify "job(%id) start exp-pipe.", @shell.debug? + _eop = true + th = Thread.start{ + Thread.critical = true + begin + Thread.critical = false + @input.each{|l| @pipe_out.print l} + _eop = false + rescue Errno::EPIPE + _eop = false + ensure + if _eop + notify("shell: warn: Process finishing...", + "wait for Job(%id) to finish pipe exporting.", + "You can use Shell#transact or Shell#check_point for more safe execution.") +# Tracer.on + redo + end + Thread.exclusive do + notify "job(%id) close exp-pipe.", @shell.debug? + @pipe_out.close + end + end + } + end + + alias super_each each + def each(rs = nil) + while (l = @input_queue.pop) != :EOF + yield l + end + end + + # ex) + # if you wish to output: + # "shell: job(#{@command}:#{@pid}) close pipe-out." + # then + # mes: "job(%id) close pipe-out." + # yorn: Boolean(@shell.debug? or @shell.verbose?) + def notify(*opts, &block) + Thread.exclusive do + @shell.notify(*opts) {|mes| + yield mes if iterator? + + mes.gsub!("%id", "#{@command}:##{@pid}") + mes.gsub!("%name", "#{@command}") + mes.gsub!("%pid", "#{@pid}") + } + end + end + end +end diff --git a/lib/shell/version.rb b/lib/shell/version.rb new file mode 100644 index 0000000000..6694c804d8 --- /dev/null +++ b/lib/shell/version.rb @@ -0,0 +1,16 @@ +# +# version.rb - shell version definition file +# $Release Version: 0.6.0$ +# $Revision$ +# $Date$ +# by Keiju ISHITSUKA(Nihon Rational Software Co.,Ltd) +# +# -- +# +# +# + +class Shell + @RELEASE_VERSION = "0.6.0" + @LAST_UPDATE_DATE = "01/03/19" +end diff --git a/lib/thread.rb b/lib/thread.rb index d4b6ad6ec1..0537c78650 100644 --- a/lib/thread.rb +++ b/lib/thread.rb @@ -74,7 +74,10 @@ class Mutex retry end Thread.critical = false - t.run if t + begin + t.run if t + rescue ThreadError + end self end @@ -160,7 +163,10 @@ class Queue ensure Thread.critical = false end - t.run if t + begin + t.run if t + rescue ThreadError + end end def enq(obj) push(obj) @@ -170,7 +176,7 @@ class Queue Thread.critical = true begin loop do - if @que.length == 0 + if @que.empty? if non_block raise ThreadError, "queue empty" end @@ -184,13 +190,11 @@ class Queue Thread.critical = false end end - def shift(non_block=false) - pop(non_block=false) - end - alias deq shift + alias shift pop + alias deq pop def empty? - @que.length == 0 + @que.empty? end def clear @@ -223,11 +227,11 @@ class SizedQueue<Queue def max=(max) Thread.critical = true - if max >= @max + if max <= @max @max = max Thread.critical = false else - diff = max - @max + diff = @max - max @max = max Thread.critical = false diff.times do @@ -253,6 +257,7 @@ class SizedQueue<Queue end def pop(*args) + retval = super Thread.critical = true if @que.length < @max begin @@ -263,9 +268,12 @@ class SizedQueue<Queue ensure Thread.critical = false end - t.run if t + begin + t.run if t + rescue ThreadError + end end - super + retval end def num_waiting |