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 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: []