mini_readline 0.0.1

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