From 073cc52dcc5f0945e56877c703688517f58c6a65 Mon Sep 17 00:00:00 2001 From: manga_osyo Date: Sun, 23 Jun 2019 13:29:09 +0900 Subject: [PATCH] Add `class Reline::History` and test. --- lib/reline.rb | 62 +--------- lib/reline/history.rb | 60 ++++++++++ test/test_history.rb | 273 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 335 insertions(+), 60 deletions(-) create mode 100644 lib/reline/history.rb create mode 100644 test/test_history.rb diff --git a/lib/reline.rb b/lib/reline.rb index bf8967c561..5bb47d5d3f 100644 --- a/lib/reline.rb +++ b/lib/reline.rb @@ -5,6 +5,7 @@ require 'reline/config' require 'reline/key_actor' require 'reline/key_stroke' require 'reline/line_editor' +require 'reline/history' module Reline Key = Struct.new('Key', :char, :combined_char, :with_meta) @@ -26,66 +27,7 @@ module Reline @@line_editor = Reline::LineEditor.new(@@config) @@ambiguous_width = nil - HISTORY = Class.new(Array) { - def initialize(config) - @config = config - end - - def to_s - 'HISTORY' - end - - def delete_at(index) - index = check_index(index) - super(index) - end - - def [](index) - index = check_index(index) - super(index) - end - - def []=(index, val) - index = check_index(index) - super(index, String.new(val, encoding: Encoding::default_external)) - end - - def concat(*val) - val.each do |v| - push(*v) - end - end - - def push(*val) - diff = size + val.size - @config.history_size - if diff > 0 - if diff <= size - shift(diff) - else - diff -= size - clear - val.shift(diff) - end - end - super(*(val.map{ |v| String.new(v, encoding: Encoding::default_external) })) - end - - def <<(val) - shift if size + 1 > @config.history_size - super(String.new(val, encoding: Encoding::default_external)) - end - - private def check_index(index) - index += size if index < 0 - raise RangeError.new("index=<#{index}>") if index < -@config.history_size or @config.history_size < index - raise IndexError.new("index=<#{index}>") if index < 0 or size <= index - index - end - - private def set_config(config) - @config = config - end - }.new(@@config) + HISTORY = History.new(@@config) @@completion_append_character = nil def self.completion_append_character diff --git a/lib/reline/history.rb b/lib/reline/history.rb new file mode 100644 index 0000000000..d988230941 --- /dev/null +++ b/lib/reline/history.rb @@ -0,0 +1,60 @@ +class Reline::History < Array + def initialize(config) + @config = config + end + + def to_s + 'HISTORY' + end + + def delete_at(index) + index = check_index(index) + super(index) + end + + def [](index) + index = check_index(index) + super(index) + end + + def []=(index, val) + index = check_index(index) + super(index, String.new(val, encoding: Encoding::default_external)) + end + + def concat(*val) + val.each do |v| + push(*v) + end + end + + def push(*val) + diff = size + val.size - @config.history_size + if diff > 0 + if diff <= size + shift(diff) + else + diff -= size + clear + val.shift(diff) + end + end + super(*(val.map{ |v| String.new(v, encoding: Encoding::default_external) })) + end + + def <<(val) + shift if size + 1 > @config.history_size + super(String.new(val, encoding: Encoding::default_external)) + end + + private def check_index(index) + index += size if index < 0 + raise RangeError.new("index=<#{index}>") if index < -@config.history_size or @config.history_size < index + raise IndexError.new("index=<#{index}>") if index < 0 or size <= index + index + end + + private def set_config(config) + @config = config + end +end diff --git a/test/test_history.rb b/test/test_history.rb new file mode 100644 index 0000000000..b38d9a3433 --- /dev/null +++ b/test/test_history.rb @@ -0,0 +1,273 @@ +require_relative 'helper' +require "reline/history" + +class Reline::KeyStroke::Test < Reline::TestCase + def test_ancestors + assert_equal(Reline::History.ancestors.include?(Array), true) + end + + def test_to_s + history = history_new + expected = "HISTORY" + assert_equal(expected, history.to_s) + end + + def test_get + history, lines = lines = history_new_and_push_history(5) + lines.each_with_index do |s, i| + assert_external_string_equal(s, history[i]) + end + end + + def test_get__negative + history, lines = lines = history_new_and_push_history(5) + (1..5).each do |i| + assert_equal(lines[-i], history[-i]) + end + end + + def test_get__out_of_range + history, _ = history_new_and_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 + history, _ = history_new_and_push_history(5) + 5.times do |i| + expected = "set: #{i}" + history[i] = expected + assert_external_string_equal(expected, history[i]) + end + rescue NotImplementedError + end + end + + def test_set__out_of_range + history = history_new + assert_raise(IndexError, NotImplementedError, "index=<0>") do + history[0] = "set: 0" + end + + history, _ = history_new_and_push_history(5) + invalid_indexes = [5, 6, 100, -6, -7, -100] + invalid_indexes.each do |i| + assert_raise(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 + history = history_new + 5.times do |i| + s = i.to_s + assert_equal(history, history.push(s)) + assert_external_string_equal(s, history[i]) + end + assert_equal(5, history.length) + end + + def test_push__operator + history = history_new + 5.times do |i| + s = i.to_s + assert_equal(history, history << s) + assert_external_string_equal(s, history[i]) + end + assert_equal(5, history.length) + end + + def test_push__plural + history = history_new + assert_equal(history, history.push("0", "1", "2", "3", "4")) + (0..4).each do |i| + assert_external_string_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_external_string_equal(i.to_s, history[i]) + end + assert_equal(10, history.length) + end + + def test_pop + history = history_new + begin + assert_equal(nil, history.pop) + + history, lines = lines = history_new_and_push_history(5) + (1..5).each do |i| + assert_external_string_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 + history = history_new + begin + assert_equal(nil, history.shift) + + history, lines = lines = history_new_and_push_history(5) + (0..4).each do |i| + assert_external_string_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 = history_new + e = history.each do |s| + assert(false) # not reachable + end + assert_equal(history, e) + history, lines = lines = history_new_and_push_history(5) + i = 0 + e = history.each do |s| + assert_external_string_equal(history[i], s) + assert_external_string_equal(lines[i], s) + i += 1 + end + assert_equal(history, e) + end + + def test_each__enumerator + history = history_new + e = history.each + assert_instance_of(Enumerator, e) + end + + def test_length + history = history_new + assert_equal(0, history.length) + push_history(history, 1) + assert_equal(1, history.length) + push_history(history, 4) + assert_equal(5, history.length) + history.clear + assert_equal(0, history.length) + end + + def test_empty_p + history = history_new + 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 + history, lines = lines = history_new_and_push_history(5) + (0..4).each do |i| + assert_external_string_equal(lines[i], history.delete_at(0)) + end + assert(history.empty?) + + history, lines = lines = history_new_and_push_history(5) + (1..5).each do |i| + assert_external_string_equal(lines[lines.length - i], history.delete_at(-1)) + end + assert(history.empty?) + + history, lines = lines = history_new_and_push_history(5) + assert_external_string_equal(lines[0], history.delete_at(0)) + assert_external_string_equal(lines[4], history.delete_at(3)) + assert_external_string_equal(lines[1], history.delete_at(0)) + assert_external_string_equal(lines[3], history.delete_at(1)) + assert_external_string_equal(lines[2], history.delete_at(0)) + assert(history.empty?) + rescue NotImplementedError + end + end + + def test_delete_at__out_of_range + history = history_new + assert_raise(IndexError, NotImplementedError, "index=<0>") do + history.delete_at(0) + end + + history, _ = history_new_and_push_history(5) + invalid_indexes = [5, 6, 100, -6, -7, -100] + invalid_indexes.each do |i| + assert_raise(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_raise(RangeError, NotImplementedError, "index=<#{i}>") do + history.delete_at(i) + end + end + end + + private + + def history_new(history_size: 10) + Reline::History.new(Struct.new(:history_size).new(history_size)) + end + + def push_history(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 history, lines + end + + def history_new_and_push_history(num) + history = history_new(history_size: 100) + return push_history(history, num) + end + + def assert_external_string_equal(expected, actual) + assert_equal(expected, actual) + assert_equal(get_default_internal_encoding, actual.encoding) + end + + def get_default_internal_encoding + return Encoding.default_internal || Encoding.find("locale") + end +end