mini_readline 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +207 -0
- data/Rakefile +40 -0
- data/lib/mini_readline.rb +28 -0
- data/lib/mini_readline/options.rb +19 -0
- data/lib/mini_readline/raw_term.rb +14 -0
- data/lib/mini_readline/raw_term/other.rb +67 -0
- data/lib/mini_readline/raw_term/other/map.rb +75 -0
- data/lib/mini_readline/raw_term/other/set_posn.rb +22 -0
- data/lib/mini_readline/raw_term/windows.rb +88 -0
- data/lib/mini_readline/raw_term/windows/map.rb +71 -0
- data/lib/mini_readline/raw_term/windows/set_posn.rb +40 -0
- data/lib/mini_readline/raw_term/windows/win_32_api.rb +47 -0
- data/lib/mini_readline/read_line.rb +65 -0
- data/lib/mini_readline/read_line/edit.rb +87 -0
- data/lib/mini_readline/read_line/edit/cancel.rb +15 -0
- data/lib/mini_readline/read_line/edit/delete_left.rb +21 -0
- data/lib/mini_readline/read_line/edit/delete_right.rb +19 -0
- data/lib/mini_readline/read_line/edit/enter.rb +14 -0
- data/lib/mini_readline/read_line/edit/go_end.rb +14 -0
- data/lib/mini_readline/read_line/edit/go_home.rb +14 -0
- data/lib/mini_readline/read_line/edit/go_left.rb +18 -0
- data/lib/mini_readline/read_line/edit/go_right.rb +18 -0
- data/lib/mini_readline/read_line/edit/insert_text.rb +18 -0
- data/lib/mini_readline/read_line/edit/next_history.rb +19 -0
- data/lib/mini_readline/read_line/edit/previous_history.rb +19 -0
- data/lib/mini_readline/read_line/edit/unmapped.rb +18 -0
- data/lib/mini_readline/read_line/edit_window.rb +72 -0
- data/lib/mini_readline/read_line/edit_window/sync_cursor.rb +15 -0
- data/lib/mini_readline/read_line/edit_window/sync_window.rb +46 -0
- data/lib/mini_readline/read_line/history.rb +61 -0
- data/lib/mini_readline/version.rb +4 -0
- data/mini_readline.gemspec +26 -0
- data/reek.txt +1 -0
- data/sire.rb +102 -0
- data/tests/mini_readline_tests.rb +55 -0
- metadata +110 -0
@@ -0,0 +1,22 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
#* windows/set_posn.rb - Set the column of the cursor.
|
4
|
+
module MiniReadline
|
5
|
+
|
6
|
+
#* windows/set_posn.rb - Set the column of the cursor.
|
7
|
+
class RawTerm
|
8
|
+
|
9
|
+
#Move the cursor using terminal primitives.
|
10
|
+
def set_posn(new_posn)
|
11
|
+
if new_posn == 0
|
12
|
+
put_string(CARRIAGE_RETURN)
|
13
|
+
elsif new_posn > @cursor_posn
|
14
|
+
print("\e[#{new_posn - @cursor_posn}C")
|
15
|
+
elsif new_posn < @cursor_posn
|
16
|
+
print("\e[#{@cursor_posn - new_posn}D")
|
17
|
+
end
|
18
|
+
|
19
|
+
@cursor_posn = new_posn
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require_relative 'windows/win_32_api'
|
4
|
+
require_relative 'windows/map'
|
5
|
+
require_relative 'windows/set_posn'
|
6
|
+
|
7
|
+
#* raw_term/windows.rb - Support for raw terminal access in windows systems.
|
8
|
+
module MiniReadline
|
9
|
+
|
10
|
+
#The detected platform is windows.
|
11
|
+
PLATFORM = :windows
|
12
|
+
|
13
|
+
#The class used to manipulate console i/o on a low level.
|
14
|
+
class RawTerm
|
15
|
+
|
16
|
+
#The sleep interval waiting for a key to be pressed.
|
17
|
+
WAIT_SLEEP = 0.02
|
18
|
+
|
19
|
+
#Carriage return
|
20
|
+
CARRIAGE_RETURN = "\x0D"
|
21
|
+
|
22
|
+
#The magic number for standard out.
|
23
|
+
STD_OUTPUT_HANDLE = -11
|
24
|
+
|
25
|
+
#Set up the Windows Raw Terminal.
|
26
|
+
def initialize
|
27
|
+
@_getch = Win32API.new("msvcrt", "_getch", [])
|
28
|
+
@_kbhit = Win32API.new("msvcrt", "_kbhit", [])
|
29
|
+
@_beep = Win32API.new("user32", "MessageBeep", ['L'])
|
30
|
+
|
31
|
+
@_set_cursor_posn = Win32API.new("kernel32",
|
32
|
+
"SetConsoleCursorPosition",
|
33
|
+
['L','L'])
|
34
|
+
|
35
|
+
@_get_screen_info = Win32API.new("kernel32",
|
36
|
+
"GetConsoleScreenBufferInfo",
|
37
|
+
['L','P'])
|
38
|
+
|
39
|
+
@_get_handle = Win32API.new("kernel32", "GetStdHandle", ['L'])
|
40
|
+
end
|
41
|
+
|
42
|
+
#Output a string
|
43
|
+
def put_string(str)
|
44
|
+
scan_string(str) unless @_out_handle
|
45
|
+
print(str)
|
46
|
+
end
|
47
|
+
|
48
|
+
#Home the cursor and start at a known state.
|
49
|
+
def initialize_parms
|
50
|
+
@_out_handle = @_get_handle.call(STD_OUTPUT_HANDLE)
|
51
|
+
put_string CARRIAGE_RETURN
|
52
|
+
end
|
53
|
+
|
54
|
+
#Start on a new line.
|
55
|
+
def put_new_line
|
56
|
+
print("\n")
|
57
|
+
end
|
58
|
+
|
59
|
+
#Sound a beep
|
60
|
+
def beep
|
61
|
+
@_beep.call(0)
|
62
|
+
end
|
63
|
+
|
64
|
+
#Wait for a key to be pressed.
|
65
|
+
def wait_for_key
|
66
|
+
while (@_kbhit.call == 0)
|
67
|
+
sleep(WAIT_SLEEP)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
#Get a uncooked character keystroke.
|
72
|
+
def get_raw_char
|
73
|
+
wait_for_key
|
74
|
+
@_getch.call.chr
|
75
|
+
end
|
76
|
+
|
77
|
+
#Determine the affect of a string on the cursor.
|
78
|
+
def scan_string(str)
|
79
|
+
str.chars.each do |char|
|
80
|
+
if char == CARRIAGE_RETURN
|
81
|
+
@cursor_posn = 0
|
82
|
+
else
|
83
|
+
@cursor_posn += 1
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
#* windows/map.rb - Character mapping for windows systems.
|
4
|
+
module MiniReadline
|
5
|
+
|
6
|
+
#* windows/map.rb - Character mapping for windows systems.
|
7
|
+
class RawTerm
|
8
|
+
|
9
|
+
#Create a hash with a default value.
|
10
|
+
MAP = Hash.new {|_hash, key| [:unmapped, key]}
|
11
|
+
|
12
|
+
#Map the printable characters.
|
13
|
+
(32..126).each do |code|
|
14
|
+
char = code.chr
|
15
|
+
MAP[char] = [:insert_text, char]
|
16
|
+
end
|
17
|
+
|
18
|
+
pfx = 0xE0.chr
|
19
|
+
|
20
|
+
#Map the non-terminal entries.
|
21
|
+
MAP["\x00"] = false
|
22
|
+
MAP[ pfx ] = false
|
23
|
+
|
24
|
+
#Map the non-printing characters.
|
25
|
+
|
26
|
+
#Left Arrows
|
27
|
+
MAP["\x00K"] = [:go_left]
|
28
|
+
MAP[pfx+"K"] = [:go_left]
|
29
|
+
|
30
|
+
#Right Arrows
|
31
|
+
MAP["\x00M"] = [:go_right]
|
32
|
+
MAP[pfx+"M"] = [:go_right]
|
33
|
+
|
34
|
+
#Up Arrows
|
35
|
+
MAP["\x00H"] = [:previous_history]
|
36
|
+
MAP[pfx+"H"] = [:previous_history]
|
37
|
+
|
38
|
+
#Down Arrows
|
39
|
+
MAP["\x00P"] = [:next_history]
|
40
|
+
MAP[pfx+"P"] = [:next_history]
|
41
|
+
|
42
|
+
#The Home keys
|
43
|
+
MAP["\x00G"] = [:go_home]
|
44
|
+
MAP[pfx+"G"] = [:go_home]
|
45
|
+
|
46
|
+
#The End keys
|
47
|
+
MAP["\x00O"] = [:go_end]
|
48
|
+
MAP[pfx+"O"] = [:go_end]
|
49
|
+
|
50
|
+
#The Backspace key
|
51
|
+
MAP["\x08"] = [:delete_left]
|
52
|
+
|
53
|
+
#The Delete keys
|
54
|
+
MAP["\x7F"] = [:delete_right]
|
55
|
+
MAP["\x00S"] = [:delete_right]
|
56
|
+
MAP[pfx+"S"] = [:delete_right]
|
57
|
+
|
58
|
+
#The Enter key
|
59
|
+
MAP["\x0D"] = [:enter]
|
60
|
+
|
61
|
+
#The Escape key
|
62
|
+
MAP["\x1B"] = [:cancel]
|
63
|
+
|
64
|
+
#Get a mapped sequence.
|
65
|
+
def get_mapped_keystroke
|
66
|
+
first_char = get_raw_char
|
67
|
+
|
68
|
+
MAP[first_char] || MAP[first_char + get_raw_char]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
#* windows/set_posn.rb - Set the column of the cursor.
|
4
|
+
module MiniReadline
|
5
|
+
|
6
|
+
#* windows/set_posn.rb - Set the column of the cursor.
|
7
|
+
class RawTerm
|
8
|
+
|
9
|
+
#Set the position of the screen cursor within the line.
|
10
|
+
def set_posn(new_posn)
|
11
|
+
if @_out_handle
|
12
|
+
api_set_posn(new_posn)
|
13
|
+
else
|
14
|
+
term_set_posn(new_posn)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
#Move the cursor using windows voodoo API.
|
19
|
+
def api_set_posn(new_posn)
|
20
|
+
raw_buffer = 0.chr * 24
|
21
|
+
@_get_screen_info.call(@_out_handle, raw_buffer)
|
22
|
+
y_posn = (raw_buffer[6,2].unpack('S'))[0]
|
23
|
+
|
24
|
+
@_set_cursor_posn.call(@_out_handle, y_posn * 65536 + new_posn)
|
25
|
+
end
|
26
|
+
|
27
|
+
#Move the cursor using terminal primitives.
|
28
|
+
def term_set_posn(new_posn)
|
29
|
+
|
30
|
+
if new_posn > @cursor_posn
|
31
|
+
print("\e#{new_posn - @cursor_posn}C")
|
32
|
+
elsif new_posn < @cursor_posn
|
33
|
+
print("\e#{@cursor_posn - new_posn}D")
|
34
|
+
end
|
35
|
+
|
36
|
+
@cursor_posn = new_posn
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
#* windows/win_32_api.rb - Support for selected low level Win32 API entry points.
|
4
|
+
module MiniReadline
|
5
|
+
|
6
|
+
require 'fiddle'
|
7
|
+
|
8
|
+
#The classic \Win32API gem is deprecated, so we emulate it with fiddle.
|
9
|
+
class Win32API
|
10
|
+
|
11
|
+
#Use standard calling conventions
|
12
|
+
STDCALL = 1
|
13
|
+
|
14
|
+
#A hash of DLL files used for one or more API entry points.
|
15
|
+
DLL = {}
|
16
|
+
|
17
|
+
#Type mappings.
|
18
|
+
TYPES = {"0" => Fiddle::TYPE_VOID,
|
19
|
+
"S" => Fiddle::TYPE_VOIDP,
|
20
|
+
"I" => Fiddle::TYPE_LONG}
|
21
|
+
|
22
|
+
#Set up an API entry point.
|
23
|
+
def initialize(dllname, func, import)
|
24
|
+
@proto = import.join.tr("VPpNnLlIi", "0SSI").chomp('0').split('')
|
25
|
+
|
26
|
+
handle = DLL[dllname] ||= Fiddle.dlopen(dllname)
|
27
|
+
|
28
|
+
@func = Fiddle::Function.new(handle[func], TYPES.values_at(*@proto), STDCALL)
|
29
|
+
end
|
30
|
+
|
31
|
+
#Call the Win 32 API entry point with appropriate arguments.
|
32
|
+
#<br>Endemic Code Smells
|
33
|
+
#* :reek:FeatureEnvy
|
34
|
+
def call(*args)
|
35
|
+
args.each_with_index do |arg, index|
|
36
|
+
case @proto[index]
|
37
|
+
when "S"
|
38
|
+
args[index], = [arg == 0 ? nil : arg].pack("p").unpack("l!*")
|
39
|
+
when "I"
|
40
|
+
args[index], = [arg].pack("I").unpack("i")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
@func.call(*args).to_i || 0
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require_relative 'read_line/edit'
|
4
|
+
|
5
|
+
#* read_line.rb - The ReadLine class that does the actual work.
|
6
|
+
module MiniReadline
|
7
|
+
|
8
|
+
#The \Readline class that does the actual work of getting lines from the
|
9
|
+
#user. Note that each instance of this class maintains its own copy of
|
10
|
+
#the optional command history.
|
11
|
+
class Readline
|
12
|
+
|
13
|
+
#Setup the instance of the mini line editor.
|
14
|
+
#<br>Parameters:
|
15
|
+
#* buffer - An array of strings used to contain the history. Use an empty
|
16
|
+
# array to have a history buffer with no initial entries. Use the
|
17
|
+
# value nil (or false) to maintain no history at all.
|
18
|
+
def initialize(buffer=[])
|
19
|
+
@edit = Edit.new(buffer)
|
20
|
+
end
|
21
|
+
|
22
|
+
#Get the history buffer of this read line instance.
|
23
|
+
def history
|
24
|
+
@edit.history
|
25
|
+
end
|
26
|
+
|
27
|
+
#Read a line from the console with edit and history.
|
28
|
+
#<br>Parameters:
|
29
|
+
#* prompt - A string used to prompt the user. '>' is popular.
|
30
|
+
#* options - A hash of options; Typically :symbol => value
|
31
|
+
def readline(prompt, options = {})
|
32
|
+
initialize_parms(prompt, options)
|
33
|
+
result = @edit.edit_process
|
34
|
+
@term.put_new_line
|
35
|
+
result
|
36
|
+
end
|
37
|
+
|
38
|
+
#Initialize the read line process. This basically process the arguments
|
39
|
+
#of the readline method.
|
40
|
+
def initialize_parms(prompt, options)
|
41
|
+
@options = MiniReadline::BASE_OPTIONS.merge(options)
|
42
|
+
(@term = @options[:term]).initialize_parms
|
43
|
+
|
44
|
+
set_prompt(prompt)
|
45
|
+
@edit.initialize_edit_parms(@options)
|
46
|
+
end
|
47
|
+
|
48
|
+
#Set up the prompt options.
|
49
|
+
def set_prompt(prompt)
|
50
|
+
@options[:base_prompt] = prompt
|
51
|
+
@options[:scroll_prompt] = @options[:alt_prompt] || prompt
|
52
|
+
|
53
|
+
verify_prompt(@options[:base_prompt])
|
54
|
+
verify_prompt(@options[:scroll_prompt])
|
55
|
+
end
|
56
|
+
|
57
|
+
#Verify that the prompt will fit!
|
58
|
+
def verify_prompt(str)
|
59
|
+
unless (@options[:window_width] - str.length) >
|
60
|
+
(@options[:scroll_step] * 2)
|
61
|
+
fail "Prompt too long: #{str.inspect}"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require_relative 'edit_window'
|
4
|
+
require_relative 'history'
|
5
|
+
|
6
|
+
require_relative 'edit/insert_text'
|
7
|
+
require_relative 'edit/enter'
|
8
|
+
|
9
|
+
require_relative 'edit/go_left'
|
10
|
+
require_relative 'edit/go_right'
|
11
|
+
|
12
|
+
require_relative 'edit/go_home'
|
13
|
+
require_relative 'edit/go_end'
|
14
|
+
|
15
|
+
require_relative 'edit/delete_left'
|
16
|
+
require_relative 'edit/delete_right'
|
17
|
+
require_relative 'edit/cancel'
|
18
|
+
|
19
|
+
require_relative 'edit/previous_history'
|
20
|
+
require_relative 'edit/next_history'
|
21
|
+
|
22
|
+
require_relative 'edit/unmapped'
|
23
|
+
|
24
|
+
#* read_line/edit.rb - The line editor.
|
25
|
+
module MiniReadline
|
26
|
+
|
27
|
+
#* read_line/edit.rb - The line editor.
|
28
|
+
class Edit
|
29
|
+
|
30
|
+
#Set up the edit instance.
|
31
|
+
def initialize(buffer)
|
32
|
+
@history = History.new(buffer)
|
33
|
+
@edit_window = EditWindow.new
|
34
|
+
end
|
35
|
+
|
36
|
+
#Set up the initial edit settings.
|
37
|
+
def initialize_edit_parms(options)
|
38
|
+
@options = options
|
39
|
+
@term = @options[:term]
|
40
|
+
@edit_posn = 0
|
41
|
+
@edit_buffer = ""
|
42
|
+
@working = true
|
43
|
+
|
44
|
+
@edit_window.initialize_parms(@options)
|
45
|
+
@history.initialize_parms(@options)
|
46
|
+
end
|
47
|
+
|
48
|
+
#The main edit buffer
|
49
|
+
attr_reader :edit_buffer
|
50
|
+
|
51
|
+
#The current edit position
|
52
|
+
attr_reader :edit_posn
|
53
|
+
|
54
|
+
#How long is the current string?
|
55
|
+
def length
|
56
|
+
edit_buffer.length
|
57
|
+
end
|
58
|
+
|
59
|
+
#Get the history array for this edit instance.
|
60
|
+
def history
|
61
|
+
@history.history
|
62
|
+
end
|
63
|
+
|
64
|
+
#Interact with the user
|
65
|
+
def edit_process
|
66
|
+
result = edit_loop
|
67
|
+
@history.append_history(result)
|
68
|
+
result
|
69
|
+
end
|
70
|
+
|
71
|
+
#The line editor processing loop.
|
72
|
+
def edit_loop
|
73
|
+
while @working
|
74
|
+
@edit_window.sync_window(edit_buffer, edit_posn)
|
75
|
+
@edit_window.sync_cursor(edit_posn)
|
76
|
+
process_keystroke(@term.get_mapped_keystroke)
|
77
|
+
end
|
78
|
+
|
79
|
+
edit_buffer
|
80
|
+
end
|
81
|
+
|
82
|
+
#Process a keystroke.
|
83
|
+
def process_keystroke(key_cmd)
|
84
|
+
send(key_cmd[0], key_cmd)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
#* read_line/window/edit/cancel.rb - Process :cancel
|
4
|
+
module MiniReadline
|
5
|
+
|
6
|
+
#* read_line/window/edit/cancel.rb - Process :cancel
|
7
|
+
class Edit
|
8
|
+
|
9
|
+
#All right! Scrap all of this and start over!
|
10
|
+
def cancel(_keyboard_args)
|
11
|
+
@edit_buffer = ""
|
12
|
+
@edit_posn = 0
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|