bestliner 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+ }
@@ -0,0 +1,4 @@
1
+ require "mkmf"
2
+ dir_config("bestline")
3
+ have_header("bestline.h")
4
+ create_makefile "bestliner/bestliner"
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bestliner
4
+ VERSION = "0.1.0"
5
+ end
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: []