rereline 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in rereline.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+
10
+ gem "test-unit", "~> 3.0"
data/README.md ADDED
@@ -0,0 +1,44 @@
1
+ [![Ruby](https://github.com/osyo-manga/gem-rereline/actions/workflows/main.yml/badge.svg)](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,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/test_*.rb"]
10
+ end
11
+
12
+ task default: :test
@@ -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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rereline
4
+ VERSION = "0.1.0"
5
+ 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
@@ -0,0 +1,4 @@
1
+ module Rereline
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
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: []