bestliner 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/.gitignore +15 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +25 -0
- data/LICENSE +36 -0
- data/README.md +65 -0
- data/Rakefile +13 -0
- data/bestliner.gemspec +24 -0
- data/ext/bestliner/Makefile +266 -0
- data/ext/bestliner/bestline.c +3578 -0
- data/ext/bestliner/bestline.h +39 -0
- data/ext/bestliner/bestliner.c +136 -0
- data/ext/bestliner/extconf.rb +4 -0
- data/lib/bestliner/version.rb +5 -0
- data/lib/bestliner.rb +131 -0
- metadata +88 -0
@@ -0,0 +1,39 @@
|
|
1
|
+
#pragma once
|
2
|
+
|
3
|
+
typedef struct bestlineCompletions {
|
4
|
+
unsigned long len;
|
5
|
+
char **cvec;
|
6
|
+
} bestlineCompletions;
|
7
|
+
|
8
|
+
typedef void(bestlineCompletionCallback)(const char *, bestlineCompletions *);
|
9
|
+
typedef char *(bestlineHintsCallback)(const char *, const char **,
|
10
|
+
const char **);
|
11
|
+
typedef void(bestlineFreeHintsCallback)(void *);
|
12
|
+
typedef unsigned(bestlineXlatCallback)(unsigned);
|
13
|
+
|
14
|
+
void bestlineSetCompletionCallback(bestlineCompletionCallback *);
|
15
|
+
void bestlineSetHintsCallback(bestlineHintsCallback *);
|
16
|
+
void bestlineSetFreeHintsCallback(bestlineFreeHintsCallback *);
|
17
|
+
void bestlineAddCompletion(bestlineCompletions *, const char *);
|
18
|
+
void bestlineSetXlatCallback(bestlineXlatCallback *);
|
19
|
+
|
20
|
+
char *bestline(const char *);
|
21
|
+
char *bestlineRaw(const char *, int, int);
|
22
|
+
char *bestlineWithHistory(const char *, const char *);
|
23
|
+
int bestlineHistoryAdd(const char *);
|
24
|
+
int bestlineHistorySave(const char *);
|
25
|
+
int bestlineHistoryLoad(const char *);
|
26
|
+
void bestlineFreeCompletions(bestlineCompletions *);
|
27
|
+
void bestlineHistoryFree(void);
|
28
|
+
void bestlineClearScreen(int);
|
29
|
+
void bestlineMaskModeEnable(void);
|
30
|
+
void bestlineMaskModeDisable(void);
|
31
|
+
void bestlineDisableRawMode(void);
|
32
|
+
void bestlineFree(void *);
|
33
|
+
|
34
|
+
char bestlineIsSeparator(unsigned);
|
35
|
+
char bestlineNotSeparator(unsigned);
|
36
|
+
char bestlineIsXeparator(unsigned);
|
37
|
+
unsigned bestlineUppercase(unsigned);
|
38
|
+
unsigned bestlineLowercase(unsigned);
|
39
|
+
long bestlineReadCharacter(int, char *, unsigned long);
|
@@ -0,0 +1,136 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
#include "bestline.h"
|
3
|
+
|
4
|
+
static VALUE Bestliner;
|
5
|
+
static ID sym_id_call;
|
6
|
+
|
7
|
+
/* Callback Functions */
|
8
|
+
|
9
|
+
static void completionCallback(const char *buf, bestlineCompletions *lc) {
|
10
|
+
VALUE proc, ary, str;
|
11
|
+
long i, matches;
|
12
|
+
proc = rb_iv_get(Bestliner, "@completion_callback");
|
13
|
+
ary = rb_funcall(proc, sym_id_call, 1, rb_utf8_str_new_cstr(buf));
|
14
|
+
Check_Type(ary, T_ARRAY);
|
15
|
+
matches = RARRAY_LEN(ary);
|
16
|
+
for (i = 0; i < matches; i++) {
|
17
|
+
str = rb_obj_as_string(RARRAY_AREF(ary, i));
|
18
|
+
bestlineAddCompletion(lc, StringValueCStr(str));
|
19
|
+
}
|
20
|
+
}
|
21
|
+
|
22
|
+
static char *hintsCallback(const char *buf, const char **ansi1, const char **ansi2) {
|
23
|
+
VALUE proc, str, a1, a2;
|
24
|
+
proc = rb_iv_get(Bestliner, "@hints_callback");
|
25
|
+
str = rb_funcall(proc, sym_id_call, 1, rb_utf8_str_new_cstr(buf));
|
26
|
+
if (NIL_P(str)) {
|
27
|
+
return NULL;
|
28
|
+
} else {
|
29
|
+
a1 = rb_iv_get(Bestliner, "@hints_before");
|
30
|
+
if (!NIL_P(a1)) *ansi1 = StringValueCStr(a1);
|
31
|
+
a2 = rb_iv_get(Bestliner, "@hints_after");
|
32
|
+
if (!NIL_P(a2)) *ansi2 = StringValueCStr(a2);
|
33
|
+
return StringValueCStr(str);
|
34
|
+
}
|
35
|
+
}
|
36
|
+
|
37
|
+
/* Wrapper Functions */
|
38
|
+
|
39
|
+
static VALUE bestliner_bestline(VALUE self, VALUE prompt) {
|
40
|
+
VALUE result;
|
41
|
+
char *line;
|
42
|
+
line = bestline(StringValueCStr(prompt));
|
43
|
+
if (line == NULL) return Qnil;
|
44
|
+
result = rb_utf8_str_new_cstr(line);
|
45
|
+
free(line);
|
46
|
+
return result;
|
47
|
+
}
|
48
|
+
|
49
|
+
static VALUE bestliner_bestlineWithHistory(VALUE self, VALUE prompt, VALUE filename) {
|
50
|
+
VALUE result;
|
51
|
+
char *line;
|
52
|
+
line = bestlineWithHistory(StringValueCStr(prompt), StringValueCStr(filename));
|
53
|
+
if (line == NULL) return Qnil;
|
54
|
+
result = rb_utf8_str_new_cstr(line);
|
55
|
+
free(line);
|
56
|
+
return result;
|
57
|
+
}
|
58
|
+
|
59
|
+
static VALUE bestliner_bestlineRaw(VALUE self, VALUE prompt, VALUE in, VALUE out) {
|
60
|
+
VALUE result;
|
61
|
+
char *line;
|
62
|
+
line = bestlineRaw(StringValueCStr(prompt), NUM2INT(in), NUM2INT(out));
|
63
|
+
if (line == NULL) return Qnil;
|
64
|
+
result = rb_utf8_str_new_cstr(line);
|
65
|
+
free(line);
|
66
|
+
return result;
|
67
|
+
}
|
68
|
+
|
69
|
+
static VALUE bestliner_bestlineHistoryAdd(VALUE self, VALUE line) {
|
70
|
+
int rc;
|
71
|
+
rc = bestlineHistoryAdd(StringValueCStr(line));
|
72
|
+
return INT2NUM(rc);
|
73
|
+
}
|
74
|
+
|
75
|
+
static VALUE bestliner_bestlineHistorySave(VALUE self, VALUE filename) {
|
76
|
+
int rc;
|
77
|
+
rc = bestlineHistorySave(StringValueCStr(filename));
|
78
|
+
return INT2NUM(rc);
|
79
|
+
}
|
80
|
+
|
81
|
+
static VALUE bestliner_bestlineHistoryLoad(VALUE self, VALUE filename) {
|
82
|
+
int rc;
|
83
|
+
rc = bestlineHistoryLoad(StringValueCStr(filename));
|
84
|
+
return INT2NUM(rc);
|
85
|
+
}
|
86
|
+
|
87
|
+
static VALUE bestliner_bestlineHistoryFree(VALUE self) {
|
88
|
+
bestlineHistoryFree();
|
89
|
+
return Qnil;
|
90
|
+
}
|
91
|
+
|
92
|
+
static VALUE bestliner_bestlineClearScreen(VALUE self, VALUE out) {
|
93
|
+
bestlineClearScreen(NUM2INT(out));
|
94
|
+
return Qnil;
|
95
|
+
}
|
96
|
+
|
97
|
+
static VALUE bestliner_bestlineMaskModeEnable(VALUE self) {
|
98
|
+
bestlineMaskModeEnable();
|
99
|
+
return Qtrue;
|
100
|
+
}
|
101
|
+
|
102
|
+
static VALUE bestliner_bestlineMaskModeDisable(VALUE self) {
|
103
|
+
bestlineMaskModeDisable();
|
104
|
+
return Qfalse;
|
105
|
+
}
|
106
|
+
|
107
|
+
static VALUE bestliner_bestlineSetCompletionCallback(VALUE self, VALUE proc) {
|
108
|
+
rb_iv_set(self, "@completion_callback", proc);
|
109
|
+
bestlineSetCompletionCallback(completionCallback);
|
110
|
+
return proc;
|
111
|
+
}
|
112
|
+
|
113
|
+
static VALUE bestliner_bestlineSetHintsCallback(VALUE self, VALUE proc) {
|
114
|
+
rb_iv_set(self, "@hints_callback", proc);
|
115
|
+
bestlineSetHintsCallback(hintsCallback);
|
116
|
+
return proc;
|
117
|
+
}
|
118
|
+
|
119
|
+
void Init_bestliner(void) {
|
120
|
+
/* Setup symbols */
|
121
|
+
sym_id_call = rb_intern("call");
|
122
|
+
/* Setup module */
|
123
|
+
Bestliner = rb_define_module("Bestliner");
|
124
|
+
rb_define_singleton_method(Bestliner, "__bestline", bestliner_bestline, 1);
|
125
|
+
rb_define_singleton_method(Bestliner, "__bestline_with_history", bestliner_bestlineWithHistory, 2);
|
126
|
+
rb_define_singleton_method(Bestliner, "__bestline_raw", bestliner_bestlineRaw, 3);
|
127
|
+
rb_define_singleton_method(Bestliner, "__add_history", bestliner_bestlineHistoryAdd, 1);
|
128
|
+
rb_define_singleton_method(Bestliner, "__save_history", bestliner_bestlineHistorySave, 1);
|
129
|
+
rb_define_singleton_method(Bestliner, "__load_history", bestliner_bestlineHistoryLoad, 1);
|
130
|
+
rb_define_singleton_method(Bestliner, "__free_history", bestliner_bestlineHistoryFree, 0);
|
131
|
+
rb_define_singleton_method(Bestliner, "__clear_screen", bestliner_bestlineClearScreen, 1);
|
132
|
+
rb_define_singleton_method(Bestliner, "__mask_mode_enable", bestliner_bestlineMaskModeEnable, 0);
|
133
|
+
rb_define_singleton_method(Bestliner, "__mask_mode_disable", bestliner_bestlineMaskModeDisable, 0);
|
134
|
+
rb_define_singleton_method(Bestliner, "__set_completion_cb", bestliner_bestlineSetCompletionCallback, 1);
|
135
|
+
rb_define_singleton_method(Bestliner, "__set_hints_cb", bestliner_bestlineSetHintsCallback, 1);
|
136
|
+
}
|
data/lib/bestliner.rb
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bestliner
|
4
|
+
class << self
|
5
|
+
# Reads text input after an optional prompt
|
6
|
+
#
|
7
|
+
# @param prompt [String] the prompt to display before the input cursor
|
8
|
+
# @return [String, nil] the line of input, `nil` if EOF
|
9
|
+
def bestline(prompt = "")
|
10
|
+
raise ArgumentError, "prompt must be of class String" unless prompt.is_a?(String)
|
11
|
+
__bestline prompt
|
12
|
+
end
|
13
|
+
|
14
|
+
# Reads text input after an optional prompt and saves input to a file
|
15
|
+
#
|
16
|
+
# @param prompt [String] the prompt to display before the input cursor
|
17
|
+
# @param filename [String] the file to use for saving input history
|
18
|
+
# @return (see #bestline)
|
19
|
+
def bestline_with_history(prompt = "", filename = ".bestliner_history")
|
20
|
+
raise ArgumentError, "prompt must be of class String" unless prompt.is_a?(String)
|
21
|
+
raise ArgumentError, "filename must be of class String" unless filename.is_a?(String)
|
22
|
+
__bestline_with_history prompt, filename
|
23
|
+
end
|
24
|
+
|
25
|
+
# Reads text input after an optional prompt from the input and displays on output
|
26
|
+
#
|
27
|
+
# @param prompt [String] the prompt to display before the input cursor
|
28
|
+
# @param input [IO] the stream to use as input
|
29
|
+
# @param output [IO] the stream to use as output
|
30
|
+
# @return (see #bestline)
|
31
|
+
def bestline_raw(prompt = "", input, output)
|
32
|
+
raise ArgumentError, "prompt must be of class String" unless prompt.is_a?(String)
|
33
|
+
raise ArgumentError, "input must be of class IO" unless input.is_a?(IO)
|
34
|
+
raise ArgumentError, "output must be of class IO" unless output.is_a?(IO)
|
35
|
+
__bestline_raw prompt, input.fileno, output.fileno
|
36
|
+
end
|
37
|
+
|
38
|
+
# Adds the line to the internal history
|
39
|
+
#
|
40
|
+
# @param line [String] the line to add
|
41
|
+
def add_history(line)
|
42
|
+
raise ArgumentError, "line must be of class String" unless line.is_a?(String)
|
43
|
+
return_code = __add_history line
|
44
|
+
raise IOError, "cannot write to history" unless return_code == 1
|
45
|
+
end
|
46
|
+
|
47
|
+
# Saves the history to the file
|
48
|
+
#
|
49
|
+
# @param filename [String] the filename to use
|
50
|
+
def save_history(filename)
|
51
|
+
raise ArgumentError, "filename must be of class String" unless filename.is_a?(String)
|
52
|
+
return_code = __save_history filename
|
53
|
+
raise IOError, "cannot save history to #{filename}" unless return_code == 0
|
54
|
+
end
|
55
|
+
|
56
|
+
# Loads the history from the file
|
57
|
+
#
|
58
|
+
# @param (see #save_history)
|
59
|
+
def load_history(filename)
|
60
|
+
raise Errno::ENOENT, filename unless File.exist?(filename)
|
61
|
+
return_code = __load_history filename
|
62
|
+
raise IOError, "cannot load history from #{filename}" unless return_code == 0
|
63
|
+
end
|
64
|
+
|
65
|
+
# Frees the memory used for the internal history
|
66
|
+
def free_history
|
67
|
+
__free_history
|
68
|
+
end
|
69
|
+
|
70
|
+
# Clears the screen of the output stream
|
71
|
+
#
|
72
|
+
# @param output [IO] the stream to use as output
|
73
|
+
def clear_screen(output)
|
74
|
+
raise ArgumentError, "output must be of class IO" unless output.is_a?(IO)
|
75
|
+
__clear_screen output.fileno
|
76
|
+
end
|
77
|
+
|
78
|
+
# Sets the mask mode
|
79
|
+
#
|
80
|
+
# @param is_enabled [Boolean] whether mask mode is enabled
|
81
|
+
def mask_mode=(is_enabled)
|
82
|
+
@mask_mode = if is_enabled
|
83
|
+
__mask_mode_enable
|
84
|
+
else
|
85
|
+
__mask_mode_disable
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Checks the status of mask mode
|
90
|
+
#
|
91
|
+
# @return [Boolean] whether mask mode is enabled
|
92
|
+
def mask_mode?
|
93
|
+
!!(defined?(@mask_mode) && @mask_mode)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Sets the completion callback
|
97
|
+
#
|
98
|
+
# @param callback [#call] callback that returns completions
|
99
|
+
def completion_callback=(callback)
|
100
|
+
raise ArgumentError, "callback must respond to :call" unless callback.respond_to?(:call)
|
101
|
+
__set_completion_cb callback
|
102
|
+
end
|
103
|
+
|
104
|
+
# Sets the hints callback
|
105
|
+
#
|
106
|
+
# @param callback [#call] callback that returns a hint
|
107
|
+
def hints_callback=(callback)
|
108
|
+
raise ArgumentError, "callback must respond to :call" unless callback.respond_to?(:call)
|
109
|
+
__set_hints_cb callback
|
110
|
+
end
|
111
|
+
|
112
|
+
|
113
|
+
# Sets the ANSI code to use before a hint
|
114
|
+
#
|
115
|
+
# @param ansi_code [String] ANSI code output before hint
|
116
|
+
def hints_before=(ansi_code)
|
117
|
+
raise ArgumentError, "ansi_code must be of class String" unless ansi_code.is_a?(String)
|
118
|
+
@hints_before = ansi_code
|
119
|
+
end
|
120
|
+
|
121
|
+
# Sets the ANSI code to use after a hint
|
122
|
+
#
|
123
|
+
# @param ansi_code [String] ANSI code output after hint
|
124
|
+
def hints_after=(ansi_code)
|
125
|
+
raise ArgumentError, "ansi_code must be of class String" unless ansi_code.is_a?(String)
|
126
|
+
@hints_after = ansi_code
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
require "bestliner/bestliner"
|
metadata
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bestliner
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Michael Camilleri
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-07-18 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake-compiler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: yard
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: |
|
42
|
+
Bestliner is a Ruby wrapper around bestline, a C library for interactive
|
43
|
+
pseudoteletypewriter command sessions using ANSI Standard X3.64 control
|
44
|
+
sequences. Bestliner supports Emacs-style editing shortcuts, a searchable
|
45
|
+
history, completion and hint support via callbacks and UTF-8 editing.
|
46
|
+
email: mike@inqk.net
|
47
|
+
executables: []
|
48
|
+
extensions: []
|
49
|
+
extra_rdoc_files: []
|
50
|
+
files:
|
51
|
+
- ".gitignore"
|
52
|
+
- Gemfile
|
53
|
+
- Gemfile.lock
|
54
|
+
- LICENSE
|
55
|
+
- README.md
|
56
|
+
- Rakefile
|
57
|
+
- bestliner.gemspec
|
58
|
+
- ext/bestliner/Makefile
|
59
|
+
- ext/bestliner/bestline.c
|
60
|
+
- ext/bestliner/bestline.h
|
61
|
+
- ext/bestliner/bestliner.c
|
62
|
+
- ext/bestliner/extconf.rb
|
63
|
+
- lib/bestliner.rb
|
64
|
+
- lib/bestliner/version.rb
|
65
|
+
homepage: https://github.com/pyrmont/bestliner
|
66
|
+
licenses:
|
67
|
+
- BSD-2
|
68
|
+
metadata: {}
|
69
|
+
post_install_message:
|
70
|
+
rdoc_options: []
|
71
|
+
require_paths:
|
72
|
+
- lib
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
requirements: []
|
84
|
+
rubygems_version: 3.3.15
|
85
|
+
signing_key:
|
86
|
+
specification_version: 4
|
87
|
+
summary: Ruby wrapper for bestline, a C library for reading user input
|
88
|
+
test_files: []
|