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.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +207 -0
  6. data/Rakefile +40 -0
  7. data/lib/mini_readline.rb +28 -0
  8. data/lib/mini_readline/options.rb +19 -0
  9. data/lib/mini_readline/raw_term.rb +14 -0
  10. data/lib/mini_readline/raw_term/other.rb +67 -0
  11. data/lib/mini_readline/raw_term/other/map.rb +75 -0
  12. data/lib/mini_readline/raw_term/other/set_posn.rb +22 -0
  13. data/lib/mini_readline/raw_term/windows.rb +88 -0
  14. data/lib/mini_readline/raw_term/windows/map.rb +71 -0
  15. data/lib/mini_readline/raw_term/windows/set_posn.rb +40 -0
  16. data/lib/mini_readline/raw_term/windows/win_32_api.rb +47 -0
  17. data/lib/mini_readline/read_line.rb +65 -0
  18. data/lib/mini_readline/read_line/edit.rb +87 -0
  19. data/lib/mini_readline/read_line/edit/cancel.rb +15 -0
  20. data/lib/mini_readline/read_line/edit/delete_left.rb +21 -0
  21. data/lib/mini_readline/read_line/edit/delete_right.rb +19 -0
  22. data/lib/mini_readline/read_line/edit/enter.rb +14 -0
  23. data/lib/mini_readline/read_line/edit/go_end.rb +14 -0
  24. data/lib/mini_readline/read_line/edit/go_home.rb +14 -0
  25. data/lib/mini_readline/read_line/edit/go_left.rb +18 -0
  26. data/lib/mini_readline/read_line/edit/go_right.rb +18 -0
  27. data/lib/mini_readline/read_line/edit/insert_text.rb +18 -0
  28. data/lib/mini_readline/read_line/edit/next_history.rb +19 -0
  29. data/lib/mini_readline/read_line/edit/previous_history.rb +19 -0
  30. data/lib/mini_readline/read_line/edit/unmapped.rb +18 -0
  31. data/lib/mini_readline/read_line/edit_window.rb +72 -0
  32. data/lib/mini_readline/read_line/edit_window/sync_cursor.rb +15 -0
  33. data/lib/mini_readline/read_line/edit_window/sync_window.rb +46 -0
  34. data/lib/mini_readline/read_line/history.rb +61 -0
  35. data/lib/mini_readline/version.rb +4 -0
  36. data/mini_readline.gemspec +26 -0
  37. data/reek.txt +1 -0
  38. data/sire.rb +102 -0
  39. data/tests/mini_readline_tests.rb +55 -0
  40. 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