?? cplfile.c
字號:
/* * 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. *//* * Standard includes. */#include <stdio.h>#include <stdlib.h>#include <limits.h>#include <errno.h>#include <string.h>#include <ctype.h>/* * Local includes. */#include "libtecla.h"#include "direader.h"#include "homedir.h"#include "pathutil.h"#include "cplfile.h"/* * Set the maximum length allowed for usernames. * names. */#define USR_LEN 100/* * Set the maximum length allowed for environment variable names. */#define ENV_LEN 100/* * Set the max length of the error-reporting string. There is no point * in this being longer than the width of a typical terminal window. * In composing error messages, I have assumed that this number is * at least 80, so you don't decrease it below this number. */#define ERRLEN 200/* * The resources needed to complete a filename are maintained in objects * of the following type. */struct CompleteFile { DirReader *dr; /* A directory reader */ HomeDir *home; /* A home directory expander */ PathName *path; /* The buffer in which to accumulate the path */ PathName *buff; /* A pathname work buffer */ char usrnam[USR_LEN+1]; /* The buffer used when reading the names of */ /* users. */ char envnam[ENV_LEN+1]; /* The buffer used when reading the names of */ /* environment variables. */ char errmsg[ERRLEN+1]; /* The error-report buffer */};static int cf_expand_home_dir(CompleteFile *cf, const char *user);static int cf_complete_username(CompleteFile *cf, WordCompletion *cpl, const char *prefix, const char *line, int word_start, int word_end, int escaped);static HOME_DIR_FN(cf_homedir_callback);static int cf_complete_entry(CompleteFile *cf, WordCompletion *cpl, const char *line, int word_start, int word_end, int escaped, CplCheckFn *check_fn, void *check_data);static char *cf_read_name(CompleteFile *cf, const char *type, const char *string, int slen, char *nambuf, int nammax);static int cf_prepare_suffix(CompleteFile *cf, const char *suffix, int add_escapes);/* * A stack based object of the following type is used to pass data to the * cf_homedir_callback() function. */typedef struct { CompleteFile *cf; /* The file-completion resource object */ WordCompletion *cpl; /* The string-completion rsource object */ const char *prefix; /* The username prefix to be completed */ const char *line; /* The line from which the prefix was extracted */ int word_start; /* The index in line[] of the start of the username */ int word_end; /* The index in line[] following the end of the prefix */ int escaped; /* If true, add escapes to the completion suffixes */} CfHomeArgs;/*....................................................................... * Create a new file-completion object. * * Output: * return CompleteFile * The new object, or NULL on error. */CompleteFile *_new_CompleteFile(void){ CompleteFile *cf; /* The object to be returned *//* * Allocate the container. */ cf = (CompleteFile *) malloc(sizeof(CompleteFile)); if(!cf) { fprintf(stderr, "_new_CompleteFile: 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_CompleteFile(). */ cf->dr = NULL; cf->home = NULL; cf->path = NULL; cf->buff = NULL; cf->usrnam[0] = '\0'; cf->envnam[0] = '\0'; cf->errmsg[0] = '\0';/* * Create the object that is used for reading directories. */ cf->dr = _new_DirReader(); if(!cf->dr) return _del_CompleteFile(cf);/* * Create the object that is used to lookup home directories. */ cf->home = _new_HomeDir(); if(!cf->home) return _del_CompleteFile(cf);/* * Create the buffer in which the completed pathname is accumulated. */ cf->path = _new_PathName(); if(!cf->path) return _del_CompleteFile(cf);/* * Create a pathname work buffer. */ cf->buff = _new_PathName(); if(!cf->buff) return _del_CompleteFile(cf); return cf;}/*....................................................................... * Delete a file-completion object. * * Input: * cf CompleteFile * The object to be deleted. * Output: * return CompleteFile * The deleted object (always NULL). */CompleteFile *_del_CompleteFile(CompleteFile *cf){ if(cf) { cf->dr = _del_DirReader(cf->dr); cf->home = _del_HomeDir(cf->home); cf->path = _del_PathName(cf->path); cf->buff = _del_PathName(cf->buff); free(cf); }; return NULL;}/*....................................................................... * Look up the possible completions of the incomplete filename that * lies between specified indexes of a given command-line string. * * Input: * cpl WordCompletion * The object in which to record the completions. * cf CompleteFile * The filename-completion resource object. * line const char * The string containing the incomplete filename. * word_start int The index of the first character in line[] * of the incomplete filename. * word_end int The index of the character in line[] that * follows the last character of the incomplete * filename. * escaped int If true, backslashes in line[] are * interpreted as escaping the characters * that follow them, and any spaces, tabs, * backslashes, or wildcard characters in the * returned suffixes will be similarly escaped. * If false, backslashes will be interpreted as * literal parts of the file name, and no * backslashes will be added to the returned * suffixes. * check_fn CplCheckFn * If not zero, this argument specifies a * function to call to ask whether a given * file should be included in the list * of completions. * check_data void * Anonymous data to be passed to check_fn(). * Output: * return int 0 - OK. * 1 - Error. A description of the error can be * acquired by calling _cf_last_error(cf). */int _cf_complete_file(WordCompletion *cpl, CompleteFile *cf, const char *line, int word_start, int word_end, int escaped, CplCheckFn *check_fn, void *check_data){ const char *lptr; /* A pointer into line[] */ int nleft; /* The number of characters still to be processed */ /* in line[]. *//* * Check the arguments. */ if(!cpl || !cf || !line || word_end < word_start) { if(cf) strcpy(cf->errmsg, "_cf_complete_file: Invalid arguments"); return 1; };/* * Clear the buffer in which the filename will be constructed. */ _pn_clear_path(cf->path);/* * How many characters are to be processed? */ nleft = word_end - word_start;/* * Get a pointer to the start of the incomplete filename. */ lptr = line + word_start;/* * If the first character is a tilde, then perform home-directory * interpolation. */ if(nleft > 0 && *lptr == '~') { int slen; if(!cf_read_name(cf, "User", ++lptr, --nleft, cf->usrnam, USR_LEN)) return 1;/* * Advance over the username in the input line. */ slen = strlen(cf->usrnam); lptr += slen; nleft -= slen;/* * If we haven't hit the end of the input string then we have a complete * username to translate to the corresponding home directory. */ if(nleft > 0) { if(cf_expand_home_dir(cf, cf->usrnam)) return 1;/* * ~user and ~ are usually followed by a directory separator to * separate them from the file contained in the home directory. * If the home directory is the root directory, then we don't want * to follow the home directory by a directory separator, so we should * skip over it so that it doesn't get copied into the filename. */ if(strcmp(cf->path->name, FS_ROOT_DIR) == 0 && strncmp(lptr, FS_DIR_SEP, FS_DIR_SEP_LEN) == 0) { lptr += FS_DIR_SEP_LEN; nleft -= FS_DIR_SEP_LEN; };/* * If we have reached the end of the input string, then the username * may be incomplete, and we should attempt to complete it. */ } else {/* * Look up the possible completions of the username. */ return cf_complete_username(cf, cpl, cf->usrnam, line, word_start+1, word_end, escaped); }; };/* * Copy the rest of the path, stopping to expand $envvar expressions * where encountered. */ while(nleft > 0) { int seglen; /* The length of the next segment to be copied *//* * Find the length of the next segment to be copied, stopping if an * unescaped '$' is seen, or the end of the path is reached. */ for(seglen=0; seglen < nleft; seglen++) { int c = lptr[seglen]; if(escaped && c == '\\') seglen++; else if(c == '$') break;/* * We will be completing the last component of the file name, * so whenever a directory separator is seen, assume that it * might be the start of the last component, and mark the character * that follows it as the start of the name that is to be completed. */ if(nleft >= FS_DIR_SEP_LEN && strncmp(lptr + seglen, FS_DIR_SEP, FS_DIR_SEP_LEN)==0) { word_start = (lptr + seglen) - line + FS_DIR_SEP_LEN; }; };/* * We have reached either the end of the filename or the start of * $environment_variable expression. Record the newly checked * segment of the filename in the output filename, removing * backslash-escapes where needed. */ if(_pn_append_to_path(cf->path, lptr, seglen, escaped) == NULL) { strcpy(cf->errmsg, "Insufficient memory to complete filename"); return 1; }; lptr += seglen; nleft -= seglen;/* * If the above loop finished before we hit the end of the filename, * then this was because an unescaped $ was seen. In this case, interpolate * the value of the environment variable that follows it into the output * filename. */ if(nleft > 0) { char *value; /* The value of the environment variable */ int vlen; /* The length of the value string */ int nlen; /* The length of the environment variable name *//* * Read the name of the environment variable. */ if(!cf_read_name(cf, "Environment", ++lptr, --nleft, cf->envnam, ENV_LEN)) return 1;/* * Advance over the environment variable name in the input line. */ nlen = strlen(cf->envnam); lptr += nlen; nleft -= nlen;/* * Get the value of the environment variable. */ value = getenv(cf->envnam); if(!value) { const char *fmt = "Unknown environment variable: %.*s"; sprintf(cf->errmsg, fmt, ERRLEN - strlen(fmt), cf->envnam); return 1; }; vlen = strlen(value);/* * If we are at the start of the filename and the first character of the * environment variable value is a '~', attempt home-directory * interpolation. */ if(cf->path->name[0] == '\0' && value[0] == '~') { if(!cf_read_name(cf, "User", value+1, vlen-1, cf->usrnam, USR_LEN) || cf_expand_home_dir(cf, cf->usrnam)) return 1;/* * If the home directory is the root directory, and the ~usrname expression * was followed by a directory separator, prevent the directory separator * from being appended to the root directory by skipping it in the * input line. */ if(strcmp(cf->path->name, FS_ROOT_DIR) == 0 && strncmp(lptr, FS_DIR_SEP, FS_DIR_SEP_LEN) == 0) { lptr += FS_DIR_SEP_LEN; nleft -= FS_DIR_SEP_LEN; }; } else {/* * Append the value of the environment variable to the output path. */ if(_pn_append_to_path(cf->path, value, strlen(value), escaped)==NULL) { strcpy(cf->errmsg, "Insufficient memory to complete filename"); return 1; };/* * Prevent extra directory separators from being added. */ if(nleft >= FS_DIR_SEP_LEN && strcmp(cf->path->name, FS_ROOT_DIR) == 0 && strncmp(lptr, FS_DIR_SEP, FS_DIR_SEP_LEN) == 0) { lptr += FS_DIR_SEP_LEN; nleft -= FS_DIR_SEP_LEN; } else if(vlen > FS_DIR_SEP_LEN && strcmp(value + vlen - FS_DIR_SEP_LEN, FS_DIR_SEP)==0) { cf->path->name[vlen-FS_DIR_SEP_LEN] = '\0'; }; };/* * If adding the environment variable didn't form a valid directory, * we can't complete the line, since there is no way to separate append * a partial filename to an environment variable reference without * that appended part of the name being seen later as part of the * environment variable name. Thus if the currently constructed path * isn't a directory, quite now with no completions having been * registered. */ if(!_pu_path_is_dir(cf->path->name)) return 0;/* * For the reasons given above, if we have reached the end of the filename * with the expansion of an environment variable, the only allowed * completion involves the addition of a directory separator. */ if(nleft == 0) { if(cpl_add_completion(cpl, line, lptr-line, word_end, FS_DIR_SEP, "", "")) { strncpy(cf->errmsg, cpl_last_error(cpl), ERRLEN); cf->errmsg[ERRLEN] = '\0'; return 1; }; return 0; }; }; };/* * Complete the filename if possible. */ return cf_complete_entry(cf, cpl, line, word_start, word_end, escaped, check_fn, check_data);}
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -