diff --git a/ChangeLog b/ChangeLog index bb29125bfd..d1b08817a2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,24 @@ +Fri Aug 1 23:49:44 2008 TAKAO Kouji + + * ext/readline/extconf.rb: checked to have clear_history in + readline library. + * ext/readline/readline.c (hist_get, hist_each, Init_readline): + The offset specified for the argument of history_get() might be + different in GNU Readline and libedit. If use libedit, it was + corrected that the computational method of the offset specified + for the argument of history_get() when the Readline module was + initialized was decided. + (hist_get, hist_set): If use libedit, accesses first an input + content in history when specifies the negative offset for the + argument of history_get() or replace_history_entry(). Then + checks the offset is negative in ruby. + (rb_remove_history): When compiling, it corrects it to warning + when libedit is used. + (hist_clear, Init_readline): added Readline::HISTORY.clear + method. [ruby-dev:35551] + * test/readline/test_readline_history.rb: added unit test for + Readline::HISTORY. + Fri Aug 1 23:26:45 2008 NARUSE, Yui * transcode.c (transcode_loop): undefined character is replaced with diff --git a/ext/readline/extconf.rb b/ext/readline/extconf.rb index cd500ceec8..84fb9b14a6 100644 --- a/ext/readline/extconf.rb +++ b/ext/readline/extconf.rb @@ -66,4 +66,5 @@ have_readline_func("rl_vi_editing_mode") have_readline_func("rl_emacs_editing_mode") have_readline_func("replace_history_entry") have_readline_func("remove_history") +have_readline_func("clear_history") create_makefile("readline") diff --git a/ext/readline/readline.c b/ext/readline/readline.c index 6481e4854f..4e9792e663 100644 --- a/ext/readline/readline.c +++ b/ext/readline/readline.c @@ -29,6 +29,8 @@ static VALUE mReadline; +#define EDIT_LINE_LIBRARY_VERSION "EditLine wrapper" + #define COMPLETION_PROC "completion_proc" #define COMPLETION_CASE_FOLD "completion_case_fold" static ID completion_proc, completion_case_fold; @@ -43,6 +45,8 @@ static ID completion_proc, completion_case_fold; # define rl_completion_matches completion_matches #endif +static int (*history_get_offset_func)(int); + static char **readline_attempted_completion_function(const char *text, int start, int end); @@ -505,10 +509,22 @@ hist_to_s(VALUE self) return rb_str_new2("HISTORY"); } +static int +history_get_offset_history_base(int offset) +{ + return history_base + offset; +} + +static int +history_get_offset_0(int offset) +{ + return offset; +} + static VALUE hist_get(VALUE self, VALUE index) { - HIST_ENTRY *entry; + HIST_ENTRY *entry = NULL; int i; rb_secure(4); @@ -516,7 +532,9 @@ hist_get(VALUE self, VALUE index) if (i < 0) { i += history_length; } - entry = history_get(history_base + i); + if (i >= 0) { + entry = history_get(history_get_offset_func(i)); + } if (entry == NULL) { rb_raise(rb_eIndexError, "invalid index"); } @@ -527,7 +545,7 @@ static VALUE hist_set(VALUE self, VALUE index, VALUE str) { #ifdef HAVE_REPLACE_HISTORY_ENTRY - HIST_ENTRY *entry; + HIST_ENTRY *entry = NULL; int i; rb_secure(4); @@ -536,7 +554,9 @@ hist_set(VALUE self, VALUE index, VALUE str) if (i < 0) { i += history_length; } - entry = replace_history_entry(i, RSTRING_PTR(str), NULL); + if (i >= 0) { + entry = replace_history_entry(i, RSTRING_PTR(str), NULL); + } if (entry == NULL) { rb_raise(rb_eIndexError, "invalid index"); } @@ -581,7 +601,7 @@ rb_remove_history(int index) entry = remove_history(index); if (entry) { val = rb_tainted_str_new2(entry->line); - free(entry->line); + free((void *) entry->line); free(entry); return val; } @@ -624,7 +644,7 @@ hist_each(VALUE self) rb_secure(4); for (i = 0; i < history_length; i++) { - entry = history_get(history_base + i); + entry = history_get(history_get_offset_func(i)); if (entry == NULL) break; rb_yield(rb_tainted_str_new2(entry->line)); @@ -661,6 +681,19 @@ hist_delete_at(VALUE self, VALUE index) return rb_remove_history(i); } +static VALUE +hist_clear(VALUE self) +{ +#ifdef HAVE_CLEAR_HISTORY + rb_secure(4); + clear_history(); + return self; +#else + rb_notimplement(); + return Qnil; /* not reached */ +#endif +} + static VALUE filename_completion_proc_call(VALUE self, VALUE str) { @@ -782,6 +815,7 @@ Init_readline() rb_define_singleton_method(history,"size", hist_length, 0); rb_define_singleton_method(history,"empty?", hist_empty_p, 0); rb_define_singleton_method(history,"delete_at", hist_delete_at, 1); + rb_define_singleton_method(history,"clear", hist_clear, 0); rb_define_const(mReadline, "HISTORY", history); fcomp = rb_obj_alloc(rb_cObject); @@ -793,8 +827,19 @@ Init_readline() rb_define_singleton_method(ucomp, "call", username_completion_proc_call, 1); rb_define_const(mReadline, "USERNAME_COMPLETION_PROC", ucomp); + history_get_offset_func = history_get_offset_history_base; #if defined HAVE_RL_LIBRARY_VERSION rb_define_const(mReadline, "VERSION", rb_str_new2(rl_library_version)); +#if defined HAVE_CLEAR_HISTORY + if (strncmp(rl_library_version, EDIT_LINE_LIBRARY_VERSION, + strlen(EDIT_LINE_LIBRARY_VERSION)) == 0) { + add_history("1"); + if (history_get(history_get_offset_func(0)) == NULL) { + history_get_offset_func = history_get_offset_0; + } + clear_history(); + } +#endif #else rb_define_const(mReadline, "VERSION", rb_str_new2("2.0 or prior version")); #endif diff --git a/test/readline/test_readline_history.rb b/test/readline/test_readline_history.rb new file mode 100644 index 0000000000..0235a4ec90 --- /dev/null +++ b/test/readline/test_readline_history.rb @@ -0,0 +1,313 @@ +begin + require "readline" +=begin + class << Readline::HISTORY + def []=(index, str) + raise NotImplementedError + end + + def pop + raise NotImplementedError + end + + def shift + raise NotImplementedError + end + + def delete_at(index) + raise NotImplementedError + end + end +=end + +=begin + class << Readline::HISTORY + def clear + raise NotImplementedError + end + end +=end +rescue LoadError +else + require "test/unit" +end + +class Readline::TestHistory < Test::Unit::TestCase + include Readline + + def setup + HISTORY.clear + end + + def test_safe_level_4 + method_args = + [ + ["[]", [0]], + ["[]=", [0, "s"]], + ["\<\<", ["s"]], + ["push", ["s"]], + ["pop", []], + ["shift", []], + ["length", []], + ["delete_at", [0]], + ["clear", []], + ] + method_args.each do |method_name, args| + assert_raises(SecurityError, NotImplementedError, + "method=<#{method_name}>") do + Thread.start { + $SAFE = 4 + HISTORY.send(method_name.to_sym, *args) + assert(true) + }.join + end + end + + assert_raises(SecurityError, NotImplementedError, + "method=") do + Thread.start { + $SAFE = 4 + HISTORY.each { |s| + assert(true) + } + }.join + end + end + + def test_to_s + assert_equal("HISTORY", HISTORY.to_s) + end + + def test_get + lines = push_history(5) + lines.each_with_index do |s, i| + assert_equal(s, HISTORY[i]) + end + end + + def test_get__negative + lines = push_history(5) + (1..5).each do |i| + assert_equal(lines[-i], HISTORY[-i]) + end + end + + def test_get__out_of_range + lines = push_history(5) + invalid_indexes = [5, 6, 100, -6, -7, -100] + invalid_indexes.each do |i| + assert_raise(IndexError, "i=<#{i}>") do + HISTORY[i] + end + end + + invalid_indexes = [100_000_000_000_000_000_000, + -100_000_000_000_000_000_000] + invalid_indexes.each do |i| + assert_raise(RangeError, "i=<#{i}>") do + HISTORY[i] + end + end + end + + def test_set + begin + lines = push_history(5) + 5.times do |i| + expected = "set: #{i}" + HISTORY[i] = expected + assert_equal(expected, HISTORY[i]) + end + rescue NotImplementedError + end + end + + def test_set__out_of_range + assert_raises(IndexError, NotImplementedError, "index=<0>") do + HISTORY[0] = "set: 0" + end + + lines = push_history(5) + invalid_indexes = [5, 6, 100, -6, -7, -100] + invalid_indexes.each do |i| + assert_raises(IndexError, NotImplementedError, "index=<#{i}>") do + HISTORY[i] = "set: #{i}" + end + end + + invalid_indexes = [100_000_000_000_000_000_000, + -100_000_000_000_000_000_000] + invalid_indexes.each do |i| + assert_raise(RangeError, NotImplementedError, "index=<#{i}>") do + HISTORY[i] = "set: #{i}" + end + end + end + + def test_push + 5.times do |i| + assert_equal(HISTORY, HISTORY.push(i.to_s)) + assert_equal(i.to_s, HISTORY[i]) + end + assert_equal(5, HISTORY.length) + end + + def test_push__operator + 5.times do |i| + assert_equal(HISTORY, HISTORY << i.to_s) + assert_equal(i.to_s, HISTORY[i]) + end + assert_equal(5, HISTORY.length) + end + + def test_push__plural + assert_equal(HISTORY, HISTORY.push("0", "1", "2", "3", "4")) + (0..4).each do |i| + assert_equal(i.to_s, HISTORY[i]) + end + assert_equal(5, HISTORY.length) + + assert_equal(HISTORY, HISTORY.push("5", "6", "7", "8", "9")) + (5..9).each do |i| + assert_equal(i.to_s, HISTORY[i]) + end + assert_equal(10, HISTORY.length) + end + + def test_pop + begin + assert_equal(nil, HISTORY.pop) + + lines = push_history(5) + (1..5).each do |i| + assert_equal(lines[-i], HISTORY.pop) + assert_equal(lines.length - i, HISTORY.length) + end + + assert_equal(nil, HISTORY.pop) + rescue NotImplementedError + end + end + + def test_shift + begin + assert_equal(nil, HISTORY.shift) + + lines = push_history(5) + (0..4).each do |i| + assert_equal(lines[i], HISTORY.shift) + assert_equal(lines.length - (i + 1), HISTORY.length) + end + + assert_equal(nil, HISTORY.shift) + rescue NotImplementedError + end + end + + def test_each + HISTORY.each do |s| + assert(false) # not reachable + end + lines = push_history(5) + i = 0 + HISTORY.each do |s| + assert_equal(HISTORY[i], s) + assert_equal(lines[i], s) + i += 1 + end + end + + def test_each__enumerator + e = HISTORY.each + assert_instance_of(Enumerable::Enumerator, e) + end + + def test_length + assert_equal(0, HISTORY.length) + push_history(1) + assert_equal(1, HISTORY.length) + push_history(4) + assert_equal(5, HISTORY.length) + HISTORY.clear + assert_equal(0, HISTORY.length) + end + + def test_empty_p + 2.times do + assert(HISTORY.empty?) + HISTORY.push("s") + assert_equal(false, HISTORY.empty?) + HISTORY.clear + assert(HISTORY.empty?) + end + end + + def test_delete_at + begin + lines = push_history(5) + (0..4).each do |i| + assert_equal(lines[i], HISTORY.delete_at(0)) + end + assert(HISTORY.empty?) + + lines = push_history(5) + (1..5).each do |i| + assert_equal(lines[lines.length - i], HISTORY.delete_at(-1)) + end + assert(HISTORY.empty?) + + lines = push_history(5) + assert_equal(lines[0], HISTORY.delete_at(0)) + assert_equal(lines[4], HISTORY.delete_at(3)) + assert_equal(lines[1], HISTORY.delete_at(0)) + assert_equal(lines[3], HISTORY.delete_at(1)) + assert_equal(lines[2], HISTORY.delete_at(0)) + assert(HISTORY.empty?) + rescue NotImplementedError + end + end + + def test_delete_at__out_of_range + assert_raises(IndexError, NotImplementedError, "index=<0>") do + HISTORY.delete_at(0) + end + + lines = push_history(5) + invalid_indexes = [5, 6, 100, -6, -7, -100] + invalid_indexes.each do |i| + assert_raises(IndexError, NotImplementedError, "index=<#{i}>") do + HISTORY.delete_at(i) + end + end + + invalid_indexes = [100_000_000_000_000_000_000, + -100_000_000_000_000_000_000] + invalid_indexes.each do |i| + assert_raises(RangeError, NotImplementedError, "index=<#{i}>") do + HISTORY.delete_at(i) + end + end + end + + private + + def push_history(num) + lines = [] + num.times do |i| + s = "a" + i.times do + s = s.succ + end + lines.push("#{i + 1}:#{s}") + end + HISTORY.push(*lines) + return lines + end +end if defined?(::Readline) && defined?(::Readline::HISTORY) && + ( + begin + Readline::HISTORY.clear + rescue NotImplementedError + false + end + )