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