/* * Copyright (c) 2000, 2001 by Martin C. Shepherd. * * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, and/or sell copies of the Software, and to permit persons * to whom the Software is furnished to do so, provided that the above * copyright notice(s) and this permission notice appear in all copies of * the Software and that both the above copyright notice(s) and this * permission notice appear in supporting documentation. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Except as contained in this notice, the name of a copyright holder * shall not be used in advertising or otherwise to promote the sale, use * or other dealings in this Software without prior written authorization * of the copyright holder. */ #include #include #include #include #include #include #include #include #include "libtecla.h" /* * Encapsulate the resources needed by this demo. */ typedef struct { GetLine *gl; /* The line editor */ PathCache *pc; /* A cache of executables in the user's path */ PcaPathConf *ppc; /* The configuration argument of pca_path_completions() */ } DemoRes; /* * The following functions allocate and free instances of the above * structure. */ static DemoRes *new_DemoRes(void); static DemoRes *del_DemoRes(DemoRes *res); /* * Search backwards for the start of a pathname. */ static char *start_of_path(const char *string, int back_from); /* * Find the array indexes of the first character of the first * space-delimited word in the specified string, and of the character * that follows it. */ static int get_word_limits(const char *string, int *wa, int *wb); /* * This is the demonstration completion callback function (defined below). */ static CPL_MATCH_FN(demo_cpl_fn); /*....................................................................... * This demo takes no arguments. It reads lines of input until the * word 'exit' is entered, or C-d is pressed. It replaces the default * tab-completion callback with one which when invoked at the start of * a line, looks up completions of commands in the user's execution * path, and when invoked in other parts of the line, reverts to * normal filename completion. Whenever a new line is entered, it * extracts the first word on the line, looks it up in the user's * execution path to see if it corresponds to a known executable file, * and if so, displays the full pathname of the file, along with the * remaining arguments. */ int main(int argc, char *argv[]) { char *line; /* A line of input */ DemoRes *res; /* The resources of the demo */ int wa,wb; /* The delimiting indexes of a word in line[] */ int major,minor,micro; /* The version number of the library */ /* * Allocate the resources needed by this demo. */ res = new_DemoRes(); if(!res) return 1; /* * If the user has the LC_CTYPE or LC_ALL environment variables set, * enable display of characters corresponding to the specified locale. */ (void) setlocale(LC_CTYPE, ""); /* * Lookup and display the version number of the library. */ libtecla_version(&major, &minor, µ); printf("Welcome to the demo2 program of libtecla version %d.%d.%d\n", major, minor, micro); /* * Read lines of input from the user and print them to stdout. */ do { /* * Get a new line from the user. */ line = gl_get_line(res->gl, "$ ", NULL, 0); if(!line) break; /* * Work out the extent of the first word in the input line, and * try to identify this as a command in the path, displaying the * full pathname of the match if found. */ if(get_word_limits(line, &wa, &wb) == 0) { char *cmd = pca_lookup_file(res->pc, line + wa, wb-wa, 0); if(cmd) { printf("Command=%s\n", cmd); printf("Arguments=%s", line+wb); } else { printf("Command not found\n"); }; }; /* * If the user types "exit", quit the program. */ if(strcmp(line, "exit\n")==0) break; } while(1); /* * Clean up. */ res = del_DemoRes(res); return 0; } /*....................................................................... * This completion callback searches for completions of executables in * the user's path when invoked on a word at the start of the path, and * performs normal filename completion elsewhere. */ static CPL_MATCH_FN(demo_cpl_fn) { /* * Get the resource object that was passed to gl_customize_completion(). */ DemoRes *res = (DemoRes *) data; /* * Find the start of the filename prefix to be completed, searching * backwards for the first unescaped space, or the start of the line. */ char *start = start_of_path(line, word_end); /* * Skip spaces preceding the start of the prefix. */ while(start > line && isspace((int)(unsigned char) start[-1])) start--; /* * If the filename prefix is at the start of the line, attempt * to complete the filename as a command in the path. Otherwise * perform normal filename completion. */ return (start == line) ? pca_path_completions(cpl, res->ppc, line, word_end) : cpl_file_completions(cpl, NULL, line, word_end); } /*....................................................................... * Search backwards for the potential start of a filename. This * looks backwards from the specified index in a given string, * stopping at the first unescaped space or the start of the line. * * Input: * string const char * The string to search backwards in. * back_from int The index of the first character in string[] * that follows the pathname. * Output: * return char * The pointer to the first character of * the potential pathname, or NULL on error. */ static char *start_of_path(const char *string, int back_from) { int i, j; /* * Search backwards from the specified index. */ for(i=back_from-1; i>=0; i--) { int c = string[i]; /* * Stop on unescaped spaces. */ if(isspace((int)(unsigned char)c)) { /* * The space can't be escaped if we are at the start of the line. */ if(i==0) break; /* * Find the extent of the escape characters which precedes the space. */ for(j=i-1; j>=0 && string[j]=='\\'; j--) ; /* * If there isn't an odd number of escape characters before the space, * then the space isn't escaped. */ if((i - 1 - j) % 2 == 0) break; }; }; return (char *)string + i + 1; } /*....................................................................... * Create a new DemoRes object containing the resources needed by the * demo. * * Output: * return DemoRes * The new object, or NULL on error. */ static DemoRes *new_DemoRes(void) { DemoRes *res; /* The object to be returned */ /* * Allocate the container. */ res = (DemoRes *)malloc(sizeof(DemoRes)); if(!res) { fprintf(stderr, "new_DemoRes: Insufficient memory.\n"); return NULL; }; /* * Before attempting any operation that might fail, initialize the * container at least up to the point at which it can safely be passed * to del_DemoRes(). */ res->gl = NULL; res->pc = NULL; res->ppc = NULL; /* * Create the line editor, specifying a max line length of 500 bytes, * and 10000 bytes to allocate to storage of historical input lines. */ res->gl = new_GetLine(500, 10000); if(!res->gl) return del_DemoRes(res); /* * Enable text attribute formatting directives in prompt strings. */ gl_prompt_style(res->gl, GL_FORMAT_PROMPT); /* * Allocate a cache of the executable files found in the user's path. */ res->pc = new_PathCache(); if(!res->pc) return del_DemoRes(res); /* * Populate the cache with the contents of the user's path. */ if(pca_scan_path(res->pc, getenv("PATH"))) return del_DemoRes(res); /* * Arrange for susequent calls to pca_lookup_file() and pca_path_completions() * to only report files that are executable by the user. */ pca_set_check_fn(res->pc, cpl_check_exe, NULL); /* * Allocate a configuration object for use with pca_path_completions(). */ res->ppc = new_PcaPathConf(res->pc); if(!res->ppc) return del_DemoRes(res); /* * Replace the builtin filename completion callback with one which * searches for completions of executables in the user's path when * invoked on a word at the start of the path, and completes files * elsewhere. */ if(gl_customize_completion(res->gl, res, demo_cpl_fn)) return del_DemoRes(res); return res; } /*....................................................................... * Delete a DemoRes object. * * Input: * res DemoRes * The object to be deleted. * Output: * return DemoRes * The deleted object (always NULL). */ static DemoRes *del_DemoRes(DemoRes *res) { if(res) { res->gl = del_GetLine(res->gl); res->pc = del_PathCache(res->pc); res->ppc = del_PcaPathConf(res->ppc); free(res); }; return NULL; } /*....................................................................... * Return the limits of the word at the start of a given string, ignoring * leading white-space, and interpretting the first unescaped space, tab or * the end of the line, as the end of the word. * * Input: * string const char * The string to tokenize. * Input/Output: * wa,wb int * The indexes of the first character of the word, * and the character which follows the last * character of the word, will be assigned to * *wa and *wb, respectively. * Output: * return int 0 - A word was found. * 1 - No word was found before the end of the * string. */ static int get_word_limits(const char *string, int *wa, int *wb) { int escaped = 0; /* True if the next character is escaped */ /* * Skip leading white-space. */ for(*wa=0; isspace((int)(unsigned char)string[*wa]); (*wa)++) ; /* * Find the first unescaped space, stopping early if the end of the * string is reached. */ for(*wb = *wa; ; (*wb)++) { int c = string[*wb]; if(c=='\\') escaped = !escaped; else if((!escaped && isspace((int)(unsigned char)c)) || c=='\0') break; }; return *wa == *wb; }