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