mini_readline 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|