readline-3.0/0000755000076400007640000000000014036743336011270 5ustar pjbpjbreadline-3.0/readline.lua0000644000076400007640000004504314036743336013564 0ustar pjbpjb--------------------------------------------------------------------- -- This Lua5 module is Copyright (c) 2011, Peter J Billam -- -- www.pjb.com.au -- -- -- -- This module is free software; you can redistribute it and/or -- -- modify it under the same terms as Lua5 itself. -- --------------------------------------------------------------------- local M = {} -- public interface M.Version = '2.9' -- fix version number again M.VersionDate = '27jan2021' --[[ Alexander Adler suggests adding four Alternate-Interface functions: https://tiswww.case.edu/php/chet/readline/readline.html#SEC41 void rl_callback_handler_install (const char *prompt, rl_vcpfunc_t *lhandler) Set up the terminal for readline I/O and display the initial expanded value of prompt. Save the value of lhandler to use as a handler function to call when a complete line of input has been entered. The handler function receives the text of the line as an argument. As with readline(), the handler function should free the line when it it finished with it. void rl_callback_read_char (void) void rl_callback_sigcleanup (void) void rl_callback_handler_remove (void) ]] if string.tonumber then tonumber = string.tonumber end -- 5.4 -------------------- private utility functions ------------------- local function warn(str) io.stderr:write(str,'\n') end local function die(str) io.stderr:write(str,'\n') ; os.exit(1) end local function qw(s) -- t = qw[[ foo bar baz ]] local t = {} ; for x in s:gmatch("%S+") do t[#t+1] = x end ; return t end local function deepcopy(object) -- http://lua-users.org/wiki/CopyTable local lookup_table = {} local function _copy(object) if type(object) ~= "table" then return object elseif lookup_table[object] then return lookup_table[object] end local new_table = {} lookup_table[object] = new_table for index, value in pairs(object) do new_table[_copy(index)] = _copy(value) end return setmetatable(new_table, getmetatable(object)) end return _copy(object) end local function sorted_keys(t) local a = {} for k,v in pairs(t) do a[#a+1] = k end table.sort(a) return a end local function touch(fn) local f=io.open(fn,'r') -- or check if posix.stat(path) returns non-nil if f then f:close(); return true else f=io.open(fn,'w') if f then f:write(""); f:close(); return true else return false end end end local function homedir(user) if not user and os.getenv('HOME') then return os.getenv('HOME') end local P = nil pcall(function() P = require 'posix' ; end ) if type(P) == 'table' then -- we have posix if not user then user = P.getpid('euid') end return P.getpasswd(user, 'dir') or '/tmp' end warn('readline: HOME not set and luaposix not installed; using /tmp') return '/tmp/' end local function tilde_expand(filename) if string.match(filename, '^~') then local user = string.match(filename, '^~(%a+)/') local home = homedir(user) filename = string.gsub(filename, '^~%a*', home) end return filename end ---------------- from Lua Programming Gems p. 331 ---------------- local require, table = require, table -- save the used globals local aux, prv = {}, {} -- auxiliary & private C function tables local initialise = require 'C-readline' initialise(aux, prv, M) -- initialise the C lib with aux,prv & module tables ------------------------ public functions ---------------------- prv.using_history() local Option = { -- the default options auto_add = true, completion = true, histfile = '~/.rl_lua_history', ignoredups = true, keeplines = 500, minlength = 2, } local PreviousLine = '' function M.read_history () local histfile = tilde_expand( Option['histfile'] ) return prv.read_history ( histfile ) end M.read_history( Option['histfile'] ) local OldHistoryLength = prv.history_length() -- print('OldHistoryLength='..tostring(OldHistoryLength)) ------------------------ public functions ---------------------- function M.set_options ( tbl ) if tbl == nil then return end if type(tbl) ~= 'table' then die('set_options: argument must be a table, not '..type(tbl)) end local old_options = deepcopy(Option) for k,v in pairs(tbl) do if k == 'completion' then if type(v) ~= 'boolean' then die('set_options: completion must be boolean, not '..type(v)) end prv.tabcompletion ( v ) Option[k] = v elseif k == 'histfile' then if v ~= Option['histfile'] then if type(v) ~= 'string' then die('set_options: histfile must be string, not '..type(v)) end Option[k] = v prv.clear_history() local rc = M.read_history( Option['histfile'] ) -- 1.2 end elseif k == 'keeplines' or k == 'minlength' then if type(v) ~= 'number' then die('set_options: '..k..' must be number, not '..type(v)) end Option[k] = v elseif k == 'ignoredups' or k == 'auto_add' then if type(v) ~= 'boolean' then die('set_options: '..k..' must be boolean, not '..type(v)) end Option[k] = v else die('set_options: unrecognised option '..tostring(k)) end end return old_options end function M.readline ( prompt ) prompt = prompt or '' if type(prompt) ~= 'string' then die('readline: prompt must be a string, not '..type(prompt)) end local line = prv.readline ( prompt ) -- might be nil if EOF... if line == nil then return nil end -- 1.8 if Option['completion'] then line = string.gsub(line, ' $', '') -- 1.3, 2.0 end if Option['auto_add'] and line and line~='' and string.len(line)>=Option['minlength'] then if line ~= PreviousLine or not Option['ignoredups'] then prv.add_history(line) PreviousLine = line end end return line end function M.add_history ( str ) if type(str) ~= 'string' then die('add_history: str must be a string, not '..type(str)) end return prv.add_history ( str ) end function M.save_history ( ) if type(Option['histfile']) ~= 'string' then die('save_history: histfile must be a string, not ' .. type(Option['histfile'])) end if Option['histfile'] == '' then return end local histfile = tilde_expand( Option['histfile'] ) if type(Option['keeplines']) ~= 'number' then die('save_history: keeplines must be a number, not ' .. type(Option['keeplines'])) end local n = prv.history_length() if n > OldHistoryLength then touch(histfile) local rc = prv.append_history(n-OldHistoryLength, histfile) if rc ~= 0 then warn('append_history: '..prv.strerror(rc)) end rc = prv.history_truncate_file ( histfile, Option['keeplines'] ) if rc ~= 0 then warn('history_truncate_file: '..prv.strerror(rc)) end end return end function M.strerror ( errnum ) return prv.strerror(tonumber(errnum)) end -------------------- The Alternate Interface ------------------- function M.handler_install(prompt, linehandlerfunction) prompt = prompt or '' if type(prompt) ~= 'string' then die('handler_install: prompt must be a string, not '..type(prompt)) end if type(linehandlerfunction) ~= 'function' then die('handler_install: linehandlerfunction must be a function, not '.. type(linehandlerfunction)) end prv.callback_handler_install(prompt, linehandlerfunction) end M.read_char = prv.callback_read_char M.handler_remove = prv.callback_handler_remove -- M.sigcleanup will be nil unless C-readline.c was -- compiled with RL_VERSION_MAJOR = 7 or greater M.sigcleanup = prv.callback_sigcleanup -------------------- Custom Completion ------------------------ M.set_readline_name = prv.set_readline_name M.set_complete_function = prv.set_complete_function M.set_default_complete_function = prv.set_default_complete_function M.set_completion_append_character = prv.set_completion_append_character function M.set_complete_list(a) if type(a) ~= 'table' then die('set_complete_list: arg must be a table, not '..type(a)) end local completer_function = function(text, from, to) local incomplete = string.sub(text, from, to) local matches = {} for i,v in ipairs(a) do if incomplete == string.sub(v, 1, #incomplete) then matches[1 + #matches] = v end end return matches end M.set_complete_function(completer_function) end return M --[[ =pod =head1 NAME C - a simple interface to the I and I libraries =head1 SYNOPSIS local RL = require 'readline' RL.set_options{ keeplines=1000, histfile='~/.synopsis_history' } -- the Standard Interface local str = RL.readline('Please enter some filename: ') local save_options = RL.set_options{ completion=false } str = RL.readline('Please type a line which can include Tabs: ') RL.set_options(save_options) str = RL.readline('Now tab-filename-completion is working again: ') ... -- the Alternate Interface local poll = require 'posix.poll'.poll local line = nil local linehandler = function (str) line = str RL.handler_remove() RL.add_history(str) end RL.handler_install("prompt> ", linehandler) local fds = {[0] = {events={IN={true}}}} while true do poll(fds, -1) if fds[0].revents.IN then RL.read_char() -- only if there's something to be read else -- do some useful background task end if line then break end end print("got line: " .. line) -- Custom Completion local reserved_words = { 'and', 'assert', 'break', 'do', 'else', 'elseif', 'end', 'false', 'for', 'function', 'if', 'ipairs', 'local', 'nil', 'not', 'pairs', 'print', 'require', 'return', 'then', 'tonumber', 'tostring', 'true', 'type', 'while', } RL.set_complete_list(reserved_words) line = RL.readline('now it expands lua reserved words: ') ... RL.save_history() ; os.exit() =head1 DESCRIPTION This Lua module offers a simple calling interface to the GNU Readline/History Library. The function I is a wrapper, which invokes the GNU I, adds the line to the end of the History List, and then returns the line. Usually you call I before the program exits, so that the History List is saved to the I. Various options can be changed using the I function. The user can configure the GNU Readline (e.g. I or I keystrokes ?) with their individual I<~/.inputrc> file, see the I section of I. By default, the GNU I library dialogues with the user by reading from I and writing to I; This fits badly with applications that want to use I and I to input and output data. Therefore, this Lua module dialogues with the user on the controlling-terminal of the process (typically I) as returned by I. =head1 STANDARD INTERFACE =head3 RL.set_options{ histfile='~/.myapp_history', keeplines=100 } Returns the old options, so they can be restored later. The I option controls whether the line entered will be added to the History List, The default options are: auto_add = true, histfile = '~/.rl_lua_history', keeplines = 500, completion = true, ignoredups = true, minlength = 2, Lines shorter than the I option will not be put on the History List. Tilde expansion is performed on the I option. The I option must be a string, so don't set it to I, if you want to avoid reading or writing your History List to the filesystem, set I to the empty string. If you want no history behaviour (Up or Down arrows etc.) at all, then set set_options{ histfile='', auto_add=false, } =head3 RL.readline( prompt ) Displays the I and returns the text of the line the user enters. A blank line returns the empty string. If EOF is encountered while reading a line, and the line is empty, I is returned; if an EOF is read with a non-empty line, it is treated as a newline. If the I option is I (which is the default), the line the user enters will be added to the History List, unless it's shorter than I, or it's the same as the previous line and the I option is set. =head3 RL.save_history() Normally, you should call this function before your program exits. It saves the lines the user has entered onto the end of the I file. Then if necessary it truncates lines off the beginning of the I to confine it to I long. =head3 RL.add_history( line ) Adds the I to the History List. You'll only need this function if you want to assume complete control over the strings that get added, in which case you: RL.set_options{ auto_add=false, } and then after calling I you can process the I as you wish and call I if appropriate. =head1 ALTERNATE INTERFACE Some applications need to interleave keyboard I/O with file, device, or window system I/O, by using a main loop to select() on various file descriptors. With the Alernate Interface, readline can be invoked as a 'callback' function from an event loop. The Alternate Interface does not add to the history file, so you will probably want to call RL.add_history(s) explicitly =head3 RL.handler_install( prompt, linehandlerfunction ) This function sets up the terminal, installs a linehandler function that will receive the text of the line as an argument, and displays the string prompt. A typical linehandler function might be: linehandler = function (str) RL.add_history(str) RL.handler_remove() line = str -- line is a global, or an upvalue end =head3 RL.read_char() Whenever an application determines that keyboard input is available, it should call read_char(), which will read the next character from the current input source. If that character completes the line, read_char will invoke the linehandler function installed by handler_install to process the line. Before calling the linehandler function, the terminal settings are reset to the values they had before calling handler_install. If the linehandler function returns, and the line handler remains installed, the terminal settings are modified for Readline's use again. EOF is indicated by calling the linehandler handler with a nil line. Interface. =head3 RL.handler_remove() Restore the terminal to its initial state and remove the line handler. You may call this function from within the linehandler as well as independently. If the linehandler function does not exit the program, this function should be called before the program exits to reset the terminal settings. =head1 CUSTOM COMPLETION =head3 RL.set_complete_list( array_of_strings ) This function sets up custom completion of an array of strings. For example, the I might be the dictionary-words of a language, or the reserved words of a programming language. =head3 RL.set_complete_function( completer_function ) This is the lower-level function on which set_complete_list() is based. Its argument is a function which takes three arguments: the text of the line as it stands, and the indexes from and to, which delimit the segment of the text (for example, the word) which is to be completed. This syntax is the same as I The I must return an array of the possible completions. For example, the I of set_complete_list() is: local completer_function = function(text, from, to) local incomplete = string.sub(text, from, to) local matches = {} for i,v in ipairs(array_of_strings) do if incomplete == string.sub(v, 1, #incomplete) then matches[1 + #matches] = v end end return matches end but the completer_function can also have more complex behaviour. Because it knows the contents of the line so far, it could ask for a date in format B<18 Aug 2018> and offer three different completions for the three different fields. Or if the line so far seems to be in Esperanto it could offer completions in Esperanto, and so on. By default, after every completion readline appends a space to the string, so you can start the next word. You can change this space to another character by calling set_completion_append_character(s), which sets the append_character to the first byte of the string B. For example this sets it to the empty string: RL.set_completion_append_character('') It only makes sense to call I from within a completer_function. After the completer_function has executed, the readline library resets the append_character to the default space. Setting the append_character to C<','> or C<':'> or C<'.'> or C<'-'> may not behave as you expect when trying to tab-complete the following word, because I treats those characters as being part of a 'word', not as a delimiter between words. =head1 INSTALLATION This module is available as a LuaRock in http://luarocks.org/modules/peterbillam so you should be able to install it with the command: $ su Password: # luarocks install readline or: # luarocks install http://www.pjb.com.au/comp/lua/readline-2.2-0.rockspec It depends on the I library and its header-files; for example on Debian you may need: # aptitude install libreadline6 libreadline6-dev or on Centos you may need: # yum install readline-devel =head1 CHANGES 20210127 2.9 fix version number again 20210106 2.8 add set_readline_name() and fix version number 20200801 2.7 add 5.4 20180924 2.2 add set_completion_append_character 20180912 2.1 C code stack-bug fix in handler_calls_completion_callback 20180910 2.0 add set_complete_list and set_complete_function 20180901 1.9 add handler_install read_char and handler_remove 20151020 1.8 readline() returns nil correctly on EOF 20150422 1.7 works with lua5.3 20140608 1.5 switch pod and doc over to using moonrocks 20140519 1.4 installs as readline not Readline under luarocks 2.1.2 20131031 1.3 readline erases final space if tab-completion is used 20131020 1.2 set_options{histfile='~/d'} expands the tilde 20130921 1.1 uses ctermid() (usually /dev/tty) to dialogue with the user 20130918 1.0 first working version =head1 AUTHOR Peter Billam, http://www.pjb.com.au/comp/contact.html Alexander Adler, of the University of Frankfurt, contributed the Alternate Interface functions. =head1 SEE ALSO =over 3 man readline http://www.gnu.org/s/readline https://tiswww.case.edu/php/chet/readline/readline.html https://tiswww.case.edu/php/chet/readline/readline.html#SEC41 https://tiswww.case.edu/php/chet/readline/readline.html#SEC45 /usr/share/readline/inputrc ~/.inputrc http://lua-users.org/wiki/CompleteWithReadline http://luaposix.github.io/luaposix http://www.pjb.com.au http://www.pjb.com.au/comp/index.html#lua =back =cut ]] readline-3.0/C-readline.c0000644000076400007640000002622214036743336013403 0ustar pjbpjb/* C-readline.c - readline and history bindings for Lua This Lua5 module is Copyright (c) 2013, Peter J Billam www.pjb.com.au This module is free software; you can redistribute it and/or modify it under the same terms as Lua5 itself. */ #include #include /* #include strlen() & friends, including strerror */ /* #include isatty() */ /* --------------- from man readline -------------------- */ #include #include /* #include 2.8 20210106 strerror is not in string.h ? */ #include #include #include /* http://cnswww.cns.cwru.edu/php/chet/readline/rltop.html http://cnswww.cns.cwru.edu/php/chet/readline/readline.html#SEC52 (totally alarming :-( ) http://cnswww.cns.cwru.edu/php/chet/readline/history.html#IDX5 (only moderately alarming) http://cnswww.cns.cwru.edu/php/chet/readline/readline.html#IDX207 Variable: FILE * rl_instream The stdio stream from which Readline reads input. If NULL, Readline defaults to stdin. Variable: FILE * rl_outstream The stdio stream to which Readline performs output. If NULL, Readline defaults to stdout. http://man7.org/linux/man-pages/man3/ctermid.3.html http://man7.org/linux/man-pages/man3/fopen.3.html http://man7.org/linux/man-pages/man3/fileno.3.html http://man7.org/linux/man-pages/man3/isatty.3.html */ /* see Programming in Lua p.233 */ /* apparently a BUG: after being invoked, c_readline leaves SIGWINCH handling messed up, and the kernel unable to follow further changes in size; thence also tput, stty size, resize, $COLS $ROWS, etc... Only xwininfo -id $WINDOWID seems to get up-to-date data. Surprisingly, rl_catch_sigwinch and rl_cleanup_after_signal have no effect http://cnswww.cns.cwru.edu/php/chet/readline/readline.html#SEC43 */ static int c_readline(lua_State *L) { /* prompt in, line out */ size_t len; const char *prompt = lua_tolstring(L, 1, &len); char buffer[L_ctermid]; const char *devtty = ctermid(buffer); /* 20130919 1.1 */ FILE *tty_stream; if (devtty != NULL) { tty_stream = fopen(devtty, "a+"); if (tty_stream != NULL) { /* int tty_fd = fileno(tty_stream); */ rl_instream = tty_stream; rl_outstream = tty_stream; } } /* rl_catch_sigwinch = 0; rl_set_signals(); no effect :-( 1.3 */ const char *line = readline(prompt); /* rl_cleanup_after_signal(); rl_clear_signals(); no effect :-( 1.3 */ lua_pushstring(L, line); if (tty_stream != NULL) { fclose(tty_stream); } return 1; } static int c_tabcompletion(lua_State *L) { /* Lua stack: is_on */ int is_on = lua_toboolean(L, 1); if (is_on) { rl_bind_key ('\t', rl_complete); } else { rl_bind_key ('\t', rl_insert); } return 0; } static int c_history_length(lua_State *L) { /* void in, length out */ lua_Integer n = history_length; lua_pushinteger(L, n); return 1; } static int c_using_history(lua_State *L) { /* void in, void out */ using_history(); return 0; } static int c_clear_history(lua_State *L) { /* void in, void out */ clear_history(); return 0; } static int c_add_history(lua_State *L) { /* Lua stack: str to be added */ size_t len; const char *str = lua_tolstring(L, 1, &len); add_history(str); return 0; } static int c_append_history(lua_State *L) { /* num,filename in, rc out */ lua_Integer num = lua_tointeger(L, 1); size_t len; const char *filename = lua_tolstring(L, 2, &len); lua_Integer rc = append_history(num, filename); lua_pushinteger(L, rc); return 1; } static int c_read_history(lua_State *L) { /* filename in, returncode out */ size_t len; const char *filename = lua_tolstring(L, 1, &len); lua_Integer rc = read_history(filename); lua_pushinteger(L, rc); return 1; /* so maybe we should provide access to char *strerror(int errnum); */ } static int c_strerror(lua_State *L) { /* errnum in, errstr out */ lua_Integer errnum = lua_tointeger(L, 1); const char * str = strerror(errnum); lua_pushstring(L, str); return 1; } static int c_stifle_history(lua_State *L) { /* Lua stack: num */ lua_Integer num = lua_tointeger(L, 1); stifle_history(num); return 0; } static int c_write_history(lua_State *L) { /* filename in, returncode out */ size_t len; const char *filename = lua_tolstring(L, 1, &len); lua_Integer rc = write_history(filename); lua_pushinteger(L, rc); return 1; } static int c_history_truncate_file(lua_State *L) { /* filename,num in rc out */ size_t len; const char *filename = lua_tolstring(L, 1, &len); lua_Integer num = lua_tointeger(L, 2); lua_Integer rc = history_truncate_file(filename, num); lua_pushinteger(L, rc); return 1; } /* ------------------ alternate interface ---------------------- */ /* * saves the last given callback handler and its Lua state * * ouch: this is not reentrant! */ static int alternate_interface_callback = LUA_NOREF; static lua_State *last_state = NULL; static FILE *callback_tty_stream; /* <-- new */ /* * calls the registered callback handler with `line` */ static void handler_calls_lua_callback (char *line) { lua_rawgeti(last_state, LUA_REGISTRYINDEX, alternate_interface_callback); lua_pushstring(last_state, line); lua_call(last_state, 1, 0); } static int c_callback_handler_install(lua_State *L) { char buffer[L_ctermid]; const char *prompt; /* copied from c_readline */ const char *devtty = ctermid(buffer); /* 20130919 1.1 */ if (devtty != NULL) { callback_tty_stream = fopen(devtty, "a+"); if (callback_tty_stream != NULL) { rl_instream = callback_tty_stream; rl_outstream = callback_tty_stream; } } prompt = luaL_checkstring(L, 1); luaL_checktype(L, 2, LUA_TFUNCTION); luaL_unref(L, LUA_REGISTRYINDEX, alternate_interface_callback); alternate_interface_callback = luaL_ref(L, LUA_REGISTRYINDEX); rl_callback_handler_install(prompt, handler_calls_lua_callback); last_state = L; return 0; } static int c_callback_read_char(lua_State *L) { rl_callback_read_char(); return 0; } #ifdef RL_VERSION_MAJOR #if RL_VERSION_MAJOR >= 7 static int c_callback_sigcleanup(lua_State *L) { rl_callback_sigcleanup(); return 0; } #endif #endif static int c_callback_handler_remove(lua_State *L) { if (callback_tty_stream != NULL) { fclose(callback_tty_stream); } /* new */ rl_callback_handler_remove(); return 0; } /* --------------- interface to custom completion ------------- */ /* * this isn't reentrant either — and reuses last_state */ static int complete_callback = LUA_NOREF; static char **completions = NULL; char *dummy_generator(const char *text, int state) { return completions[state]; } static char **handler_calls_completion_callback(const char *text, int start, int end) { size_t i; /* ? int ? */ size_t number_of_completions; rl_attempted_completion_over = 1; lua_settop(last_state, 0); /* 2.1 */ lua_rawgeti(last_state, LUA_REGISTRYINDEX, complete_callback); lua_pushstring(last_state, rl_line_buffer); lua_pushinteger(last_state, (lua_Integer) start+1); lua_pushinteger(last_state, (lua_Integer) end+1); lua_call(last_state, 3, 1); luaL_checktype(last_state, 1, LUA_TTABLE); /* lua_rawlen is not available in lua5.1. Use lua_objlen instead */ /* http://www.lua.org/manual/5.1/manual.html#lua_objlen */ #if LUA_VERSION_NUM >= 502 number_of_completions = lua_rawlen(last_state, 1); #else number_of_completions = lua_objlen(last_state, 1); #endif if (!number_of_completions) return NULL; /* malloc never fails due to overcommit */ completions = malloc(sizeof(char *)*(1+number_of_completions)); for (i = 0; i < number_of_completions; i++) { size_t length; const char *tmp; lua_rawgeti(last_state, 1, i+1); tmp = luaL_checkstring(last_state, 2); #if LUA_VERSION_NUM >= 502 length = 1 + lua_rawlen(last_state, 2); #else length = 1 + lua_objlen(last_state, 2); #endif completions[i] = malloc(sizeof(char)*length); strncpy(completions[i], tmp, length); lua_remove(last_state, 2); } /* sentinel NULL means: end of list */ completions[number_of_completions] = NULL; return rl_completion_matches(text, dummy_generator); } static int c_set_readline_name(lua_State *L) { luaL_checktype(L, 1, LUA_TSTRING); rl_readline_name = (const char *) lua_tolstring(L, 1, NULL); /* 2.8 */ return 0; } static int c_set_complete_function(lua_State *L) { luaL_checktype(L, 1, LUA_TFUNCTION); luaL_unref(L, LUA_REGISTRYINDEX, complete_callback); complete_callback = luaL_ref(L, LUA_REGISTRYINDEX); rl_attempted_completion_function = handler_calls_completion_callback; last_state = L; return 0; } static int c_set_default_completer(lua_State *L) { rl_attempted_completion_function = NULL; return 0; } static int c_set_completion_append_character(lua_State *L) { /* 2.2 */ size_t len; const char *s = lua_tolstring(L, -1, &len); /* PiL4 p.280 */ rl_completion_append_character = s[0]; return 0; } /* ----------------- evolved from C-midialsa.c ---------------- */ struct constant { /* Gems p. 334 */ const char * name; int value; }; static const struct constant constants[] = { /* {"Version", Version}, */ {NULL, 0} }; static const luaL_Reg prv[] = { /* private functions */ {"add_history", c_add_history}, {"append_history", c_append_history}, {"clear_history", c_clear_history}, {"history_length", c_history_length}, {"history_truncate_file", c_history_truncate_file}, {"read_history", c_read_history}, {"readline", c_readline}, {"stifle_history", c_stifle_history}, {"strerror", c_strerror}, {"tabcompletion", c_tabcompletion}, {"using_history", c_using_history}, {"callback_handler_install", c_callback_handler_install}, {"callback_read_char", c_callback_read_char}, #ifdef RL_VERSION_MAJOR #if RL_VERSION_MAJOR >= 7 {"callback_sigcleanup", c_callback_sigcleanup}, #endif #endif {"callback_handler_remove", c_callback_handler_remove}, {"set_readline_name", c_set_readline_name}, {"set_complete_function", c_set_complete_function}, {"set_default_complete_function", c_set_default_completer}, {"set_completion_append_character", c_set_completion_append_character}, {NULL, NULL} }; static int initialise(lua_State *L) { /* Lua Programming Gems p. 335 */ /* Lua stack: aux table, prv table, dat table */ int index; /* define constants in module namespace */ for (index = 0; constants[index].name != NULL; ++index) { lua_pushinteger(L, constants[index].value); lua_setfield(L, 3, constants[index].name); } /* lua_pushvalue(L, 1); * set the aux table as environment */ /* lua_replace(L, LUA_ENVIRONINDEX); unnecessary here, fortunately, because it fails in 5.2 */ lua_pushvalue(L, 2); /* register the private functions */ #if LUA_VERSION_NUM >= 502 luaL_setfuncs(L, prv, 0); /* 5.2 */ return 0; #else luaL_register(L, NULL, prv); /* 5.1 */ return 0; #endif } int luaopen_readline(lua_State *L) { lua_pushcfunction(L, initialise); return 1; } readline-3.0/doc/0000755000076400007640000000000014036743336012035 5ustar pjbpjbreadline-3.0/doc/readline.html0000644000076400007640000004520114036743336014510 0ustar pjbpjbreadline.lua

       readline.lua


NAME

readline - a simple interface to the readline and history libraries


SYNOPSIS

 local RL = require 'readline'
 RL.set_options{ keeplines=1000, histfile='~/.synopsis_history' }
 RL.set_readline_name('fennel')

-- the Standard Interface
 local str = RL.readline('Please enter some filename: ')
 local save_options = RL.set_options{ completion=false }
 str = RL.readline('Please type a line which can include Tabs: ')
 RL.set_options(save_options)
 str = RL.readline('Now tab-filename-completion is working again: ')
 ...

 -- the Alternate Interface
 local poll = require 'posix.poll'.poll
 local line = nil
 local linehandler = function (str)
    RL.add_history(str)
    RL.handler_remove()
    line = str
 end
 RL.handler_install("prompt> ", linehandler)
 local fds = {[0] = {events={IN={true}}}}
 while true do
    poll(fds, -1)
    if fds[0].revents.IN then
       RL.read_char()  -- only if there's something to be read
    else
       -- do some useful background task
    end
    if line then break end
 end
 print("got line: " .. line)

 -- Custom Completion
 local reserved_words = {
   'and', 'assert', 'break', 'do', 'else', 'elseif', 'end', 'false',
   'for', 'function', 'if', 'ipairs', 'local', 'nil', 'not', 'pairs',
   'print', 'require', 'return', 'then', 'tonumber', 'tostring',
   'true', 'type', 'while',
 }
 RL.set_complete_list(reserved_words)
 line = RL.readline('now it expands lua reserved words: ')

 ...
 RL.save_history() ; os.exit()


DESCRIPTION

This Lua module offers a simple calling interface to the GNU Readline/History Library.

The function readline() is a wrapper, which invokes the GNU readline, adds the line to the end of the History List, and then returns the line. Usually you call save_history() before the program exits, so that the History List is saved to the histfile.

Various options can be changed using the set_options{} function.

The user can configure the GNU Readline (e.g. vi or emacs keystrokes ?) with their individual ~/.inputrc file, see the INITIALIZATION FILE section of man readline.

By default, the GNU readline library dialogues with the user by reading from stdin and writing to stdout; this fits very badly with applications that want to use stdin and stdout to input and output data. Therefore, this Lua module dialogues with the user on the controlling-terminal of the process (typically /dev/tty) as returned by ctermid().

Most of readline's Alternate Interface is now included, namely   handler_install,   read_char   and handler_remove.
Some applications need to interleave keyboard I/O with file, device, or window system I/O, typically by using a main loop to select() on various file descriptors.   To accommodate this need, readline can also be invoked as a 'callback' function from an event loop, and the Alternate Interface offers functions to do this.
The Alternate Interface does offer tab-completion; but it does not add to the history file, so you will probably want to call RL.add_history(s) explicitly. See handler_install()

Access to readline's Custom Completion is now provided.

This module does not work lua -i because that runs its own readline, and the two conflict with each other.


STANDARD INTERFACE

RL.set_options{ histfile='~/.myapp_history', keeplines=100 }

Returns the old options, so they can be restored later. The auto_add option controls whether the line entered will be added to the History List, The default options are:
  auto_add = true,
  histfile = '~/.rl_lua_history',
  keeplines = 500,
  completion = true,
  ignoredups = true,
  minlength = 2,

Lines shorter than the minlength option will not be put on the History List. Tilde expansion is performed on the histfile option. The histfile option must be a string, so don't set it to nil, if you want to avoid reading or writing your History List to the filesystem, set histfile to the empty string. If you want no history behaviour (Up or Down arrows etc.) at all, then set
  set_options{ histfile='', auto_add=false, }

RL.set_readline_name( 'myapp' )

Sets the internal libreadline variable rl_readline_name for use with conditional directives in .inputrc (see the manual).
It should be invoked once, before calling readline(), so that the name is set before .inputrc is sourced.
For example: if, in the initialization before first readline prompt, you
  RL.set_readline_name('fennel')

then the call to readline() would execute this conditional in .inputrc
  $if fennel
    set blink-matching-paren On
    set enable-bracketed-paste On
  $endif

RL.readline( prompt )

Displays the prompt and returns the text of the line the user enters. A blank line returns the empty string. If EOF is encountered while reading a line, and the line is empty, nil is returned; if an EOF is read with a non-empty line, it is treated as a newline.

If the auto_add option is true (which is the default), the line the user enters will be added to the History List, unless it's shorter than minlength, or it's the same as the previous line and the ignoredups option is set.

RL.save_history()

Normally, you should call this function once, just before your program exits. It saves the lines the user has entered onto the end of the histfile file. Then if necessary it truncates lines off the beginning of the histfile to confine it to keeplines long.

RL.add_history( line )

Adds the line to the History List.
With the Standard Interface, you'll only need this function if you want to assume complete control over the strings that get added, in which case you set:
  RL.set_options{ auto_add=false, }
and then after calling readline(prompt) you can process the line as you wish and call add_history(line) if appropriate.

But with the Alternative Interface, you have to call add_history(line) yourself, even if {auto_add=true}
You should do this in the linehandler function, see below.

ALTERNATE INTERFACE

Some applications need to interleave keyboard I/O with file, device, or window system I/O, by using a main loop to select() on various file descriptors.
With the Alernate Interface, readline can be invoked as a 'callback' function from an event loop.

The Alternate Interface does not add to the history file, so you will probably want to call RL.add_history(s) explicitly.
You should do this within the linehandler function !
(This constraint is due to what may be an unadvertised quirk of libreadline.)

RL.handler_install( prompt, linehandlerfunction )

This function sets up the terminal, installs a linehandler function that will receive the text of the line as an argument, and displays the string prompt.   A typical linehandler function might be:
  linehandler = function (str)
    RL.add_history(str)
    RL.handler_remove()
    line = str   -- line is a global, or an upvalue
  end

RL.read_char()

Whenever an application determines that keyboard input is available, it should call read_char(), which will read the next character from the current input source. If that character completes the line, read_char will invoke the linehandler function installed by handler_install to process the line.
Before calling the linehandler function, the terminal settings are reset to the values they had before calling handler_install. If the linehandler function returns, and the line handler remains installed, the terminal settings are modified for Readline's use again. EOF is indicated by calling the linehandler handler with a nil line.

RL.handler_remove()

Restore the terminal to its initial state and remove the line handler. You may call this function from within the linehandler as well as independently. If the linehandler function does not exit the program, this function should be called before the program exits to reset the terminal settings.

CUSTOM COMPLETION

RL.set_complete_list( array_of_strings )

This function sets up custom completion of an array of strings.
For example, the array_of_strings might be the dictionary-words of a language, or the reserved words of a programming language.

RL.set_complete_function( completer_function )

This is the lower-level function on which set_complete_list() is based. Its argument is a function which takes three arguments: the text of the line as it stands, and the indexes from and to, which delimit the segment of the text (for example, the word) which is to be completed. This syntax is the same as string.sub(text, from, to)
The completer_function must return an array of the possible completions.
For example, the completer_function of set_complete_list() is:

  local completer_function = function(text, from, to)
     local incomplete = string.sub(text, from, to)
     local matches = {}
     for i,v in ipairs(array_of_strings) do
        if incomplete == string.sub(v, 1, #incomplete) then
           matches[1 + #matches] = v
        end
     end
     return matches
  end
but the completer_function can also have more complex behaviour. Because it knows the contents of the line so far, it could ask for a date in format 18 Aug 2018 and offer three different completions for the three different fields.
Or if the line so far seems to be in Esperanto it could offer completions in Esperanto, and so on.

By default, after every completion readline appends a space to the string, so you can start the next word. You can change this space to another character by calling set_completion_append_character(s), which sets the append_character to the first byte of the string s. For example, this sets it to the empty string:
  RL.set_completion_append_character('')
It only makes sense to call set_completion_append_character from within a completer_function.
After the completer_function has executed, the readline library resets the append_character to the default space.
Setting the append_character to ',' or ':' or '.' or '-' may not behave as you expect when trying to tab-complete the following word, because readline treats those characters as being part of a 'word', not as a delimiter between words.


INSTALLATION

This module is available as a LuaRock in luarocks.org/modules/peterbillam so you should be able to install it with the command:
  $ su
  Password:
  # luarocks install readline

or:
  # luarocks install https://pjb.com.au/comp/lua/readline-3.0-0.rockspec

If this results in an error message such as:
  Error: Could not find expected file libreadline.a, or libreadline.so,
then you need to find the appropriate directory with:
  find /usr/lib -name 'libreadline.*' -print
and then invoke:
  luarocks install \
  https://www.pjb.com.au/comp/lua/readline-3.0-0.rockspec \
  READLINE_INCDIR=/usr/local/Cellar/readline/8.1/include \
  READLINE_LIBDIR=/usr/local/Cellar/readline/8.1/lib \
  HISTORY_INCDIR=/usr/local/Cellar/readline/8.1/include \
  HISTORY_LIBDIR=/usr/local/Cellar/readline/8.1/lib # or wherever

accordingly.

It depends on the readline library and its header-files; for example on Debian you may need:
  # aptitude install libreadline6 libreadline6-dev

or on Centos you may need:
  # yum install readline-devel

You can see the source-code in:
  https://pjb.com.au/comp/lua/readline-3.0.tar.gz


CHANGES

 20210418 3.0 pass READLINE_INCDIR and READLINE_LIBDIR to gcc
 20210127 2.9 fix version number again
 20210106 2.8 include string.h
 20200801 2.7 add lua 5.4
 20200409 2.6 jaawerth: added set_readline_name()
 20190110 2.5 fix a lua_rawlen v. lua_objlen bug if using lua 5.1
 20180924 2.2 add set_completion_append_character
 20180912 2.1 C code stack-bug fix in handler_calls_completion_callback
 20180910 2.0 add set_complete_list and set_complete_function
 20180827 1.9 add handler_install read_char and handler_remove
 20151020 1.8 readline() returns nil correctly on EOF
 20150421 1.7 include lua5.3, and move pod and doc back to luarocks.org
 20150416 1.6 readline specified as an external dependency
 20140608 1.5 switch pod and doc over to using moonrocks
 20140519 1.4 installs as readline not Readline under luarocks 2.1.2
 20131031 1.3 readline erases final space if tab-completion is used
 20131020 1.2 set_options{histfile='~/d'} expands the tilde
 20130921 1.1 uses ctermid() (usually /dev/tty) to dialogue with the user
 20130918 1.0 first working version


AUTHORS

Peter Billam   https://pjb.com.au/comp/contact.html
Alexander Adler, University of Frankfurt, contributed the Alternate Interface and Custom Completion
Jesse Wertheim, one of the developers of Fennel, contributed the set_readline_name() function.


SEE ALSO

man readline   http://www.gnu.org/s/readline
https://tiswww.case.edu/php/chet/readline/readline.html
https://tiswww.case.edu/php/chet/readline/readline.html#SEC28   Readline Variables
https://tiswww.case.edu/php/chet/readline/readline.html#SEC41
https://tiswww.case.edu/php/chet/readline/readline.html#SEC45
/usr/share/readline/inputrc   ~/.inputrc
http://lua-users.org/wiki/CompleteWithReadline
http://luaposix.github.io/luaposix
fennel-lang.org/
terminfo.lua
https://pjb.com.au
https://pjb.com.au/comp/index.html#lua

readline-3.0/test/0000755000076400007640000000000014036743336012247 5ustar pjbpjbreadline-3.0/test/test_rl.lua0000644000076400007640000002042614036743336014432 0ustar pjbpjb#! /usr/local/bin/lua local RL = require 'readline' local poll = require 'posix.poll'.poll -- local TC = require 'testcases' -- luarocks install http://pjb.com.au/comp/lua/testcases-0.1-0.rockspec --------------------------- infrastructure ----------------------- local eps = .000000001 function equal(x, y) -- unused here if #x ~= #y then return false end local i; for i in pairs(x) do if math.abs(x[i]-y[i]) > eps then return false end end return true end -- use Test::Simple tests => 6; local Test = 73 ; local i_test = 0; local Failed = 0; function ok(b,s) i_test = i_test + 1 if b then io.write('ok '..i_test..' - '..s.."\n") return true else io.write('not ok '..i_test..' - '..s.."\n") Failed = Failed + 1 return false end end local function qw(s) -- t = qw([[ foo bar baz ]]) local t = {} for x in s:gmatch("([-%s]+)") do t[#t+1] = x end return t end local function uname_minus_s() local pipe = assert(io.popen('uname -s')) local uname_output = pipe:read('*all') pipe:close() return string.gsub(uname_output, '%s$', '') end -- strict.lua checks uses of undeclared global variables -- All global variables must be 'declared' through a regular assignment -- (even assigning nil will do) in a main chunk before being used -- anywhere or assigned to inside a function. local mt = getmetatable(_G) if mt == nil then mt = {} setmetatable(_G, mt) end mt.__declared = {} mt.__newindex = function (t, n, v) if not mt.__declared[n] then local w = debug.getinfo(2, "S").what if w ~= "main" and w ~= "C" then error("assign to undeclared variable '"..n.."'", 2) end mt.__declared[n] = true end rawset(t, n, v) end mt.__index = function (t, n) if not mt.__declared[n] and debug.getinfo(2, "S").what ~= "C" then error("variable '"..n.."' is not declared", 2) end return rawget(t, n) end ----------------------- here we go... ------------------------- print('Testing readline.lua '..RL.Version..', '..RL.VersionDate.. ' on '..uname_minus_s()) if not ok(type(RL) == 'table', 'type of RL is table') then print('type was '..type(RL)) end RL.set_completion_append_character('') RL.set_completion_append_character(' ') RL.set_completion_append_character('X') -- for k,v in pairs(RL) do print(k,tostring(v)) end local filename = '/tmp/test_rl_history' os.remove(filename) RL.set_options{histfile=filename , ignoredups=false} print('About to test the Alternative Interface ...') local s0 = nil RL.handler_install("Tab-completion should work: ", function(s) s0 = s RL.handler_remove() end) local fds = {[0] = {events={IN={true}}}} while true do poll(fds, -1) if fds[0].revents.IN then -- read only if there's something to be read RL.read_char() else -- do some useful background task end if s0 then break end -- don't add to the history this time... end print('About to test the standard interface ...') print('Please make all answers longer than two characters !') local s1 = RL.readline('Please enter something: ') if not ok(type(s1)=='string', "readline returned "..s1) then print('xc='..tostring(xc)..' xv='..tostring(xv)) end local s2 = RL.readline('this time Up-arrow should work: ') local s3 = RL.readline('enter a filename and test Tab-completion: ') local save = RL.set_options{completion=false} local s4 = RL.readline('now Tab-completion should be disabled: ') RL.set_options(save) local s5 = RL.readline('now it should be re-enabled :-) ') RL.set_options{auto_add=false} local s6 = RL.readline('this answer should not get added into the history: ') RL.set_options(save) local s7 = RL.readline('now it should be re-enabled :-) ') print('About to test the Alternative Interface again ...') line = nil local linehandler = function(s) RL.handler_remove() RL.add_history(s) line = s end RL.handler_install("Please enter something: ", linehandler) fds = {[0] = {events={IN={true}}}} while true do poll(fds, -1) if fds[0].revents.IN then -- read only if there's something to be read RL.read_char() else -- do some useful background task end if line then s8 = line ; break end end local reserved_words = { 'and', 'assert', 'break', 'do', 'else', 'elseif', 'end', 'false', 'for', 'function', 'if', 'ipairs(', 'io.flush(', 'io.input(', 'io.lines(', 'io.open(', 'io.output(', 'io.popen(', 'io.read(', 'io.seek(', 'io.setvbuf(', 'io.stderr', 'io.stdin', 'io.stdout', 'io.tmpfile(', 'io.write(', 'local', 'math.asin(', 'math.ceil(', 'math.cos(', 'math.deg(', 'math.floor(', 'math.huge(', 'math.max(', 'math.maxinteger(', 'math.mod(', 'math.rad(', 'math.random(', 'math.randomseed(', 'math.sin(', 'math.tan(', 'math.tointeger(', 'math.type(', 'math.ult(', 'nil', 'not', 'print', 'require', 'return', 'os.clock(', 'os.date(', 'os.difftime(', 'os.execute(', 'os.exit(', 'os.getenv(', 'os.remove(', 'os.rename(', 'os.time(', 'string.byte(', 'string.char(', 'string.dump(', 'string.find(', 'string.format(', 'string.gmatch(', 'string.gsub(', 'string.len(', 'string.lower(', 'string.match(', 'string.pack(', 'string.rep(', 'string.sub(', 'string.unpack(', 'string.unpack(', 'string.upper(', 'table.concat(', 'table.insert(', 'table.move(', 'table.pack(', 'table.remove(', 'table.sort(', 'table.unpack(', 'then', 'tostring(', 'tonumber(', 'true', 'type', 'while', 'pairs(', 'print', } RL.set_complete_list(reserved_words) local s9 = RL.readline('now it expands lua reserved words: ') line = nil RL.handler_install("the same but with the Alternate Interface: ", linehandler) -- fds = {[0] = {events={IN={true}}}} -- RL.set_complete_list(reserved_words) while true do poll(fds, -1) if fds[0].revents.IN then -- read only if there's something to be read RL.read_char() else -- do some useful background task end if line then sA = line ; break end end -- print (sA) -- print(type(TC.empty_the_stack)) --[[ local comp_func = function () TC.empty_the_stack() return {'gloop'} end RL.set_complete_function(comp_func) local sB = RL.readline("now the complete_function empties the stack: ") local reg = debug.getregistry() for k,v in pairs(reg) do print(k,v) end comp_func = function () TC.dump_the_stack('dummy-string', nil, 42) return {'gleep'} end RL.set_complete_function(comp_func) local sC = RL.readline("now the complete_function dumps the stack: ") ]] comp_func_2 = function () RL.set_completion_append_character('') return {'glurp'} end RL.set_complete_function(comp_func_2) local sD = RL.readline("now the completion_append_character is \\0: ") comp_func_3 = function () return {'glork'} end RL.set_complete_function(comp_func_3) local sD = RL.readline("the completion_append_character is ' ' again: ") RL.save_history() print('Now checking the saved histfile:') local F = assert(io.open(filename)) local lines = {} for line in F:lines() do lines[#lines+1] = line end F:close() -- os.remove(filename) if not ok(lines[1] == s1, 'line 1 was '..s1) then print('lines[1]='..tostring(lines[1])..' s1='..tostring(s1)) end if not ok(lines[2] == s2, 'line 2 was '..s2) then print('lines[2]='..tostring(lines[2])..' s2='..tostring(s2)) end if not ok(lines[3] == s3, 'line 3 was '..s3) then print('lines[3]='..tostring(lines[3])..' s3='..tostring(s3)) end if not ok(lines[4] == s4, 'line 4 was '..s4) then print('lines[4]='..tostring(lines[4])..' s4='..tostring(s4)) end if not ok(lines[5] == s5, 'line 5 was '..s5) then print('lines[5]='..tostring(lines[5])..' s5='..tostring(s5)) end if not ok(lines[6] == s7, 'line 6 was '..s7) then print('lines[6]='..tostring(lines[6])..' s7='..tostring(s7)) end if not ok(lines[7] == s8, 'line 7 was '..s8) then print('lines[7]='..tostring(lines[7])..' s8='..tostring(s8)) end if not ok(lines[8] == s9, 'line 8 was '..s9) then print('lines[8]="'..tostring(lines[8])..'" s9="'..tostring(s9)..'"') end if not ok(lines[9] == sA, 'line 9 was '..sA) then print('lines[9]="'..tostring(lines[9])..'" sA="'..tostring(sA)..'"') end --[[ if not ok(lines[10] == sB, 'line 10 was '..sB) then print('lines[10]="'..tostring(lines[10])..'" sB="'..tostring(sB)..'"') end if not ok(lines[11] == sC, 'line 11 was '..sC) then print('lines[11]="'..tostring(lines[11])..'" sC="'..tostring(sC)..'"') end ]] if Failed == 0 then print('Passed all '..i_test..' tests :-)') else print('Failed '..Failed..' tests out of '..i_test) end os.exit()