rereline 0.1.0
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/Gemfile +10 -0
- data/README.md +44 -0
- data/Rakefile +12 -0
- data/lib/rereline/debugger.rb +34 -0
- data/lib/rereline/key_actor.rb +40 -0
- data/lib/rereline/key_stroke.rb +83 -0
- data/lib/rereline/line_editor.rb +54 -0
- data/lib/rereline/terminal.rb +77 -0
- data/lib/rereline/version.rb +5 -0
- data/lib/rereline.rb +12 -0
- data/rereline.gemspec +36 -0
- data/sig/rereline.rbs +4 -0
- metadata +70 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: dd3498e07ec6c70cc5fd50bdbcf32ed452107feece93a29699684efa2f6ee37d
|
4
|
+
data.tar.gz: 830bbedf1db6a285671a41ad73318bb896284e548e9477699adb6e35796f021e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d7f8486b9a4af0b4c1560b3372e532299b81aa731156c1ebc4326b34f620e6df32bb67dd170a62675bf8059caa383f02ce0f8ed076ae6afbd5424eaeb1e60676
|
7
|
+
data.tar.gz: 6049ff2d93336fbbd1d13c8c14c8dbcd30f2d8e27b94379b9f4f99c624b8d58e358c7841bf782c7406a0f7e6b01fd0f00fbf776437f6c486663d46da51cdc8ad
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
[](https://github.com/osyo-manga/gem-rereline/actions/workflows/main.yml)
|
2
|
+
|
3
|
+
# Rereline
|
4
|
+
|
5
|
+
reline library from scratch.
|
6
|
+
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Install the gem and add to the application's Gemfile by executing:
|
11
|
+
|
12
|
+
$ bundle add rereline
|
13
|
+
|
14
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
15
|
+
|
16
|
+
$ gem install rereline
|
17
|
+
|
18
|
+
## Usage
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
require "rereline"
|
22
|
+
|
23
|
+
prompt = 'prompt> '
|
24
|
+
use_history = false
|
25
|
+
|
26
|
+
puts Rereline.readline(prompt, use_history)
|
27
|
+
```
|
28
|
+
|
29
|
+
## Support
|
30
|
+
|
31
|
+
* Key
|
32
|
+
* [x] BackSpace
|
33
|
+
* [x] CR
|
34
|
+
* [x] Cursor (<- ->)
|
35
|
+
|
36
|
+
## Development
|
37
|
+
|
38
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test-unit` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
39
|
+
|
40
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
41
|
+
|
42
|
+
## Contributing
|
43
|
+
|
44
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/osyo-manga/gem-rereline.
|
data/Rakefile
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# ENV["RELINE_STDERR_TTY"] = "/dev/pts/1"
|
2
|
+
|
3
|
+
module Rereline
|
4
|
+
class Debugger
|
5
|
+
def initialize(output)
|
6
|
+
@output = output
|
7
|
+
end
|
8
|
+
|
9
|
+
def puts(*args)
|
10
|
+
@output&.puts(*args)
|
11
|
+
end
|
12
|
+
|
13
|
+
def debug(*args, &block)
|
14
|
+
if block
|
15
|
+
node = RubyVM::AbstractSyntaxTree.of(block, keep_script_lines: true)
|
16
|
+
code = node.children.last.source
|
17
|
+
puts "#{code} => #{block.binding.eval(code).inspect}"
|
18
|
+
else
|
19
|
+
puts(*args)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
module Refine
|
24
|
+
refine Kernel do
|
25
|
+
output = File.open(ENV['RELINE_STDERR_TTY'], 'a') if ENV['RELINE_STDERR_TTY']
|
26
|
+
debugger = Debugger.new(output)
|
27
|
+
|
28
|
+
define_method(:debug) do |*args, **kwd, &block|
|
29
|
+
debugger.debug(*args, **kwd, &block)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Rereline
|
2
|
+
class KeyActor
|
3
|
+
MAPPING = {
|
4
|
+
[127] => [:BS],
|
5
|
+
[13] => [:CR],
|
6
|
+
[27, 91, 67] => [:RIGHT],
|
7
|
+
[27, 91, 68] => [:LEFT]
|
8
|
+
}
|
9
|
+
|
10
|
+
attr_reader :editor
|
11
|
+
|
12
|
+
def initialize(editor)
|
13
|
+
@editor = editor
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(input)
|
17
|
+
case input
|
18
|
+
when Symbol
|
19
|
+
editor_ctrl(input) && nil
|
20
|
+
else
|
21
|
+
input
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def editor_ctrl(input)
|
28
|
+
case input
|
29
|
+
when :CR
|
30
|
+
raise Terminal::Finish.new(editor.input_text)
|
31
|
+
when :BS
|
32
|
+
editor.delete_prev_input_pos
|
33
|
+
when :LEFT
|
34
|
+
editor.move_left
|
35
|
+
when :RIGHT
|
36
|
+
editor.move_right
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module Rereline
|
2
|
+
class KeyStroke
|
3
|
+
module Ex
|
4
|
+
refine Array do
|
5
|
+
def start_with?(other)
|
6
|
+
return false if size < other.size
|
7
|
+
take(other.size) == other
|
8
|
+
end
|
9
|
+
|
10
|
+
def match_status(input)
|
11
|
+
if input.start_with? self
|
12
|
+
:matched
|
13
|
+
elsif self.start_with? input
|
14
|
+
:matching
|
15
|
+
else
|
16
|
+
:unmatched
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def matched?(input)
|
21
|
+
match_status(input) == :matched
|
22
|
+
end
|
23
|
+
|
24
|
+
def matching?(input)
|
25
|
+
match_status(input) == :matching
|
26
|
+
end
|
27
|
+
|
28
|
+
def unmatched?(input)
|
29
|
+
match_status(input) == :unmatched
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
using Ex
|
34
|
+
|
35
|
+
attr_reader :mapping
|
36
|
+
|
37
|
+
def initialize(mapping)
|
38
|
+
@mapping = mapping
|
39
|
+
end
|
40
|
+
|
41
|
+
def expand(input, wait_match: true)
|
42
|
+
if input.empty?
|
43
|
+
[[], []]
|
44
|
+
elsif wait_match && matching?(input)
|
45
|
+
[[], input]
|
46
|
+
elsif matched?(input)
|
47
|
+
lhs, rhs = mapping.filter { |lhs, rhs| lhs.matched? input }.max_by { |lhs, rhs| lhs.size }
|
48
|
+
if (after_input = input.drop(lhs.size)).size > 0
|
49
|
+
# NOTE: 再帰的にマッピングされているキーを展開する時に後続のキーが存在している場合は待ち処理はせずに展開する
|
50
|
+
join(expand(rhs, wait_match: false), expand(after_input, wait_match: wait_match))
|
51
|
+
else
|
52
|
+
expand(rhs, wait_match: wait_match)
|
53
|
+
end
|
54
|
+
else
|
55
|
+
join([input.take(1), []], expand(input.drop(1), wait_match: wait_match))
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def matching?(input)
|
62
|
+
mapping.any? { |lhs, rhs| lhs.matching? input }
|
63
|
+
end
|
64
|
+
|
65
|
+
def matched?(input)
|
66
|
+
mapping.any? { |lhs, rhs| lhs.matched? input }
|
67
|
+
end
|
68
|
+
|
69
|
+
def match_status(input)
|
70
|
+
mapping.map { |lhs, rhs| lhs.match_status input }
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
# a = [[1, 2], [3, 4]]
|
76
|
+
# b = [[:a, :b], [:c, :d]]
|
77
|
+
# join(a, b)
|
78
|
+
# => [[1, 2, :a, :b], [3, 4, :c, :d]]
|
79
|
+
def join(a, b)
|
80
|
+
[a, b].transpose.map { |it| it.inject(&:+) }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Rereline
|
2
|
+
class LineEditor
|
3
|
+
attr_accessor :prompt, :encoding
|
4
|
+
attr_reader :input_text, :input_pos
|
5
|
+
|
6
|
+
def initialize(&block)
|
7
|
+
@input_text = ""
|
8
|
+
@encoding = Encoding.default_external
|
9
|
+
block.call(self) if block
|
10
|
+
@input_key_buffer = []
|
11
|
+
@input_pos = 0
|
12
|
+
end
|
13
|
+
|
14
|
+
def input_key(key)
|
15
|
+
return if key.nil?
|
16
|
+
|
17
|
+
@input_key_buffer << key
|
18
|
+
if (result = @input_key_buffer.map(&:chr).join.force_encoding(encoding)).valid_encoding?
|
19
|
+
input_char(result)
|
20
|
+
@input_key_buffer.clear
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def input_char(c)
|
25
|
+
input_text.insert(input_pos, c)
|
26
|
+
@input_pos += 1
|
27
|
+
end
|
28
|
+
|
29
|
+
def delete_prev_input_pos
|
30
|
+
input_text.slice!(-1)
|
31
|
+
@input_pos -= 1
|
32
|
+
end
|
33
|
+
|
34
|
+
def move_input_pos(offset)
|
35
|
+
@input_pos = (input_pos + offset).clamp(0, input_text.size)
|
36
|
+
end
|
37
|
+
|
38
|
+
def move_left
|
39
|
+
move_input_pos(-1)
|
40
|
+
end
|
41
|
+
|
42
|
+
def move_right
|
43
|
+
move_input_pos(+1)
|
44
|
+
end
|
45
|
+
|
46
|
+
def prev_input_pos_line
|
47
|
+
"#{prompt}#{input_text.slice(0, input_pos)}"
|
48
|
+
end
|
49
|
+
|
50
|
+
def line
|
51
|
+
"#{prompt}#{input_text}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require "reline"
|
2
|
+
require_relative "./line_editor.rb"
|
3
|
+
require_relative "./key_stroke.rb"
|
4
|
+
require_relative "./key_actor.rb"
|
5
|
+
require_relative "./debugger.rb"
|
6
|
+
|
7
|
+
|
8
|
+
module Rereline
|
9
|
+
using Debugger::Refine
|
10
|
+
|
11
|
+
class Terminal
|
12
|
+
class Finish < StandardError; end
|
13
|
+
|
14
|
+
module Ex
|
15
|
+
refine String do
|
16
|
+
def displaywidth
|
17
|
+
Reline::Unicode.calculate_width(self)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
using Ex
|
22
|
+
|
23
|
+
attr_reader :editor, :key_actor, :key_stroke, :input, :output
|
24
|
+
|
25
|
+
def initialize(prompt, use_history)
|
26
|
+
@editor = LineEditor.new do |editor|
|
27
|
+
editor.prompt = prompt
|
28
|
+
end
|
29
|
+
@key_stroke = KeyStroke.new(KeyActor::MAPPING)
|
30
|
+
@key_actor = KeyActor.new(editor)
|
31
|
+
@input = $stdin
|
32
|
+
@output = $stdout
|
33
|
+
@input_key_buffer = []
|
34
|
+
end
|
35
|
+
|
36
|
+
def readline
|
37
|
+
renader_editor
|
38
|
+
loop do
|
39
|
+
input_key(getkey)
|
40
|
+
end
|
41
|
+
rescue Finish => e
|
42
|
+
output.write "\n"
|
43
|
+
e.message
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def input_key(key)
|
49
|
+
return if key.nil?
|
50
|
+
|
51
|
+
@input_key_buffer << key
|
52
|
+
extended_keys, matching_keys = key_stroke.expand(@input_key_buffer)
|
53
|
+
|
54
|
+
@input_key_buffer = matching_keys
|
55
|
+
extended_keys.each { |it|
|
56
|
+
editor.input_key(key_actor.call(it))
|
57
|
+
}
|
58
|
+
debug { editor.input_text }
|
59
|
+
renader_editor
|
60
|
+
end
|
61
|
+
|
62
|
+
def renader_editor
|
63
|
+
# Move cursor to left edge
|
64
|
+
output.write "\e[#{0 + 1}G"
|
65
|
+
output.write editor.line
|
66
|
+
# Delete characters to right of cursor position (end of text)
|
67
|
+
output.write "\e[K"
|
68
|
+
# Adjust cursor position
|
69
|
+
output.write "\e[#{editor.prev_input_pos_line.displaywidth + 1}G"
|
70
|
+
end
|
71
|
+
|
72
|
+
def getkey
|
73
|
+
c = input.raw(intr: true) { input.wait_readable(0.1) && input.getbyte }
|
74
|
+
(c == 0x16 && input.raw(min: 0, time: 0, &:getbyte)) || c
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
data/lib/rereline.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "./rereline/version"
|
4
|
+
require_relative "./rereline/terminal.rb"
|
5
|
+
|
6
|
+
module Rereline
|
7
|
+
class Error < StandardError; end
|
8
|
+
|
9
|
+
def self.readline(prompt, use_history = false)
|
10
|
+
Terminal.new(prompt, use_history).readline
|
11
|
+
end
|
12
|
+
end
|
data/rereline.gemspec
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/rereline/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "rereline"
|
7
|
+
spec.version = Rereline::VERSION
|
8
|
+
spec.authors = ["manga_osyo"]
|
9
|
+
spec.email = ["manga.osyo@gmail.com"]
|
10
|
+
|
11
|
+
spec.summary = "reline library from scratch."
|
12
|
+
spec.description = "reline library from scratch."
|
13
|
+
spec.homepage = "https://github.com/osyo-manga/gem-rereline"
|
14
|
+
spec.required_ruby_version = ">= 2.6.0"
|
15
|
+
|
16
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
17
|
+
spec.metadata["source_code_uri"] = "https://github.com/osyo-manga/gem-rereline"
|
18
|
+
# spec.metadata["changelog_uri"] = "https://example.com"
|
19
|
+
|
20
|
+
# Specify which files should be added to the gem when it is released.
|
21
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
22
|
+
spec.files = Dir.chdir(__dir__) do
|
23
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
24
|
+
(f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|circleci)|appveyor)})
|
25
|
+
end
|
26
|
+
end
|
27
|
+
spec.bindir = "exe"
|
28
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
29
|
+
spec.require_paths = ["lib"]
|
30
|
+
|
31
|
+
# Uncomment to register a new dependency of your gem
|
32
|
+
spec.add_dependency "reline"
|
33
|
+
|
34
|
+
# For more information and examples about making a new gem, check out our
|
35
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
36
|
+
end
|
data/sig/rereline.rbs
ADDED
metadata
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rereline
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- manga_osyo
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-01-30 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: reline
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
description: reline library from scratch.
|
28
|
+
email:
|
29
|
+
- manga.osyo@gmail.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- Gemfile
|
35
|
+
- README.md
|
36
|
+
- Rakefile
|
37
|
+
- lib/rereline.rb
|
38
|
+
- lib/rereline/debugger.rb
|
39
|
+
- lib/rereline/key_actor.rb
|
40
|
+
- lib/rereline/key_stroke.rb
|
41
|
+
- lib/rereline/line_editor.rb
|
42
|
+
- lib/rereline/terminal.rb
|
43
|
+
- lib/rereline/version.rb
|
44
|
+
- rereline.gemspec
|
45
|
+
- sig/rereline.rbs
|
46
|
+
homepage: https://github.com/osyo-manga/gem-rereline
|
47
|
+
licenses: []
|
48
|
+
metadata:
|
49
|
+
homepage_uri: https://github.com/osyo-manga/gem-rereline
|
50
|
+
source_code_uri: https://github.com/osyo-manga/gem-rereline
|
51
|
+
post_install_message:
|
52
|
+
rdoc_options: []
|
53
|
+
require_paths:
|
54
|
+
- lib
|
55
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: 2.6.0
|
60
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '0'
|
65
|
+
requirements: []
|
66
|
+
rubygems_version: 3.4.2
|
67
|
+
signing_key:
|
68
|
+
specification_version: 4
|
69
|
+
summary: reline library from scratch.
|
70
|
+
test_files: []
|