新井です。

In message "[ruby-dev:14531] Re: restore terminal mode even if readlineinterrupted."
  on Mon, 20 Aug 2001 06:16:30 +0900,
  Koji Arai <JCA02266 / nifty.ne.jp> wrote:
> 新井です。

> > rl_cleanup_after_signal() を呼ぶと、signal handler の復元ま
> > でやってるのですが、これでいいのか、ちゃんと検証しきれてませ
> > ん。(この辺がやる気になりきれてない - -;)
> 
> あら、自分が追加したコードがバージョン依存してました。
> (rl_cleanup_after_signal() は、readline 4.0 から)
> 
> また、考え直します。

結論として、
  SIGINT, SIGTERM, SIGQUIT, SIGALRM, SIGTSTP, SIGTTIN, SIGTTOU, SIGWINCH
以外のシグナルを受けたときにシグナルハンドラが戻らないから戻
しておいた方がよい。けど、rl_cleanup_after_signal を持たない
ような古いライブラリに対して一生懸命になる必要はないだろうと
判断しました。ですので、rl_cleanup_after_signal()があればそ
れを呼び、他の古いバージョンでは端末状態だけ復帰させるように
しました。

その他

o Readline.readline や Readline::HISTORY[] から得た文字列は常
  に汚染させるようにしました(HIST_ENTRY の data メンバを使え
  ば、HISTORY[]= でセットした文字列の汚染状態を引き継げそう
  でしたが、pending)

o 変数 rl_completion_append_character がライブラリになければ
  NotImplementedError に (今さら readline 2.0 ってのもないだろうけど)

o Readline::HISTORY[] 等でメモリリークがあった

o Readline::HISTORY[] 等で負のインデックスを受け付けるように。

o Readline::HISTORY.size を追加

o Readline::VERSION を追加

o 変数 rl_readline_name に "Ruby" を設定

o テスト(Readline::HISTORYだけ)を追加 (最後に添付)

しました。


英語は直してください(_ _) Sun Aug 26 20:26:40 2001 Koji Arai <JCA02266 / nifty.ne.jp> * ext/readline/readline.c: restore terminal mode even if readline() interrupted. * ext/readline/readline.c: returned string need to be tainted. * ext/readline/readline.c: fixed memory leak. * ext/readline/readline.c: allow negative index. * ext/readline/readline.c: added Readline::HISTORY.size same as Readline::HISTORY.length * ext/readline/readline.c: allow conditional parsing of the ~/.inputrc file by `$if Ruby'. * ext/readline/extconf.rb: check whether the libreadline has the variable `rl_completion_append_character' (this feature was implemented from GNU readline 2.1).
Index: extconf.rb =================================================================== RCS file: /usr/local/cvsup/ruby/ruby/ext/readline/extconf.rb,v retrieving revision 1.6 diff -u -p -r1.6 extconf.rb --- extconf.rb 2001/05/06 15:03:25 1.6 +++ extconf.rb 2001/08/26 10:55:16 @@ -12,5 +12,16 @@ if have_header("readline/readline.h") an if have_func("rl_filename_completion_function") $CFLAGS += "-DREADLINE_42_OR_LATER" end + if have_func("rl_cleanup_after_signal") + $CFLAGS += "-DREADLINE_40_OR_LATER" + end + if try_link(<<EOF, $libs) +#include <stdio.h> +#include <readline/readline.h> +main() {rl_completion_append_character = 1;} +EOF + # this feature is implemented in readline-2.1 or later. + $CFLAGS += " -DREADLINE_21_OR_LATER" + end create_makefile("readline") end Index: readline.c =================================================================== RCS file: /usr/local/cvsup/ruby/ruby/ext/readline/readline.c,v retrieving revision 1.6 diff -u -p -r1.6 readline.c --- readline.c 2001/06/23 09:30:42 1.6 +++ readline.c 2001/08/26 10:57:53 @@ -38,16 +38,32 @@ readline_readline(argc, argv, self) VALUE tmp, add_hist, result; char *prompt = NULL; char *buff; + int status; if (rb_scan_args(argc, argv, "02", &tmp, &add_hist) > 0) { prompt = StringValuePtr(tmp); } - buff = readline(prompt); + + buff = (char*)rb_protect((VALUE(*)_((VALUE)))readline, (VALUE)prompt, + &status); + if (status) { +#if READLINE_40_OR_LATER + /* restore terminal mode and signal handler*/ + rl_cleanup_after_signal(); +#elif READLINE_21_OR_LATER + /* restore terminal mode */ + (*rl_deprep_term_function)(); +#else + rl_deprep_terminal(); +#endif + rb_jump_tag(status); + } + if (RTEST(add_hist) && buff) { add_history(buff); } if (buff) - result = rb_str_new2(buff); + result = rb_tainted_str_new2(buff); else result = Qnil; if (buff) free(buff); @@ -102,7 +118,7 @@ readline_attempted_completion_function(t return NULL; rl_attempted_completion_over = 1; case_fold = RTEST(rb_iv_get(mReadline, COMPLETION_CASE_FOLD)); - ary = rb_funcall(proc, rb_intern("call"), 1, rb_str_new2(text)); + ary = rb_funcall(proc, rb_intern("call"), 1, rb_tainted_str_new2(text)); if (TYPE(ary) != T_ARRAY) ary = rb_Array(ary); matches = RARRAY(ary)->len; @@ -171,6 +187,7 @@ static VALUE readline_s_set_completion_append_character(self, str) VALUE self, str; { +#ifdef READLINE_21_OR_LATER if (NIL_P(str)) { rl_completion_append_character = '\0'; } else { @@ -180,12 +197,16 @@ readline_s_set_completion_append_charact } return self; +#else + rb_notimplement(); +#endif /* READLINE_21_OR_LATER */ } static VALUE readline_s_get_completion_append_character(self) VALUE self; { +#ifdef READLINE_21_OR_LATER VALUE str; if (rl_completion_append_character == '\0') @@ -195,9 +216,29 @@ readline_s_get_completion_append_charact RSTRING(str)->ptr[0] = rl_completion_append_character; return str; +#else + rb_notimplement(); +#endif /* READLINE_21_OR_LATER */ } static VALUE +rb_remove_history(index) + int index; +{ + HIST_ENTRY *entry; + VALUE val; + + entry = remove_history(index); + if (entry) { + val = rb_tainted_str_new2(entry->line); + free(entry->line); + free(entry); + return val; + } + return Qnil; +} + +static VALUE hist_to_s(self) VALUE self; { @@ -214,10 +255,13 @@ hist_get(self, index) state = history_get_history_state(); i = NUM2INT(index); + if (i < 0) { + i += state->length; + } if (i < 0 || i > state->length - 1) { rb_raise(rb_eIndexError, "Invalid index"); } - return rb_str_new2(state->entries[i]->line); + return rb_tainted_str_new2(state->entries[i]->line); } static VALUE @@ -232,6 +276,9 @@ hist_set(self, index, str) state = history_get_history_state(); i = NUM2INT(index); + if (i < 0) { + i += state->length; + } if (i < 0 || i > state->length - 1) { rb_raise(rb_eIndexError, "Invalid index"); } @@ -268,12 +315,10 @@ hist_pop(self) VALUE self; { HISTORY_STATE *state; - HIST_ENTRY *entry; state = history_get_history_state(); if (state->length > 0) { - entry = remove_history(state->length - 1); - return rb_str_new2(entry->line); + return rb_remove_history(state->length - 1); } else { return Qnil; } @@ -284,12 +329,10 @@ hist_shift(self) VALUE self; { HISTORY_STATE *state; - HIST_ENTRY *entry; state = history_get_history_state(); if (state->length > 0) { - entry = remove_history(0); - return rb_str_new2(entry->line); + return rb_remove_history(0); } else { return Qnil; } @@ -304,7 +347,7 @@ hist_each(self) state = history_get_history_state(); for (i = 0; i < state->length; i++) { - rb_yield(rb_str_new2(state->entries[i]->line)); + rb_yield(rb_tainted_str_new2(state->entries[i]->line)); } return self; } @@ -338,16 +381,16 @@ hist_delete_at(self, index) VALUE index; { HISTORY_STATE *state; - HIST_ENTRY *entry; int i; state = history_get_history_state(); i = NUM2INT(index); + if (i < 0) + i += state->length; if (i < 0 || i > state->length - 1) { rb_raise(rb_eIndexError, "Invalid index"); } - entry = remove_history(NUM2INT(index)); - return rb_str_new2(entry->line); + return rb_remove_history(i); } static VALUE @@ -364,7 +407,7 @@ filename_completion_proc_call(self, str) if (matches) { result = rb_ary_new(); for (i = 0; matches[i]; i++) { - rb_ary_push(result, rb_str_new2(matches[i])); + rb_ary_push(result, rb_tainted_str_new2(matches[i])); free(matches[i]); } free(matches); @@ -391,7 +434,7 @@ username_completion_proc_call(self, str) if (matches) { result = rb_ary_new(); for (i = 0; matches[i]; i++) { - rb_ary_push(result, rb_str_new2(matches[i])); + rb_ary_push(result, rb_tainted_str_new2(matches[i])); free(matches[i]); } free(matches); @@ -409,6 +452,9 @@ Init_readline() { VALUE histary, fcomp, ucomp; + /* Allow conditional parsing of the ~/.inputrc file. */ + rl_readline_name = "Ruby"; + using_history(); mReadline = rb_define_module("Readline"); @@ -442,6 +488,8 @@ Init_readline() rb_define_singleton_method(histary,"shift", hist_shift, 0); rb_define_singleton_method(histary,"each", hist_each, 0); rb_define_singleton_method(histary,"length", hist_length, 0); + rb_define_singleton_method(histary,"size", hist_length, 0); + rb_define_singleton_method(histary,"empty?", hist_empty_p, 0); rb_define_singleton_method(histary,"delete_at", hist_delete_at, 1); rb_define_const(mReadline, "HISTORY", histary); @@ -455,6 +503,12 @@ Init_readline() rb_define_singleton_method(ucomp, "call", username_completion_proc_call, 1); rb_define_const(mReadline, "USERNAME_COMPLETION_PROC", ucomp); +#if READLINE_21_OR_LATER + rb_define_const(mReadline, "VERSION", rb_str_new2(rl_library_version)); +#else + rb_define_const(mReadline, "VERSION", + rb_str_new2("2.0 or before version")); +#endif rl_attempted_completion_function = (CPPFunction *) readline_attempted_completion_function;
require 'runit/testcase' require 'runit/cui/testrunner' if $".grep(/\breadline.so$/).empty? begin require './readline' rescue LoadError require 'readline' end end class TestReadline < RUNIT::TestCase def setup Readline::HISTORY.push(*%w|foo bar baz|) assert_equals('foo', Readline::HISTORY[0]) assert_equals('bar', Readline::HISTORY[1]) assert_equals('baz', Readline::HISTORY[2]) end def teardown Readline::HISTORY.shift until Readline::HISTORY.empty? end def test_version STDERR.puts Readline::VERSION.inspect if defined? Readline::VERSION end def test_s_readline # XXX: this test cause SEGV on the GNU Readline Library version 2.0 IO.popen('-', 'w') {|io| if io io.puts 'foo' else assert_equals("foo", Readline::readline) end } end def test_s_completion_proc_eq end def test_s_completion_proc end def test_s_completion_case_fold_eq end def test_s_completion_case_fold end def test_s_vi_editing_mode end def test_s_emacs_editing_mode end def test_s_completion_appear_character_eq end def test_s_completion_appear_character end def test_s_history_to_s assert_equals("HISTORY", Readline::HISTORY.to_s) assert_equals("HISTORY", Readline::HISTORY.inspect) end def test_s_history_aref assert_equals(Readline::HISTORY[0], "foo") assert_equals(Readline::HISTORY[1], "bar") assert_equals(Readline::HISTORY[2], "baz") assert_exception(IndexError) { Readline::HISTORY[4] } assert_exception(IndexError) { Readline::HISTORY[-4] } assert_equals(Readline::HISTORY[-3], "foo") assert_equals(Readline::HISTORY[-2], "bar") assert_equals(Readline::HISTORY[-1], "baz") assert_equals(3, Readline::HISTORY.size) end def test_s_history_aset assert_equals("FOO", Readline::HISTORY[0] = "FOO") assert_equals("FOO", Readline::HISTORY[0]) assert_equals("BAR", Readline::HISTORY[1] = "BAR") assert_equals("BAR", Readline::HISTORY[1]) assert_equals("BAZ", Readline::HISTORY[2] = "BAZ") assert_equals("BAZ", Readline::HISTORY[2]) assert_equals(3, Readline::HISTORY.size) end def test_s_history_add assert_equals(Readline::HISTORY, Readline::HISTORY << "FOO") assert_equals("FOO", Readline::HISTORY[-1]) assert_equals(Readline::HISTORY, Readline::HISTORY << "BAR" << "BAZ") assert_equals("BAR", Readline::HISTORY[-2]) assert_equals("BAZ", Readline::HISTORY[-1]) assert_equals(6, Readline::HISTORY.size) end def test_s_history_push assert_equals(Readline::HISTORY, Readline::HISTORY.push("FOO")) assert_equals("FOO", Readline::HISTORY[-1]) assert_equals(Readline::HISTORY, Readline::HISTORY.push("BAR", "BAZ")) assert_equals("BAR", Readline::HISTORY[-2]) assert_equals("BAZ", Readline::HISTORY[-1]) assert_equals(6, Readline::HISTORY.size) end def test_s_history_pop assert_equals("baz", Readline::HISTORY.pop) assert_equals("bar", Readline::HISTORY.pop) assert_equals("foo", Readline::HISTORY.pop) assert_nil(Readline::HISTORY.pop) assert_equals(0, Readline::HISTORY.size) end def test_s_history_shift assert_equals("foo", Readline::HISTORY.shift) assert_equals("bar", Readline::HISTORY.shift) assert_equals("baz", Readline::HISTORY.shift) assert_nil(Readline::HISTORY.shift) assert_equals(0, Readline::HISTORY.size) end def test_s_history_each ary = %w(foo bar baz) Readline::HISTORY.each {|v| assert_equals(ary.shift, v) } assert_equals(true, ary.empty?) assert_equals(3, Readline::HISTORY.size) end def test_s_history_length assert_equals(3, Readline::HISTORY.length) assert_equals(3, Readline::HISTORY.size) end def test_s_history_empty_p assert_equals("foo", Readline::HISTORY.shift) assert_equals("bar", Readline::HISTORY.shift) assert_equals("baz", Readline::HISTORY.shift) assert_equals(0, Readline::HISTORY.size) assert_equals(true, Readline::HISTORY.empty?) end def test_s_history_delete_at assert_equals("foo", Readline::HISTORY.delete_at(0)) assert_equals("baz", Readline::HISTORY.delete_at(1)) assert_equals("bar", Readline::HISTORY.delete_at(-1)) assert_exception(IndexError) { Readline::HISTORY.delete_at(-1) } assert_equals(0, Readline::HISTORY.size) end def test_hist_enumerable assert_equals(%w|foo bar baz|, Readline::HISTORY.to_a) end end if $0 == __FILE__ if ARGV.size == 0 suite = TestReadline.suite else suite = RUNIT::TestSuite.new ARGV.each do |testmethod| suite.add_test(TestReadline.new(testmethod)) end end RUNIT::CUI::TestRunner.run(suite) end