nfile; i++)
*node->files[i] = PCA_F_ENIGMA;
};
};
return;
}
#endif /* ifndef WITHOUT_FILE_SYSTEM */
yuma123_2.14/libtecla/update_html 0000775 0001750 0001750 00000002172 14770023131 017140 0 ustar vladimir vladimir #!/bin/sh
# Convert man pages to html files.
for dir in man/prog man/libr man/func man/misc man/file; do
for template in $dir/*.in;do
page=`basename "$template" .in`
if [ `wc -l < $template` -gt 1 ]; then
html="html/$page.html"
man2html $template > $html
for ref in libtecla cpl_complete_word ef_expand_file gl_get_line pca_lookup_file enhance gl_io_mode tecla; do
link="$ref.html"
ed -s $html << EOF
%s|$ref[(][^)][^) ]*[)]|$ref|g
w
q
EOF
done
fi
done
done
# Convert the change log into a web page.
cd html
echo 'The tecla library change log' > changes.html
echo '' >> changes.html
sed 's/&/&/g; s/\</g; s/>/\>/g' ../CHANGES >> changes.html
echo '
' >> changes.html
# Do the same to the release-notes file.
cd ../html
echo 'The tecla library release notes' > release.html
echo '' >> release.html
sed 's/&/&/g; s/\</g' ../RELEASE.NOTES >> release.html
echo '
' >> release.html
yuma123_2.14/libtecla/history.c 0000664 0001750 0001750 00000246204 14770023131 016557 0 ustar vladimir vladimir /*
* Copyright (c) 2000, 2001, 2002, 2003, 2004 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 "ioutil.h"
#include "history.h"
#include "freelist.h"
#include "errmsg.h"
/*
* History lines are split into sub-strings of GLH_SEG_SIZE
* characters. To avoid wasting space in the GlhLineSeg structure,
* this should be a multiple of the size of a pointer.
*/
#define GLH_SEG_SIZE 16
/*
* GlhLineSeg structures contain fixed sized segments of a larger
* string. These are linked into lists to record strings, with all but
* the last segment having GLH_SEG_SIZE characters. The last segment
* of a string is terminated within the GLH_SEG_SIZE characters with a
* '\0'.
*/
typedef struct GlhLineSeg GlhLineSeg;
struct GlhLineSeg {
GlhLineSeg *next; /* The next sub-string of the history line */
char s[GLH_SEG_SIZE]; /* The sub-string. Beware that only the final */
/* substring of a line, as indicated by 'next' */
/* being NULL, is '\0' terminated. */
};
/*
* History lines are recorded in a hash table, such that repeated
* lines are stored just once.
*
* Start by defining the size of the hash table. This should be a
* prime number.
*/
#define GLH_HASH_SIZE 113
typedef struct GlhHashBucket GlhHashBucket;
/*
* Each history line will be represented in the hash table by a
* structure of the following type.
*/
typedef struct GlhHashNode GlhHashNode;
struct GlhHashNode {
GlhHashBucket *bucket; /* The parent hash-table bucket of this node */
GlhHashNode *next; /* The next in the list of nodes within the */
/* parent hash-table bucket. */
GlhLineSeg *head; /* The list of sub-strings which make up a line */
int len; /* The length of the line, excluding any '\0' */
int used; /* The number of times this string is pointed to by */
/* the time-ordered list of history lines. */
int reported; /* A flag that is used when searching to ensure that */
/* a line isn't reported redundantly. */
};
/*
* How many new GlhHashNode elements should be allocated at a time?
*/
#define GLH_HASH_INCR 50
static int _glh_is_line(GlhHashNode *hash, const char *line, size_t n);
static int _glh_line_matches_prefix(GlhHashNode *line, GlhHashNode *prefix);
static void _glh_return_line(GlhHashNode *hash, char *line, size_t dim);
/*
* All history lines which hash to a given bucket in the hash table, are
* recorded in a structure of the following type.
*/
struct GlhHashBucket {
GlhHashNode *lines; /* The list of history lines which fall in this bucket */
};
static GlhHashBucket *glh_find_bucket(GlHistory *glh, const char *line,
size_t n);
static GlhHashNode *glh_find_hash_node(GlhHashBucket *bucket, const char *line,
size_t n);
typedef struct {
FreeList *node_mem; /* A free-list of GlhHashNode structures */
GlhHashBucket bucket[GLH_HASH_SIZE]; /* The buckets of the hash table */
} GlhLineHash;
/*
* GlhLineNode's are used to record history lines in time order.
*/
typedef struct GlhLineNode GlhLineNode;
struct GlhLineNode {
long id; /* The unique identifier of this history line */
time_t timestamp; /* The time at which the line was archived */
unsigned group; /* The identifier of the history group to which the */
/* the line belongs. */
GlhLineNode *next; /* The next youngest line in the list */
GlhLineNode *prev; /* The next oldest line in the list */
GlhHashNode *line; /* The hash-table entry of the history line */
};
/*
* The number of GlhLineNode elements per freelist block.
*/
#define GLH_LINE_INCR 100
/*
* Encapsulate the time-ordered list of historical lines.
*/
typedef struct {
FreeList *node_mem; /* A freelist of GlhLineNode objects */
GlhLineNode *head; /* The oldest line in the list */
GlhLineNode *tail; /* The newest line in the list */
} GlhLineList;
/*
* The _glh_lookup_history() returns copies of history lines in a
* dynamically allocated array. This array is initially allocated
* GLH_LOOKUP_SIZE bytes. If subsequently this size turns out to be
* too small, realloc() is used to increase its size to the required
* size plus GLH_LOOKUP_MARGIN. The idea of the later parameter is to
* reduce the number of realloc() operations needed.
*/
#define GLH_LBUF_SIZE 300
#define GLH_LBUF_MARGIN 100
/*
* Encapsulate all of the resources needed to store historical input lines.
*/
struct GlHistory {
ErrMsg *err; /* The error-reporting buffer */
GlhLineSeg *buffer; /* An array of sub-line nodes to be partitioned */
/* into lists of sub-strings recording input lines. */
int nbuff; /* The allocated dimension of buffer[] */
GlhLineSeg *unused; /* The list of free nodes in buffer[] */
GlhLineList list; /* A time ordered list of history lines */
GlhLineNode *recall; /* The last line recalled, or NULL if no recall */
/* session is currently active. */
GlhLineNode *id_node;/* The node at which the last ID search terminated */
GlhLineHash hash; /* A hash-table of reference-counted history lines */
GlhHashNode *prefix; /* A pointer to a line containing the prefix that */
/* is being searched for. Note that if prefix==NULL */
/* and prefix_len>0, this means that no line in */
/* the buffer starts with the requested prefix. */
int prefix_len; /* The length of the prefix being searched for. */
char *lbuf; /* The array in which _glh_lookup_history() returns */
/* history lines */
int lbuf_dim; /* The allocated size of lbuf[] */
int nbusy; /* The number of line segments in buffer[] that are */
/* currently being used to record sub-lines */
int nfree; /* The number of line segments in buffer that are */
/* not currently being used to record sub-lines */
unsigned long seq; /* The next ID to assign to a line node */
unsigned group; /* The identifier of the current history group */
int nline; /* The number of lines currently in the history list */
int max_lines; /* Either -1 or a ceiling on the number of lines */
int enable; /* If false, ignore history additions and lookups */
};
#ifndef WITHOUT_FILE_SYSTEM
static int _glh_cant_load_history(GlHistory *glh, const char *filename,
int lineno, const char *message, FILE *fp);
static int _glh_cant_save_history(GlHistory *glh, const char *message,
const char *filename, FILE *fp);
static int _glh_write_timestamp(FILE *fp, time_t timestamp);
static int _glh_decode_timestamp(char *string, char **endp, time_t *timestamp);
#endif
static void _glh_discard_line(GlHistory *glh, GlhLineNode *node);
static GlhLineNode *_glh_find_id(GlHistory *glh, GlhLineID id);
static GlhHashNode *_glh_acquire_copy(GlHistory *glh, const char *line,
size_t n);
static GlhHashNode *_glh_discard_copy(GlHistory *glh, GlhHashNode *hnode);
static int _glh_prepare_for_recall(GlHistory *glh, char *line);
/*
* The following structure and functions are used to iterate through
* the characters of a segmented history line.
*/
typedef struct {
GlhLineSeg *seg; /* The line segment that the next character will */
/* be returned from. */
int posn; /* The index in the above line segment, containing */
/* the next unread character. */
char c; /* The current character in the input line */
} GlhLineStream;
static void glh_init_stream(GlhLineStream *str, GlhHashNode *line);
static void glh_step_stream(GlhLineStream *str);
/*
* See if search prefix contains any globbing characters.
*/
static int glh_contains_glob(GlhHashNode *prefix);
/*
* Match a line against a search pattern.
*/
static int glh_line_matches_glob(GlhLineStream *lstr, GlhLineStream *pstr);
static int glh_matches_range(char c, GlhLineStream *pstr);
/*.......................................................................
* Create a line history maintenance object.
*
* Input:
* buflen size_t The number of bytes to allocate to the
* buffer that is used to record all of the
* most recent lines of user input that will fit.
* If buflen==0, no buffer will be allocated.
* Output:
* return GlHistory * The new object, or NULL on error.
*/
GlHistory *_new_GlHistory(size_t buflen)
{
GlHistory *glh; /* The object to be returned */
int i;
/*
* Allocate the container.
*/
glh = (GlHistory *) malloc(sizeof(GlHistory));
if(!glh) {
errno = ENOMEM;
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_GlHistory().
*/
glh->err = NULL;
glh->buffer = NULL;
glh->nbuff = (buflen+GLH_SEG_SIZE-1) / GLH_SEG_SIZE;
glh->unused = NULL;
glh->list.node_mem = NULL;
glh->list.head = glh->list.tail = NULL;
glh->recall = NULL;
glh->id_node = NULL;
glh->hash.node_mem = NULL;
for(i=0; ihash.bucket[i].lines = NULL;
glh->prefix = NULL;
glh->lbuf = NULL;
glh->lbuf_dim = 0;
glh->nbusy = 0;
glh->nfree = glh->nbuff;
glh->seq = 0;
glh->group = 0;
glh->nline = 0;
glh->max_lines = -1;
glh->enable = 1;
/*
* Allocate a place to record error messages.
*/
glh->err = _new_ErrMsg();
if(!glh->err)
return _del_GlHistory(glh);
/*
* Allocate the buffer, if required.
*/
if(glh->nbuff > 0) {
glh->nbuff = glh->nfree;
glh->buffer = (GlhLineSeg *) malloc(sizeof(GlhLineSeg) * glh->nbuff);
if(!glh->buffer) {
errno = ENOMEM;
return _del_GlHistory(glh);
};
/*
* All nodes of the buffer are currently unused, so link them all into
* a list and make glh->unused point to the head of this list.
*/
glh->unused = glh->buffer;
for(i=0; inbuff-1; i++) {
GlhLineSeg *seg = glh->unused + i;
seg->next = seg + 1;
};
glh->unused[i].next = NULL;
};
/*
* Allocate the GlhLineNode freelist.
*/
glh->list.node_mem = _new_FreeList(sizeof(GlhLineNode), GLH_LINE_INCR);
if(!glh->list.node_mem)
return _del_GlHistory(glh);
/*
* Allocate the GlhHashNode freelist.
*/
glh->hash.node_mem = _new_FreeList(sizeof(GlhLineNode), GLH_HASH_INCR);
if(!glh->hash.node_mem)
return _del_GlHistory(glh);
/*
* Allocate the array that _glh_lookup_history() uses to return a
* copy of a given history line. This will be resized when necessary.
*/
glh->lbuf_dim = GLH_LBUF_SIZE;
glh->lbuf = (char *) malloc(glh->lbuf_dim);
if(!glh->lbuf) {
errno = ENOMEM;
return _del_GlHistory(glh);
};
return glh;
}
/*.......................................................................
* Delete a GlHistory object.
*
* Input:
* glh GlHistory * The object to be deleted.
* Output:
* return GlHistory * The deleted object (always NULL).
*/
GlHistory *_del_GlHistory(GlHistory *glh)
{
if(glh) {
/*
* Delete the error-message buffer.
*/
glh->err = _del_ErrMsg(glh->err);
/*
* Delete the buffer.
*/
if(glh->buffer) {
free(glh->buffer);
glh->buffer = NULL;
glh->unused = NULL;
};
/*
* Delete the freelist of GlhLineNode's.
*/
glh->list.node_mem = _del_FreeList(glh->list.node_mem, 1);
/*
* The contents of the list were deleted by deleting the freelist.
*/
glh->list.head = NULL;
glh->list.tail = NULL;
/*
* Delete the freelist of GlhHashNode's.
*/
glh->hash.node_mem = _del_FreeList(glh->hash.node_mem, 1);
/*
* Delete the lookup buffer.
*/
if(glh->lbuf)
free(glh->lbuf);
/*
* Delete the container.
*/
free(glh);
};
return NULL;
}
/*.......................................................................
* Append a new line to the history list, deleting old lines to make
* room, if needed.
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* line char * The line to be archived.
* force int Unless this flag is non-zero, empty lines aren't
* archived. This flag requests that the line be
* archived regardless.
* Output:
* return int 0 - OK.
* 1 - Error.
*/
int _glh_add_history(GlHistory *glh, const char *line, int force)
{
int slen; /* The length of the line to be recorded (minus the '\0') */
int empty; /* True if the string is empty */
const char *nlptr; /* A pointer to a newline character in line[] */
GlhHashNode *hnode; /* The hash-table node of the line */
GlhLineNode *lnode; /* A node in the time-ordered list of lines */
int i;
/*
* Check the arguments.
*/
if(!glh || !line) {
errno = EINVAL;
return 1;
};
/*
* Is history enabled?
*/
if(!glh->enable || !glh->buffer || glh->max_lines == 0)
return 0;
/*
* Cancel any ongoing search.
*/
if(_glh_cancel_search(glh))
return 1;
/*
* How long is the string to be recorded, being careful not to include
* any terminating '\n' character.
*/
nlptr = strchr(line, '\n');
if(nlptr)
slen = (nlptr - line);
else
slen = strlen(line);
/*
* Is the line empty?
*/
empty = 1;
for(i=0; imax_lines >= 0) {
/*
* If necessary, remove old lines until there is room to add one new
* line without exceeding the specified line limit.
*/
while(glh->nline > 0 && glh->nline >= glh->max_lines)
_glh_discard_line(glh, glh->list.head);
/*
* We can't archive the line if the maximum number of lines allowed is
* zero.
*/
if(glh->max_lines == 0)
return 0;
};
/*
* Unless already stored, store a copy of the line in the history buffer,
* then return a reference-counted hash-node pointer to this copy.
*/
hnode = _glh_acquire_copy(glh, line, slen);
if(!hnode) {
_err_record_msg(glh->err, "No room to store history line", END_ERR_MSG);
errno = ENOMEM;
return 1;
};
/*
* Allocate a new node in the time-ordered list of lines.
*/
lnode = (GlhLineNode *) _new_FreeListNode(glh->list.node_mem);
/*
* If a new line-node couldn't be allocated, discard our copy of the
* stored line before reporting the error.
*/
if(!lnode) {
hnode = _glh_discard_copy(glh, hnode);
_err_record_msg(glh->err, "No room to store history line", END_ERR_MSG);
errno = ENOMEM;
return 1;
};
/*
* Record a pointer to the hash-table record of the line in the new
* list node.
*/
lnode->id = glh->seq++;
lnode->timestamp = time(NULL);
lnode->group = glh->group;
lnode->line = hnode;
/*
* Append the new node to the end of the time-ordered list.
*/
if(glh->list.head)
glh->list.tail->next = lnode;
else
glh->list.head = lnode;
lnode->next = NULL;
lnode->prev = glh->list.tail;
glh->list.tail = lnode;
/*
* Record the addition of a line to the list.
*/
glh->nline++;
return 0;
}
/*.......................................................................
* Recall the next oldest line that has the search prefix last recorded
* by _glh_search_prefix().
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* line char * The input line buffer. On input this should contain
* the current input line, and on output, if anything
* was found, its contents will have been replaced
* with the matching line.
* dim size_t The allocated dimension of the line buffer.
* Output:
* return char * A pointer to line[0], or NULL if not found.
*/
char *_glh_find_backwards(GlHistory *glh, char *line, size_t dim)
{
GlhLineNode *node; /* The line location node being checked */
GlhHashNode *old_line; /* The previous recalled line */
/*
* Check the arguments.
*/
if(!glh || !line) {
if(glh)
_err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG);
errno = EINVAL;
return NULL;
};
/*
* Is history enabled?
*/
if(!glh->enable || !glh->buffer || glh->max_lines == 0)
return NULL;
/*
* Check the line dimensions.
*/
if(dim < strlen(line) + 1) {
_err_record_msg(glh->err, "'dim' argument inconsistent with strlen(line)",
END_ERR_MSG);
errno = EINVAL;
return NULL;
};
/*
* Preserve the input line if needed.
*/
if(_glh_prepare_for_recall(glh, line))
return NULL;
/*
* From where should we start the search?
*/
if(glh->recall) {
node = glh->recall->prev;
old_line = glh->recall->line;
} else {
node = glh->list.tail;
old_line = NULL;
};
/*
* Search backwards through the list for the first match with the
* prefix string that differs from the last line that was recalled.
*/
while(node && (node->group != glh->group || node->line == old_line ||
!_glh_line_matches_prefix(node->line, glh->prefix)))
node = node->prev;
/*
* Was a matching line found?
*/
if(node) {
/*
* Recall the found node as the starting point for subsequent
* searches.
*/
glh->recall = node;
/*
* Copy the matching line into the provided line buffer.
*/
_glh_return_line(node->line, line, dim);
/*
* Return it.
*/
return line;
};
/*
* No match was found.
*/
return NULL;
}
/*.......................................................................
* Recall the next newest line that has the search prefix last recorded
* by _glh_search_prefix().
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* line char * The input line buffer. On input this should contain
* the current input line, and on output, if anything
* was found, its contents will have been replaced
* with the matching line.
* dim size_t The allocated dimensions of the line buffer.
* Output:
* return char * The line requested, or NULL if no matching line
* was found.
*/
char *_glh_find_forwards(GlHistory *glh, char *line, size_t dim)
{
GlhLineNode *node; /* The line location node being checked */
GlhHashNode *old_line; /* The previous recalled line */
/*
* Check the arguments.
*/
if(!glh || !line) {
if(glh)
_err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG);
errno = EINVAL;
return NULL;
};
/*
* Is history enabled?
*/
if(!glh->enable || !glh->buffer || glh->max_lines == 0)
return NULL;
/*
* Check the line dimensions.
*/
if(dim < strlen(line) + 1) {
_err_record_msg(glh->err, "'dim' argument inconsistent with strlen(line)",
END_ERR_MSG);
errno = EINVAL;
return NULL;
};
/*
* From where should we start the search?
*/
if(glh->recall) {
node = glh->recall->next;
old_line = glh->recall->line;
} else {
return NULL;
};
/*
* Search forwards through the list for the first match with the
* prefix string.
*/
while(node && (node->group != glh->group || node->line == old_line ||
!_glh_line_matches_prefix(node->line, glh->prefix)))
node = node->next;
/*
* Was a matching line found?
*/
if(node) {
/*
* Copy the matching line into the provided line buffer.
*/
_glh_return_line(node->line, line, dim);
/*
* Record the starting point of the next search.
*/
glh->recall = node;
/*
* If we just returned the line that was being entered when the search
* session first started, cancel the search.
*/
if(node == glh->list.tail)
_glh_cancel_search(glh);
/*
* Return the matching line to the user.
*/
return line;
};
/*
* No match was found.
*/
return NULL;
}
/*.......................................................................
* If a search is in progress, cancel it.
*
* This involves discarding the line that was temporarily saved by
* _glh_find_backwards() when the search was originally started,
* and reseting the search iteration pointer to NULL.
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* Output:
* return int 0 - OK.
* 1 - Error.
*/
int _glh_cancel_search(GlHistory *glh)
{
/*
* Check the arguments.
*/
if(!glh) {
errno = EINVAL;
return 1;
};
/*
* If there wasn't a search in progress, do nothing.
*/
if(!glh->recall)
return 0;
/*
* Reset the search pointers. Note that it is essential to set
* glh->recall to NULL before calling _glh_discard_line(), to avoid an
* infinite recursion.
*/
glh->recall = NULL;
/*
* Delete the node of the preserved line.
*/
_glh_discard_line(glh, glh->list.tail);
return 0;
}
/*.......................................................................
* Set the prefix of subsequent history searches.
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* line const char * The command line who's prefix is to be used.
* prefix_len int The length of the prefix.
* Output:
* return int 0 - OK.
* 1 - Error.
*/
int _glh_search_prefix(GlHistory *glh, const char *line, int prefix_len)
{
/*
* Check the arguments.
*/
if(!glh) {
errno = EINVAL;
return 1;
};
/*
* Is history enabled?
*/
if(!glh->enable || !glh->buffer || glh->max_lines == 0)
return 0;
/*
* Discard any existing prefix.
*/
glh->prefix = _glh_discard_copy(glh, glh->prefix);
/*
* Only store a copy of the prefix string if it isn't a zero-length string.
*/
if(prefix_len > 0) {
/*
* Get a reference-counted copy of the prefix from the history cache buffer.
*/
glh->prefix = _glh_acquire_copy(glh, line, prefix_len);
/*
* Was there insufficient buffer space?
*/
if(!glh->prefix) {
_err_record_msg(glh->err, "The search prefix is too long to store",
END_ERR_MSG);
errno = ENOMEM;
return 1;
};
};
return 0;
}
/*.......................................................................
* Recall the oldest recorded line.
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* line char * The input line buffer. On input this should contain
* the current input line, and on output, its contents
* will have been replaced with the oldest line.
* dim size_t The allocated dimensions of the line buffer.
* Output:
* return char * A pointer to line[0], or NULL if not found.
*/
char *_glh_oldest_line(GlHistory *glh, char *line, size_t dim)
{
GlhLineNode *node; /* The line location node being checked */
/*
* Check the arguments.
*/
if(!glh || !line) {
if(glh)
_err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG);
errno = EINVAL;
return NULL;
};
/*
* Is history enabled?
*/
if(!glh->enable || !glh->buffer || glh->max_lines == 0)
return NULL;
/*
* Check the line dimensions.
*/
if(dim < strlen(line) + 1) {
_err_record_msg(glh->err, "'dim' argument inconsistent with strlen(line)",
END_ERR_MSG);
errno = EINVAL;
return NULL;
};
/*
* Preserve the input line if needed.
*/
if(_glh_prepare_for_recall(glh, line))
return NULL;
/*
* Locate the oldest line that belongs to the current group.
*/
for(node=glh->list.head; node && node->group != glh->group;
node = node->next)
;
/*
* No line found?
*/
if(!node)
return NULL;
/*
* Record the above node as the starting point for subsequent
* searches.
*/
glh->recall = node;
/*
* Copy the recalled line into the provided line buffer.
*/
_glh_return_line(node->line, line, dim);
/*
* If we just returned the line that was being entered when the search
* session first started, cancel the search.
*/
if(node == glh->list.tail)
_glh_cancel_search(glh);
return line;
}
/*.......................................................................
* Recall the line that was being entered when the search started.
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* line char * The input line buffer. On input this should contain
* the current input line, and on output, its contents
* will have been replaced with the line that was
* being entered when the search was started.
* dim size_t The allocated dimensions of the line buffer.
* Output:
* return char * A pointer to line[0], or NULL if not found.
*/
char *_glh_current_line(GlHistory *glh, char *line, size_t dim)
{
/*
* Check the arguments.
*/
if(!glh || !line) {
if(glh)
_err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG);
errno = EINVAL;
return NULL;
};
/*
* If history isn't enabled, or no history search has yet been started,
* ignore the call.
*/
if(!glh->enable || !glh->buffer || glh->max_lines == 0 || !glh->recall)
return NULL;
/*
* Check the line dimensions.
*/
if(dim < strlen(line) + 1) {
_err_record_msg(glh->err, "'dim' argument inconsistent with strlen(line)",
END_ERR_MSG);
errno = EINVAL;
return NULL;
};
/*
* Copy the recalled line into the provided line buffer.
*/
_glh_return_line(glh->list.tail->line, line, dim);
/*
* Since we have returned to the starting point of the search, cancel it.
*/
_glh_cancel_search(glh);
return line;
}
/*.......................................................................
* Query the id of a history line offset by a given number of lines from
* the one that is currently being recalled. If a recall session isn't
* in progress, or the offset points outside the history list, 0 is
* returned.
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* offset int The line offset (0 for the current line, < 0
* for an older line, > 0 for a newer line.
* Output:
* return GlhLineID The identifier of the line that is currently
* being recalled, or 0 if no recall session is
* currently in progress.
*/
GlhLineID _glh_line_id(GlHistory *glh, int offset)
{
GlhLineNode *node; /* The line location node being checked */
/*
* Is history enabled?
*/
if(!glh->enable || !glh->buffer || glh->max_lines == 0)
return 0;
/*
* Search forward 'offset' lines to find the required line.
*/
if(offset >= 0) {
for(node=glh->recall; node && offset != 0; node=node->next) {
if(node->group == glh->group)
offset--;
};
} else {
for(node=glh->recall; node && offset != 0; node=node->prev) {
if(node->group == glh->group)
offset++;
};
};
return node ? node->id : 0;
}
/*.......................................................................
* Recall a line by its history buffer ID. If the line is no longer
* in the buffer, or the id is zero, NULL is returned.
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* id GlhLineID The ID of the line to be returned.
* line char * The input line buffer. On input this should contain
* the current input line, and on output, its contents
* will have been replaced with the saved line.
* dim size_t The allocated dimensions of the line buffer.
* Output:
* return char * A pointer to line[0], or NULL if not found.
*/
char *_glh_recall_line(GlHistory *glh, GlhLineID id, char *line, size_t dim)
{
GlhLineNode *node; /* The line location node being checked */
/*
* Is history enabled?
*/
if(!glh->enable || !glh->buffer || glh->max_lines == 0)
return NULL;
/*
* Preserve the input line if needed.
*/
if(_glh_prepare_for_recall(glh, line))
return NULL;
/*
* Search for the specified line.
*/
node = _glh_find_id(glh, id);
/*
* Not found?
*/
if(!node || node->group != glh->group)
return NULL;
/*
* Record the node of the matching line as the starting point
* for subsequent searches.
*/
glh->recall = node;
/*
* Copy the recalled line into the provided line buffer.
*/
_glh_return_line(node->line, line, dim);
return line;
}
/*.......................................................................
* Save the current history in a specified file.
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* filename const char * The name of the new file to record the
* history in.
* comment const char * Extra information such as timestamps will
* be recorded on a line started with this
* string, the idea being that the file can
* double as a command file. Specify "" if
* you don't care.
* max_lines int The maximum number of lines to save, or -1
* to save all of the lines in the history
* list.
* Output:
* return int 0 - OK.
* 1 - Error.
*/
int _glh_save_history(GlHistory *glh, const char *filename, const char *comment,
int max_lines)
{
#ifdef WITHOUT_FILE_SYSTEM
_err_record_msg(glh->err, "Can't save history without filesystem access",
END_ERR_MSG);
errno = EINVAL;
return 1;
#else
FILE *fp; /* The output file */
GlhLineNode *node; /* The line being saved */
GlhLineNode *head; /* The head of the list of lines to be saved */
GlhLineSeg *seg; /* One segment of a line being saved */
/*
* Check the arguments.
*/
if(!glh || !filename || !comment) {
if(glh)
_err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG);
errno = EINVAL;
return 1;
};
/*
* Attempt to open the specified file.
*/
fp = fopen(filename, "w");
if(!fp)
return _glh_cant_save_history(glh, "Can't open", filename, NULL);
/*
* If a ceiling on the number of lines to save was specified, count
* that number of lines backwards, to find the first line to be saved.
*/
head = NULL;
if(max_lines >= 0) {
for(head=glh->list.tail; head && --max_lines > 0; head=head->prev)
;
};
if(!head)
head = glh->list.head;
/*
* Write the contents of the history buffer to the history file, writing
* associated data such as timestamps, to a line starting with the
* specified comment string.
*/
for(node=head; node; node=node->next) {
/*
* Write peripheral information associated with the line, as a comment.
*/
if(fprintf(fp, "%s ", comment) < 0 ||
_glh_write_timestamp(fp, node->timestamp) ||
fprintf(fp, " %u\n", node->group) < 0) {
return _glh_cant_save_history(glh, "Error writing", filename, fp);
};
/*
* Write the history line.
*/
for(seg=node->line->head; seg; seg=seg->next) {
size_t slen = seg->next ? GLH_SEG_SIZE : strlen(seg->s);
if(fwrite(seg->s, sizeof(char), slen, fp) != slen)
return _glh_cant_save_history(glh, "Error writing", filename, fp);
};
fputc('\n', fp);
};
/*
* Close the history file.
*/
if(fclose(fp) == EOF)
return _glh_cant_save_history(glh, "Error writing", filename, NULL);
return 0;
#endif
}
#ifndef WITHOUT_FILE_SYSTEM
/*.......................................................................
* This is a private error return function of _glh_save_history(). It
* composes an error report in the error buffer, composed using
* sprintf("%s %s (%s)", message, filename, strerror(errno)). It then
* closes fp and returns the error return code of _glh_save_history().
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* message const char * A message to be followed by the filename.
* filename const char * The name of the offending output file.
* fp FILE * The stream to be closed (send NULL if not
* open).
* Output:
* return int Always 1.
*/
static int _glh_cant_save_history(GlHistory *glh, const char *message,
const char *filename, FILE *fp)
{
_err_record_msg(glh->err, message, filename, " (",
strerror(errno), ")", END_ERR_MSG);
if(fp)
(void) fclose(fp);
return 1;
}
/*.......................................................................
* Write a timestamp to a given stdio stream, in the format
* yyyymmddhhmmss
*
* Input:
* fp FILE * The stream to write to.
* timestamp time_t The timestamp to be written.
* Output:
* return int 0 - OK.
* 1 - Error.
*/
static int _glh_write_timestamp(FILE *fp, time_t timestamp)
{
struct tm *t; /* THe broken-down calendar time */
/*
* Get the calendar components corresponding to the given timestamp.
*/
if(timestamp < 0 || (t = localtime(×tamp)) == NULL) {
if(fprintf(fp, "?") < 0)
return 1;
return 0;
};
/*
* Write the calendar time as yyyymmddhhmmss.
*/
if(fprintf(fp, "%04d%02d%02d%02d%02d%02d", t->tm_year + 1900, t->tm_mon + 1,
t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec) < 0)
return 1;
return 0;
}
#endif
/*.......................................................................
* Restore previous history lines from a given file.
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* filename const char * The name of the file to read from.
* comment const char * The same comment string that was passed to
* _glh_save_history() when this file was
* written.
* line char * A buffer into which lines can be read.
* dim size_t The allocated dimension of line[].
* Output:
* return int 0 - OK.
* 1 - Error.
*/
int _glh_load_history(GlHistory *glh, const char *filename, const char *comment,
char *line, size_t dim)
{
#ifdef WITHOUT_FILE_SYSTEM
_err_record_msg(glh->err, "Can't load history without filesystem access",
END_ERR_MSG);
errno = EINVAL;
return 1;
#else
FILE *fp; /* The output file */
size_t comment_len; /* The length of the comment string */
time_t timestamp; /* The timestamp of the history line */
unsigned group; /* The identifier of the history group to which */
/* the line belongs. */
int lineno; /* The line number being read */
/*
* Check the arguments.
*/
if(!glh || !filename || !comment || !line) {
if(glh)
_err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG);
errno = EINVAL;
return 1;
};
/*
* Measure the length of the comment string.
*/
comment_len = strlen(comment);
/*
* Clear the history list.
*/
_glh_clear_history(glh, 1);
/*
* Attempt to open the specified file. Don't treat it as an error
* if the file doesn't exist.
*/
fp = fopen(filename, "r");
if(!fp)
return 0;
/*
* Attempt to read each line and preceding peripheral info, and add these
* to the history list.
*/
for(lineno=1; fgets(line, dim, fp) != NULL; lineno++) {
char *lptr; /* A pointer into the input line */
/*
* Check that the line starts with the comment string.
*/
if(strncmp(line, comment, comment_len) != 0) {
return _glh_cant_load_history(glh, filename, lineno,
"Corrupt history parameter line", fp);
};
/*
* Skip spaces and tabs after the comment.
*/
for(lptr=line+comment_len; *lptr && (*lptr==' ' || *lptr=='\t'); lptr++)
;
/*
* The next word must be a timestamp.
*/
if(_glh_decode_timestamp(lptr, &lptr, ×tamp)) {
return _glh_cant_load_history(glh, filename, lineno,
"Corrupt timestamp", fp);
};
/*
* Skip spaces and tabs.
*/
while(*lptr==' ' || *lptr=='\t')
lptr++;
/*
* The next word must be an unsigned integer group number.
*/
group = (int) strtoul(lptr, &lptr, 10);
if(*lptr != ' ' && *lptr != '\n') {
return _glh_cant_load_history(glh, filename, lineno,
"Corrupt group id", fp);
};
/*
* Skip spaces and tabs.
*/
while(*lptr==' ' || *lptr=='\t')
lptr++;
/*
* There shouldn't be anything left on the line.
*/
if(*lptr != '\n') {
return _glh_cant_load_history(glh, filename, lineno,
"Corrupt parameter line", fp);
};
/*
* Now read the history line itself.
*/
lineno++;
if(fgets(line, dim, fp) == NULL)
return _glh_cant_load_history(glh, filename, lineno, "Read error", fp);
/*
* Append the line to the history buffer.
*/
if(_glh_add_history(glh, line, 1)) {
return _glh_cant_load_history(glh, filename, lineno,
"Insufficient memory to record line", fp);
};
/*
* Record the group and timestamp information along with the line.
*/
if(glh->list.tail) {
glh->list.tail->timestamp = timestamp;
glh->list.tail->group = group;
};
};
/*
* Close the file.
*/
(void) fclose(fp);
return 0;
#endif
}
#ifndef WITHOUT_FILE_SYSTEM
/*.......................................................................
* This is a private error return function of _glh_load_history().
*/
static int _glh_cant_load_history(GlHistory *glh, const char *filename,
int lineno, const char *message, FILE *fp)
{
char lnum[20];
/*
* Convert the line number to a string.
*/
sprintf(lnum, "%d", lineno);
/*
* Render an error message.
*/
_err_record_msg(glh->err, filename, ":", lnum, ":", message, END_ERR_MSG);
/*
* Close the file.
*/
if(fp)
(void) fclose(fp);
return 1;
}
/*.......................................................................
* Read a timestamp from a string.
*
* Input:
* string char * The string to read from.
* Input/Output:
* endp char ** On output *endp will point to the next unprocessed
* character in string[].
* timestamp time_t * The timestamp will be assigned to *t.
* Output:
* return int 0 - OK.
* 1 - Error.
*/
static int _glh_decode_timestamp(char *string, char **endp, time_t *timestamp)
{
unsigned year,month,day,hour,min,sec; /* Calendar time components */
struct tm t;
/*
* There are 14 characters in the date format yyyymmddhhmmss.
*/
enum {TSLEN=14};
char timestr[TSLEN+1]; /* The timestamp part of the string */
/*
* If the time wasn't available at the time that the line was recorded
* it will have been written as "?". Check for this before trying
* to read the timestamp.
*/
if(string[0] == '\?') {
*endp = string+1;
*timestamp = -1;
return 0;
};
/*
* The timestamp is expected to be written in the form yyyymmddhhmmss.
*/
if(strlen(string) < TSLEN) {
*endp = string;
return 1;
};
/*
* Copy the timestamp out of the string.
*/
strncpy(timestr, string, TSLEN);
timestr[TSLEN] = '\0';
/*
* Decode the timestamp.
*/
if(sscanf(timestr, "%4u%2u%2u%2u%2u%2u", &year, &month, &day, &hour, &min,
&sec) != 6) {
*endp = string;
return 1;
};
/*
* Advance the string pointer over the successfully read timestamp.
*/
*endp = string + TSLEN;
/*
* Copy the read values into a struct tm.
*/
t.tm_sec = sec;
t.tm_min = min;
t.tm_hour = hour;
t.tm_mday = day;
t.tm_wday = 0;
t.tm_yday = 0;
t.tm_mon = month - 1;
t.tm_year = year - 1900;
t.tm_isdst = -1;
/*
* Convert the contents of the struct tm to a time_t.
*/
*timestamp = mktime(&t);
return 0;
}
#endif
/*.......................................................................
* Switch history groups.
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* group unsigned The new group identifier. This will be recorded
* with subsequent history lines, and subsequent
* history searches will only return lines with
* this group identifier. This allows multiple
* separate history lists to exist within
* a single GlHistory object. Note that the
* default group identifier is 0.
* Output:
* return int 0 - OK.
* 1 - Error.
*/
int _glh_set_group(GlHistory *glh, unsigned group)
{
/*
* Check the arguments.
*/
if(!glh) {
if(glh)
_err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG);
errno = EINVAL;
return 1;
};
/*
* Is the group being changed?
*/
if(group != glh->group) {
/*
* Cancel any ongoing search.
*/
if(_glh_cancel_search(glh))
return 1;
/*
* Record the new group.
*/
glh->group = group;
};
return 0;
}
/*.......................................................................
* Query the current history group.
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* Output:
* return unsigned The group identifier.
*/
int _glh_get_group(GlHistory *glh)
{
return glh ? glh->group : 0;
}
/*.......................................................................
* Display the contents of the history list.
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* write_fn GlWriteFn * The function to call to write the line, or
* 0 to discard the output.
* data void * Anonymous data to pass to write_fn().
* fmt const char * A format string. This can contain arbitrary
* characters, which are written verbatim, plus
* any of the following format directives:
* %D - The date, like 2001-11-20
* %T - The time of day, like 23:59:59
* %N - The sequential entry number of the
* line in the history buffer.
* %G - The history group number of the line.
* %% - A literal % character.
* %H - The history line.
* all_groups int If true, display history lines from all
* history groups. Otherwise only display
* those of the current history group.
* max_lines int If max_lines is < 0, all available lines
* are displayed. Otherwise only the most
* recent max_lines lines will be displayed.
* Output:
* return int 0 - OK.
* 1 - Error.
*/
int _glh_show_history(GlHistory *glh, GlWriteFn *write_fn, void *data,
const char *fmt, int all_groups, int max_lines)
{
GlhLineNode *node; /* The line being displayed */
GlhLineNode *oldest; /* The oldest line to display */
GlhLineSeg *seg; /* One segment of a line being displayed */
enum {TSMAX=32}; /* The maximum length of the date and time string */
char buffer[TSMAX+1]; /* The buffer in which to write the date and time */
int idlen; /* The length of displayed ID strings */
unsigned grpmax; /* The maximum group number in the buffer */
int grplen; /* The number of characters needed to print grpmax */
int len; /* The length of a string to be written */
/*
* Check the arguments.
*/
if(!glh || !write_fn || !fmt) {
if(glh)
_err_record_msg(glh->err, "NULL argument(s)", END_ERR_MSG);
errno = EINVAL;
return 1;
};
/*
* Is history enabled?
*/
if(!glh->enable || !glh->list.head)
return 0;
/*
* Work out the length to display ID numbers, choosing the length of
* the biggest number in the buffer. Smaller numbers will be padded
* with leading zeroes if needed.
*/
sprintf(buffer, "%lu", (unsigned long) glh->list.tail->id);
idlen = strlen(buffer);
/*
* Find the largest group number.
*/
grpmax = 0;
for(node=glh->list.head; node; node=node->next) {
if(node->group > grpmax)
grpmax = node->group;
};
/*
* Find out how many characters are needed to display the group number.
*/
sprintf(buffer, "%u", (unsigned) grpmax);
grplen = strlen(buffer);
/*
* Find the node that follows the oldest line to be displayed.
*/
if(max_lines < 0) {
oldest = glh->list.head;
} else if(max_lines==0) {
return 0;
} else {
for(oldest=glh->list.tail; oldest; oldest=oldest->prev) {
if((all_groups || oldest->group == glh->group) && --max_lines <= 0)
break;
};
/*
* If the number of lines in the buffer doesn't exceed the specified
* maximum, start from the oldest line in the buffer.
*/
if(!oldest)
oldest = glh->list.head;
};
/*
* List the history lines in increasing time order.
*/
for(node=oldest; node; node=node->next) {
/*
* Only display lines from the current history group, unless
* told otherwise.
*/
if(all_groups || node->group == glh->group) {
const char *fptr; /* A pointer into the format string */
struct tm *t = NULL; /* The broken time version of the timestamp */
/*
* Work out the calendar representation of the node timestamp.
*/
if(node->timestamp != (time_t) -1)
t = localtime(&node->timestamp);
/*
* Parse the format string.
*/
fptr = fmt;
while(*fptr) {
/*
* Search for the start of the next format directive or the end of the string.
*/
const char *start = fptr;
while(*fptr && *fptr != '%')
fptr++;
/*
* Display any literal characters that precede the located directive.
*/
if(fptr > start) {
len = (int) (fptr - start);
if(write_fn(data, start, len) != len)
return 1;
};
/*
* Did we hit a new directive before the end of the line?
*/
if(*fptr) {
/*
* Obey the directive. Ignore unknown directives.
*/
switch(*++fptr) {
case 'D': /* Display the date */
if(t && strftime(buffer, TSMAX, "%Y-%m-%d", t) != 0) {
len = strlen(buffer);
if(write_fn(data, buffer, len) != len)
return 1;
};
break;
case 'T': /* Display the time of day */
if(t && strftime(buffer, TSMAX, "%H:%M:%S", t) != 0) {
len = strlen(buffer);
if(write_fn(data, buffer, len) != len)
return 1;
};
break;
case 'N': /* Display the sequential entry number */
sprintf(buffer, "%*lu", idlen, (unsigned long) node->id);
len = strlen(buffer);
if(write_fn(data, buffer, len) != len)
return 1;
break;
case 'G':
sprintf(buffer, "%*u", grplen, (unsigned) node->group);
len = strlen(buffer);
if(write_fn(data, buffer, len) != len)
return 1;
break;
case 'H': /* Display the history line */
for(seg=node->line->head; seg; seg=seg->next) {
len = seg->next ? GLH_SEG_SIZE : strlen(seg->s);
if(write_fn(data, seg->s, len) != len)
return 1;
};
break;
case '%': /* A literal % symbol */
if(write_fn(data, "%", 1) != 1)
return 1;
break;
};
/*
* Skip the directive.
*/
if(*fptr)
fptr++;
};
};
};
};
return 0;
}
/*.......................................................................
* Change the size of the history buffer.
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* bufsize size_t The number of bytes in the history buffer, or 0
* to delete the buffer completely.
* Output:
* return int 0 - OK.
* 1 - Insufficient memory (the previous buffer
* will have been retained). No error message
* will be displayed.
*/
int _glh_resize_history(GlHistory *glh, size_t bufsize)
{
int nbuff; /* The number of segments in the new buffer */
int i;
/*
* Check the arguments.
*/
if(!glh) {
errno = EINVAL;
return 1;
};
/*
* How many buffer segments does the requested buffer size correspond
* to?
*/
nbuff = (bufsize+GLH_SEG_SIZE-1) / GLH_SEG_SIZE;
/*
* Has a different size than the current size been requested?
*/
if(glh->nbuff != nbuff) {
/*
* Cancel any ongoing search.
*/
(void) _glh_cancel_search(glh);
/*
* Create a wholly new buffer?
*/
if(glh->nbuff == 0 && nbuff>0) {
glh->buffer = (GlhLineSeg *) malloc(sizeof(GlhLineSeg) * nbuff);
if(!glh->buffer)
return 1;
glh->nbuff = nbuff;
glh->nfree = glh->nbuff;
glh->nbusy = 0;
glh->nline = 0;
/*
* Link the currently unused nodes of the buffer into a list.
*/
glh->unused = glh->buffer;
for(i=0; inbuff-1; i++) {
GlhLineSeg *seg = glh->unused + i;
seg->next = seg + 1;
};
glh->unused[i].next = NULL;
/*
* Delete an existing buffer?
*/
} else if(nbuff == 0) {
_glh_clear_history(glh, 1);
free(glh->buffer);
glh->buffer = NULL;
glh->unused = NULL;
glh->nbuff = 0;
glh->nfree = 0;
glh->nbusy = 0;
glh->nline = 0;
/*
* Change from one finite buffer size to another?
*/
} else {
GlhLineSeg *buffer; /* The resized buffer */
int nbusy; /* The number of used line segments in the new buffer */
/*
* Starting from the oldest line in the buffer, discard lines until
* the buffer contains at most 'nbuff' used line segments.
*/
while(glh->list.head && glh->nbusy > nbuff)
_glh_discard_line(glh, glh->list.head);
/*
* Attempt to allocate a new buffer.
*/
buffer = (GlhLineSeg *) malloc(nbuff * sizeof(GlhLineSeg));
if(!buffer) {
errno = ENOMEM;
return 1;
};
/*
* Copy the used segments of the old buffer to the start of the new buffer.
*/
nbusy = 0;
for(i=0; ihash.bucket + i;
GlhHashNode *hnode;
for(hnode=b->lines; hnode; hnode=hnode->next) {
GlhLineSeg *seg = hnode->head;
hnode->head = buffer + nbusy;
for( ; seg; seg=seg->next) {
buffer[nbusy] = *seg;
buffer[nbusy].next = seg->next ? &buffer[nbusy+1] : NULL;
nbusy++;
};
};
};
/*
* Make a list of the new buffer's unused segments.
*/
for(i=nbusy; ibuffer);
/*
* Install the new buffer.
*/
glh->buffer = buffer;
glh->nbuff = nbuff;
glh->nbusy = nbusy;
glh->nfree = nbuff - nbusy;
glh->unused = glh->nfree > 0 ? (buffer + nbusy) : NULL;
};
};
return 0;
}
/*.......................................................................
* Set an upper limit to the number of lines that can be recorded in the
* history list, or remove a previously specified limit.
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* max_lines int The maximum number of lines to allow, or -1 to
* cancel a previous limit and allow as many lines
* as will fit in the current history buffer size.
*/
void _glh_limit_history(GlHistory *glh, int max_lines)
{
if(!glh)
return;
/*
* Apply a new limit?
*/
if(max_lines >= 0 && max_lines != glh->max_lines) {
/*
* Count successively older lines until we reach the start of the
* list, or until we have seen max_lines lines (at which point 'node'
* will be line number max_lines+1).
*/
int nline = 0;
GlhLineNode *node;
for(node=glh->list.tail; node && ++nline <= max_lines; node=node->prev)
;
/*
* Discard any lines that exceed the limit.
*/
if(node) {
GlhLineNode *oldest = node->next; /* The oldest line to be kept */
/*
* Delete nodes from the head of the list until we reach the node that
* is to be kept.
*/
while(glh->list.head && glh->list.head != oldest)
_glh_discard_line(glh, glh->list.head);
};
};
/*
* Record the new limit.
*/
glh->max_lines = max_lines;
return;
}
/*.......................................................................
* Discard either all history, or the history associated with the current
* history group.
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* all_groups int If true, clear all of the history. If false,
* clear only the stored lines associated with the
* currently selected history group.
*/
void _glh_clear_history(GlHistory *glh, int all_groups)
{
int i;
/*
* Check the arguments.
*/
if(!glh)
return;
/*
* Cancel any ongoing search.
*/
(void) _glh_cancel_search(glh);
/*
* Delete all history lines regardless of group?
*/
if(all_groups) {
/*
* Claer the time-ordered list of lines.
*/
_rst_FreeList(glh->list.node_mem);
glh->list.head = glh->list.tail = NULL;
glh->nline = 0;
glh->id_node = NULL;
/*
* Clear the hash table.
*/
for(i=0; ihash.bucket[i].lines = NULL;
_rst_FreeList(glh->hash.node_mem);
/*
* Move all line segment nodes back onto the list of unused segments.
*/
if(glh->buffer) {
glh->unused = glh->buffer;
for(i=0; inbuff-1; i++) {
GlhLineSeg *seg = glh->unused + i;
seg->next = seg + 1;
};
glh->unused[i].next = NULL;
glh->nfree = glh->nbuff;
glh->nbusy = 0;
} else {
glh->unused = NULL;
glh->nbusy = glh->nfree = 0;
};
/*
* Just delete lines of the current group?
*/
} else {
GlhLineNode *node; /* The line node being checked */
GlhLineNode *next; /* The line node that follows 'node' */
/*
* Search out and delete the line nodes of the current group.
*/
for(node=glh->list.head; node; node=next) {
/*
* Keep a record of the following node before we delete the current
* node.
*/
next = node->next;
/*
* Discard this node?
*/
if(node->group == glh->group)
_glh_discard_line(glh, node);
};
};
return;
}
/*.......................................................................
* Temporarily enable or disable the history list.
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* enable int If true, turn on the history mechanism. If
* false, disable it.
*/
void _glh_toggle_history(GlHistory *glh, int enable)
{
if(glh)
glh->enable = enable;
}
/*.......................................................................
* Discard a given archived input line.
*
* Input:
* glh GlHistory * The history container object.
* node GlhLineNode * The line to be discarded, specified via its
* entry in the time-ordered list of historical
* input lines.
*/
static void _glh_discard_line(GlHistory *glh, GlhLineNode *node)
{
/*
* Remove the node from the linked list.
*/
if(node->prev)
node->prev->next = node->next;
else
glh->list.head = node->next;
if(node->next)
node->next->prev = node->prev;
else
glh->list.tail = node->prev;
/*
* If we are deleting the node that is marked as the start point of the
* last ID search, remove the cached starting point.
*/
if(node == glh->id_node)
glh->id_node = NULL;
/*
* If we are deleting the node that is marked as the start point of the
* next prefix search, cancel the search.
*/
if(node == glh->recall)
_glh_cancel_search(glh);
/*
* Delete our copy of the line.
*/
node->line = _glh_discard_copy(glh, node->line);
/*
* Return the node to the freelist.
*/
(void) _del_FreeListNode(glh->list.node_mem, node);
/*
* Record the removal of a line from the list.
*/
glh->nline--;
return;
}
/*.......................................................................
* Lookup the details of a given history line, given its id.
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* id GlLineID The sequential number of the line.
* Input/Output:
* line const char ** A pointer to a copy of the history line will be
* assigned to *line. Beware that this pointer may
* be invalidated by the next call to any public
* history function.
* group unsigned * The group membership of the line will be assigned
* to *group.
* timestamp time_t * The timestamp of the line will be assigned to
* *timestamp.
* Output:
* return int 0 - The requested line wasn't found.
* 1 - The line was found.
*/
int _glh_lookup_history(GlHistory *glh, GlhLineID id, const char **line,
unsigned *group, time_t *timestamp)
{
GlhLineNode *node; /* The located line location node */
/*
* Check the arguments.
*/
if(!glh)
return 0;
/*
* Search for the line that has the specified ID.
*/
node = _glh_find_id(glh, id);
/*
* Not found?
*/
if(!node)
return 0;
/*
* Has the history line been requested?
*/
if(line) {
/*
* If necessary, reallocate the lookup buffer to accomodate the size of
* a copy of the located line.
*/
if(node->line->len + 1 > glh->lbuf_dim) {
int lbuf_dim = node->line->len + 1;
char *lbuf = realloc(glh->lbuf, lbuf_dim);
if(!lbuf) {
errno = ENOMEM;
return 0;
};
glh->lbuf_dim = lbuf_dim;
glh->lbuf = lbuf;
};
/*
* Copy the history line into the lookup buffer.
*/
_glh_return_line(node->line, glh->lbuf, glh->lbuf_dim);
/*
* Assign the lookup buffer as the returned line pointer.
*/
*line = glh->lbuf;
};
/*
* Does the caller want to know the group of the line?
*/
if(group)
*group = node->group;
/*
* Does the caller want to know the timestamp of the line?
*/
if(timestamp)
*timestamp = node->timestamp;
return 1;
}
/*.......................................................................
* Lookup a node in the history list by its ID.
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* id GlhLineID The ID of the line to be returned.
* Output:
* return GlhLIneNode * The located node, or NULL if not found.
*/
static GlhLineNode *_glh_find_id(GlHistory *glh, GlhLineID id)
{
GlhLineNode *node; /* The node being checked */
/*
* Is history enabled?
*/
if(!glh->enable || !glh->list.head)
return NULL;
/*
* If possible, start at the end point of the last ID search.
* Otherwise start from the head of the list.
*/
node = glh->id_node;
if(!node)
node = glh->list.head;
/*
* Search forwards from 'node'?
*/
if(node->id < id) {
while(node && node->id != id)
node = node->next;
glh->id_node = node ? node : glh->list.tail;
/*
* Search backwards from 'node'?
*/
} else {
while(node && node->id != id)
node = node->prev;
glh->id_node = node ? node : glh->list.head;
};
/*
* Return the located node (this will be NULL if the ID wasn't found).
*/
return node;
}
/*.......................................................................
* Query the state of the history list. Note that any of the input/output
* pointers can be specified as NULL.
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* Input/Output:
* enabled int * If history is enabled, *enabled will be
* set to 1. Otherwise it will be assigned 0.
* group unsigned * The current history group ID will be assigned
* to *group.
* max_lines int * The currently requested limit on the number
* of history lines in the list, or -1 if
* unlimited.
*/
void _glh_state_of_history(GlHistory *glh, int *enabled, unsigned *group,
int *max_lines)
{
if(glh) {
if(enabled)
*enabled = glh->enable;
if(group)
*group = glh->group;
if(max_lines)
*max_lines = glh->max_lines;
};
}
/*.......................................................................
* Get the range of lines in the history buffer.
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* Input/Output:
* oldest unsigned long * The sequential entry number of the oldest
* line in the history list will be assigned
* to *oldest, unless there are no lines, in
* which case 0 will be assigned.
* newest unsigned long * The sequential entry number of the newest
* line in the history list will be assigned
* to *newest, unless there are no lines, in
* which case 0 will be assigned.
* nlines int * The number of lines currently in the history
* list.
*/
void _glh_range_of_history(GlHistory *glh, unsigned long *oldest,
unsigned long *newest, int *nlines)
{
if(glh) {
if(oldest)
*oldest = glh->list.head ? glh->list.head->id : 0;
if(newest)
*newest = glh->list.tail ? glh->list.tail->id : 0;
if(nlines)
*nlines = glh->nline;
};
}
/*.......................................................................
* Return the size of the history buffer and the amount of the
* buffer that is currently in use.
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* Input/Output:
* buff_size size_t * The size of the history buffer (bytes).
* buff_used size_t * The amount of the history buffer that
* is currently occupied (bytes).
*/
void _glh_size_of_history(GlHistory *glh, size_t *buff_size, size_t *buff_used)
{
if(glh) {
if(buff_size)
*buff_size = (glh->nbusy + glh->nfree) * GLH_SEG_SIZE;
/*
* Determine the amount of buffer space that is currently occupied.
*/
if(buff_used)
*buff_used = glh->nbusy * GLH_SEG_SIZE;
};
}
/*.......................................................................
* Return extra information (ie. in addition to that provided by errno)
* about the last error to occur in any of the public functions of this
* module.
*
* Input:
* glh GlHistory * The container of the history list.
* Output:
* return const char * A pointer to the internal buffer in which
* the error message is temporarily stored.
*/
const char *_glh_last_error(GlHistory *glh)
{
return glh ? _err_get_msg(glh->err) : "NULL GlHistory argument";
}
/*.......................................................................
* Unless already stored, store a copy of the line in the history buffer,
* then return a reference-counted hash-node pointer to this copy.
*
* Input:
* glh GlHistory * The history maintenance buffer.
* line const char * The history line to be recorded.
* n size_t The length of the string, excluding any '\0'
* terminator.
* Output:
* return GlhHashNode * The hash-node containing the stored line, or
* NULL on error.
*/
static GlhHashNode *_glh_acquire_copy(GlHistory *glh, const char *line,
size_t n)
{
GlhHashBucket *bucket; /* The hash-table bucket of the line */
GlhHashNode *hnode; /* The hash-table node of the line */
int i;
/*
* In which bucket should the line be recorded?
*/
bucket = glh_find_bucket(glh, line, n);
/*
* Is the line already recorded there?
*/
hnode = glh_find_hash_node(bucket, line, n);
/*
* If the line isn't recorded in the buffer yet, make room for it.
*/
if(!hnode) {
GlhLineSeg *seg; /* A line segment */
int offset; /* An offset into line[] */
/*
* How many string segments will be needed to record the new line,
* including space for a '\0' terminator?
*/
int nseg = ((n+1) + GLH_SEG_SIZE-1) / GLH_SEG_SIZE;
/*
* Discard the oldest history lines in the buffer until at least
* 'nseg' segments have been freed up, or until we run out of buffer
* space.
*/
while(glh->nfree < nseg && glh->nbusy > 0)
_glh_discard_line(glh, glh->list.head);
/*
* If the buffer is smaller than the new line, don't attempt to truncate
* it to fit. Simply don't archive it.
*/
if(glh->nfree < nseg)
return NULL;
/*
* Record the line in the first 'nseg' segments of the list of unused segments.
*/
offset = 0;
for(i=0,seg=glh->unused; inext, offset+=GLH_SEG_SIZE)
memcpy(seg->s, line + offset, GLH_SEG_SIZE);
memcpy(seg->s, line + offset, n-offset);
seg->s[n-offset] = '\0';
/*
* Create a new hash-node for the line.
*/
hnode = (GlhHashNode *) _new_FreeListNode(glh->hash.node_mem);
if(!hnode)
return NULL;
/*
* Move the copy of the line from the list of unused segments to
* the hash node.
*/
hnode->head = glh->unused;
glh->unused = seg->next;
seg->next = NULL;
glh->nbusy += nseg;
glh->nfree -= nseg;
/*
* Prepend the new hash node to the list within the associated bucket.
*/
hnode->next = bucket->lines;
bucket->lines = hnode;
/*
* Initialize the rest of the members of the hash node.
*/
hnode->len = n;
hnode->reported = 0;
hnode->used = 0;
hnode->bucket = bucket;
};
/*
* Increment the reference count of the line.
*/
hnode->used++;
return hnode;
}
/*.......................................................................
* Decrement the reference count of the history line of a given hash-node,
* and if the count reaches zero, delete both the hash-node and the
* buffered copy of the line.
*
* Input:
* glh GlHistory * The history container object.
* hnode GlhHashNode * The node to be removed.
* Output:
* return GlhHashNode * The deleted hash-node (ie. NULL).
*/
static GlhHashNode *_glh_discard_copy(GlHistory *glh, GlhHashNode *hnode)
{
if(hnode) {
GlhHashBucket *bucket = hnode->bucket;
/*
* If decrementing the reference count of the hash-node doesn't reduce
* the reference count to zero, then the line is still in use in another
* object, so don't delete it yet. Return NULL to indicate that the caller's
* access to the hash-node copy has been deleted.
*/
if(--hnode->used >= 1)
return NULL;
/*
* Remove the hash-node from the list in its parent bucket.
*/
if(bucket->lines == hnode) {
bucket->lines = hnode->next;
} else {
GlhHashNode *prev; /* The node which precedes hnode in the bucket */
for(prev=bucket->lines; prev && prev->next != hnode; prev=prev->next)
;
if(prev)
prev->next = hnode->next;
};
hnode->next = NULL;
/*
* Return the line segments of the hash-node to the list of unused segments.
*/
if(hnode->head) {
GlhLineSeg *tail; /* The last node in the list of line segments */
int nseg; /* The number of segments being discarded */
/*
* Get the last node of the list of line segments referenced in the hash-node,
* while counting the number of line segments used.
*/
for(nseg=1,tail=hnode->head; tail->next; nseg++,tail=tail->next)
;
/*
* Prepend the list of line segments used by the hash node to the
* list of unused line segments.
*/
tail->next = glh->unused;
glh->unused = hnode->head;
glh->nbusy -= nseg;
glh->nfree += nseg;
};
/*
* Return the container of the hash-node to the freelist.
*/
hnode = (GlhHashNode *) _del_FreeListNode(glh->hash.node_mem, hnode);
};
return NULL;
}
/*.......................................................................
* Private function to locate the hash bucket associated with a given
* history line.
*
* This uses a hash-function described in the dragon-book
* ("Compilers - Principles, Techniques and Tools", by Aho, Sethi and
* Ullman; pub. Adison Wesley) page 435.
*
* Input:
* glh GlHistory * The history container object.
* line const char * The historical line to look up.
* n size_t The length of the line in line[], excluding
* any '\0' terminator.
* Output:
* return GlhHashBucket * The located hash-bucket.
*/
static GlhHashBucket *glh_find_bucket(GlHistory *glh, const char *line,
size_t n)
{
unsigned long h = 0L;
int i;
for(i=0; ihash.bucket + (h % GLH_HASH_SIZE);
}
/*.......................................................................
* Find a given history line within a given hash-table bucket.
*
* Input:
* bucket GlhHashBucket * The hash-table bucket in which to search.
* line const char * The historical line to lookup.
* n size_t The length of the line in line[], excluding
* any '\0' terminator.
* Output:
* return GlhHashNode * The hash-table entry of the line, or NULL
* if not found.
*/
static GlhHashNode *glh_find_hash_node(GlhHashBucket *bucket, const char *line,
size_t n)
{
GlhHashNode *node; /* A node in the list of lines in the bucket */
/*
* Compare each of the lines in the list of lines, against 'line'.
*/
for(node=bucket->lines; node; node=node->next) {
if(_glh_is_line(node, line, n))
return node;
};
return NULL;
}
/*.......................................................................
* Return non-zero if a given string is equal to a given segmented line
* node.
*
* Input:
* hash GlhHashNode * The hash-table entry of the line.
* line const char * The string to be compared to the segmented
* line.
* n size_t The length of the line in line[], excluding
* any '\0' terminator.
* Output:
* return int 0 - The lines differ.
* 1 - The lines are the same.
*/
static int _glh_is_line(GlhHashNode *hash, const char *line, size_t n)
{
GlhLineSeg *seg; /* A node in the list of line segments */
int i;
/*
* Do the two lines have the same length?
*/
if(n != hash->len)
return 0;
/*
* Compare the characters of the segmented and unsegmented versions
* of the line.
*/
for(seg=hash->head; n>0 && seg; seg=seg->next) {
const char *s = seg->s;
for(i=0; n>0 && ilen > line->len)
return 0;
/*
* Compare the line to the prefix.
*/
while(pstr.c != '\0' && pstr.c == lstr.c) {
glh_step_stream(&lstr);
glh_step_stream(&pstr);
};
/*
* Did we reach the end of the prefix string before finding
* any differences?
*/
return pstr.c == '\0';
}
/*.......................................................................
* Copy a given history line into a specified output string.
*
* Input:
* hash GlhHashNode The hash-table entry of the history line to
* be copied.
* line char * A copy of the history line.
* dim size_t The allocated dimension of the line buffer.
*/
static void _glh_return_line(GlhHashNode *hash, char *line, size_t dim)
{
GlhLineSeg *seg; /* A node in the list of line segments */
int i;
for(seg=hash->head; dim>0 && seg; seg=seg->next) {
const char *s = seg->s;
for(i=0; dim>0 && irecall && glh->recall == glh->list.tail &&
!_glh_is_line(glh->recall->line, line, strlen(line))) {
_glh_cancel_search(glh);
};
/*
* If this is the first line recall of a new recall session, save the
* current line for potential recall later, and mark it as the last
* line recalled.
*/
if(!glh->recall) {
if(_glh_add_history(glh, line, 1))
return 1;
glh->recall = glh->list.tail;
/*
* The above call to _glh_add_history() will have incremented the line
* sequence number, after adding the line. Since we only want this to
* to be incremented for permanently entered lines, decrement it again.
*/
glh->seq--;
};
return 0;
}
/*.......................................................................
* Return non-zero if a history search session is currently in progress.
*
* Input:
* glh GlHistory * The input-line history maintenance object.
* Output:
* return int 0 - No search is currently in progress.
* 1 - A search is in progress.
*/
int _glh_search_active(GlHistory *glh)
{
return glh && glh->recall;
}
/*.......................................................................
* Initialize a character iterator object to point to the start of a
* given history line. The first character of the line will be placed
* in str->c, and subsequent characters can be placed there by calling
* glh_strep_stream().
*
* Input:
* str GlhLineStream * The iterator object to be initialized.
* line GlhHashNode * The history line to be iterated over (a
* NULL value here, is interpretted as an
* empty string by glh_step_stream()).
*/
static void glh_init_stream(GlhLineStream *str, GlhHashNode *line)
{
str->seg = line ? line->head : NULL;
str->posn = 0;
str->c = str->seg ? str->seg->s[0] : '\0';
}
/*.......................................................................
* Copy the next unread character in the line being iterated, in str->c.
* Once the end of the history line has been reached, all futher calls
* set str->c to '\0'.
*
* Input:
* str GlhLineStream * The history-line iterator to read from.
*/
static void glh_step_stream(GlhLineStream *str)
{
/*
* Get the character from the current iterator position within the line.
*/
str->c = str->seg ? str->seg->s[str->posn] : '\0';
/*
* Unless we have reached the end of the string, move the iterator
* to the position of the next character in the line.
*/
if(str->c != '\0' && ++str->posn >= GLH_SEG_SIZE) {
str->posn = 0;
str->seg = str->seg->next;
};
}
/*.......................................................................
* Return non-zero if the specified search prefix contains any glob
* wildcard characters.
*
* Input:
* prefix GlhHashNode * The search prefix.
* Output:
* return int 0 - The prefix doesn't contain any globbing
* characters.
* 1 - The prefix contains at least one
* globbing character.
*/
static int glh_contains_glob(GlhHashNode *prefix)
{
GlhLineStream pstr; /* The stream that is used to traverse 'prefix' */
/*
* Wrap a stream iterator around the prefix, so that we can traverse it
* without worrying about line-segmentation.
*/
glh_init_stream(&pstr, prefix);
/*
* Search for unescaped wildcard characters.
*/
while(pstr.c != '\0') {
switch(pstr.c) {
case '\\': /* Skip escaped characters */
glh_step_stream(&pstr);
break;
case '*': case '?': case '[': /* A wildcard character? */
return 1;
break;
};
glh_step_stream(&pstr);
};
/*
* No wildcard characters were found.
*/
return 0;
}
/*.......................................................................
* Return non-zero if the history line matches a search prefix containing
* a glob pattern.
*
* Input:
* lstr GlhLineStream * The iterator stream being used to traverse
* the history line that is being matched.
* pstr GlhLineStream * The iterator stream being used to traverse
* the pattern.
* Output:
* return int 0 - Doesn't match.
* 1 - The line matches the pattern.
*/
static int glh_line_matches_glob(GlhLineStream *lstr, GlhLineStream *pstr)
{
/*
* Match each character of the pattern until we reach the end of the
* pattern.
*/
while(pstr->c != '\0') {
/*
* Handle the next character of the pattern.
*/
switch(pstr->c) {
/*
* A match zero-or-more characters wildcard operator.
*/
case '*':
/*
* Skip the '*' character in the pattern.
*/
glh_step_stream(pstr);
/*
* If the pattern ends with the '*' wildcard, then the
* rest of the line matches this.
*/
if(pstr->c == '\0')
return 1;
/*
* Using the wildcard to match successively longer sections of
* the remaining characters of the line, attempt to match
* the tail of the line against the tail of the pattern.
*/
while(lstr->c) {
GlhLineStream old_lstr = *lstr;
GlhLineStream old_pstr = *pstr;
if(glh_line_matches_glob(lstr, pstr))
return 1;
/*
* Restore the line and pattern iterators for a new try.
*/
*lstr = old_lstr;
*pstr = old_pstr;
/*
* Prepare to try again, one character further into the line.
*/
glh_step_stream(lstr);
};
return 0; /* The pattern following the '*' didn't match */
break;
/*
* A match-one-character wildcard operator.
*/
case '?':
/*
* If there is a character to be matched, skip it and advance the
* pattern pointer.
*/
if(lstr->c) {
glh_step_stream(lstr);
glh_step_stream(pstr);
/*
* If we hit the end of the line, there is no character
* matching the operator, so the pattern doesn't match.
*/
} else {
return 0;
};
break;
/*
* A character range operator, with the character ranges enclosed
* in matching square brackets.
*/
case '[':
glh_step_stream(pstr); /* Skip the '[' character */
if(!lstr->c || !glh_matches_range(lstr->c, pstr))
return 0;
glh_step_stream(lstr); /* Skip the character that matched */
break;
/*
* A backslash in the pattern prevents the following character as
* being seen as a special character.
*/
case '\\':
glh_step_stream(pstr); /* Skip the backslash */
/* Note fallthrough to default */
/*
* A normal character to be matched explicitly.
*/
default:
if(lstr->c == pstr->c) {
glh_step_stream(lstr);
glh_step_stream(pstr);
} else {
return 0;
};
break;
};
};
/*
* To get here, pattern must have been exhausted. The line only
* matches the pattern if the line as also been exhausted.
*/
return pstr->c == '\0' && lstr->c == '\0';
}
/*.......................................................................
* Match a character range expression terminated by an unescaped close
* square bracket.
*
* Input:
* c char The character to be matched with the range
* pattern.
* pstr GlhLineStream * The iterator stream being used to traverse
* the pattern.
* Output:
* return int 0 - Doesn't match.
* 1 - The character matched.
*/
static int glh_matches_range(char c, GlhLineStream *pstr)
{
int invert = 0; /* True to invert the sense of the match */
int matched = 0; /* True if the character matched the pattern */
char lastc = '\0'; /* The previous character in the pattern */
/*
* If the first character is a caret, the sense of the match is
* inverted and only if the character isn't one of those in the
* range, do we say that it matches.
*/
if(pstr->c == '^') {
glh_step_stream(pstr);
invert = 1;
};
/*
* The hyphen is only a special character when it follows the first
* character of the range (not including the caret).
*/
if(pstr->c == '-') {
glh_step_stream(pstr);
if(c == '-')
matched = 1;
/*
* Skip other leading '-' characters since they make no sense.
*/
while(pstr->c == '-')
glh_step_stream(pstr);
};
/*
* The hyphen is only a special character when it follows the first
* character of the range (not including the caret or a hyphen).
*/
if(pstr->c == ']') {
glh_step_stream(pstr);
if(c == ']')
matched = 1;
};
/*
* Having dealt with the characters that have special meanings at
* the beginning of a character range expression, see if the
* character matches any of the remaining characters of the range,
* up until a terminating ']' character is seen.
*/
while(!matched && pstr->c && pstr->c != ']') {
/*
* Is this a range of characters signaled by the two end characters
* separated by a hyphen?
*/
if(pstr->c == '-') {
glh_step_stream(pstr); /* Skip the hyphen */
if(pstr->c != ']') {
if(c >= lastc && c <= pstr->c)
matched = 1;
};
/*
* A normal character to be compared directly.
*/
} else if(pstr->c == c) {
matched = 1;
};
/*
* Record and skip the character that we just processed.
*/
lastc = pstr->c;
if(pstr->c != ']')
glh_step_stream(pstr);
};
/*
* Find the terminating ']'.
*/
while(pstr->c && pstr->c != ']')
glh_step_stream(pstr);
/*
* Did we find a terminating ']'?
*/
if(pstr->c == ']') {
/*
* Skip the terminating ']'.
*/
glh_step_stream(pstr);
/*
* If the pattern started with a caret, invert the sense of the match.
*/
if(invert)
matched = !matched;
/*
* If the pattern didn't end with a ']', then it doesn't match,
* regardless of the value of the required sense of the match.
*/
} else {
matched = 0;
};
return matched;
}
yuma123_2.14/libtecla/cplfile.h 0000664 0001750 0001750 00000010607 14770023131 016475 0 ustar vladimir vladimir #ifndef cplfile_h
#define cplfile_h
/*
* Copyright (c) 2000, 2001, 2002, 2003, 2004 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.
*/
typedef struct CompleteFile CompleteFile;
/*
* Create a file-completion resource object.
*/
CompleteFile *_new_CompleteFile(void);
/*
* Delete a file-completion resource object.
*/
CompleteFile *_del_CompleteFile(CompleteFile *cf);
/*.......................................................................
* Complete the string between path[0] and path[len-1] as a pathname,
* leaving the last component uncompleted if it is potentially ambiguous,
* and returning an array of possible completions. Note that the returned
* container belongs to the 'cf' object and its contents will change on
* subsequent calls to this function.
*
* 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 path[] are
* interpreted as escaping the characters
* that follow them, and any spaces, tabs,
* backslashes, or wildcard characters in the
* returned suffixes will be similarly be 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);
/*.......................................................................
* Return a description of the error that occurred on the last call to
* cf_complete_file().
*
* Input:
* cf CompleteFile * The path-completion resource object.
* Output:
* return char * The description of the last error.
*/
const char *_cf_last_error(CompleteFile *cf);
#endif
yuma123_2.14/libtecla/freelist.h 0000664 0001750 0001750 00000006201 14770023131 016667 0 ustar vladimir vladimir #ifndef freelist_h
#define freelist_h
/*
* Copyright (c) 2000, 2001, 2002, 2003, 2004 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.
*/
/*
* This module provides a memory allocation scheme that helps to
* prevent memory fragmentation by allocating large blocks of
* fixed sized objects and forming them into a free-list for
* subsequent allocations. The free-list is expanded as needed.
*/
typedef struct FreeList FreeList;
/*
* Allocate a new free-list from blocks of 'blocking_factor' objects of size
* node_size. The node_size argument should be determined by applying
* the sizeof() operator to the object type that you intend to allocate from
* the freelist.
*/
FreeList *_new_FreeList(size_t node_size, unsigned blocking_factor);
/*
* If it is known that none of the nodes currently allocated from
* a freelist are still in use, the following function can be called
* to return all nodes to the freelist without the overhead of
* having to call del_FreeListNode() for every allocated node. The
* nodes of the freelist can then be reused by future callers to
* new_FreeListNode().
*/
void _rst_FreeList(FreeList *fl);
/*
* Delete a free-list.
*/
FreeList *_del_FreeList(FreeList *fl, int force);
/*
* Determine the number of nodes that are currently in use.
*/
long _busy_FreeListNodes(FreeList *fl);
/*
* Query the number of allocated nodes in the freelist which are
* currently unused.
*/
long _idle_FreeListNodes(FreeList *fl);
/*
* Allocate a new object from a free-list.
*/
void *_new_FreeListNode(FreeList *fl);
/*
* Return an object to the free-list that it was allocated from.
*/
void *_del_FreeListNode(FreeList *fl, void *object);
#endif
yuma123_2.14/libtecla/chrqueue.c 0000664 0001750 0001750 00000033271 14770023131 016675 0 ustar vladimir vladimir /*
* Copyright (c) 2000, 2001, 2002, 2003, 2004 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 "ioutil.h"
#include "chrqueue.h"
#include "freelist.h"
#include "errmsg.h"
/*
* Set the number of bytes allocated to each node of the list of
* character buffers. This facility is designed principally as
* an expandible I/O output buffer, so use the stdio buffer size
* where available.
*/
#ifdef BUFSIZ
#define GL_CQ_SIZE BUFSIZ
#else
#define GL_CQ_SIZE 512
#endif
/*
* The queue is contained in a list of fixed sized buffers. New nodes
* are appended to this list as needed to accomodate newly added bytes.
* Old nodes at the head of the list are removed as they are emptied.
*/
typedef struct CqCharBuff CqCharBuff;
struct CqCharBuff {
CqCharBuff *next; /* The next node in the list of buffers */
char bytes[GL_CQ_SIZE]; /* The fixed size buffer of this node */
};
/*
* Define the structure that is used to contain a list of character
* buffers.
*/
struct GlCharQueue {
ErrMsg *err; /* A buffer in which to record error messages */
FreeList *bufmem; /* A free-list of CqCharBuff structures */
struct {
CqCharBuff *head; /* The head of the list of output buffers */
CqCharBuff *tail; /* The tail of the list of output buffers */
} buffers;
int nflush; /* The total number of characters that have been */
/* flushed from the start of the queue since */
/* _glq_empty_queue() was last called. */
int ntotal; /* The total number of characters that have been */
/* appended to the queue since _glq_empty_queue() */
/* was last called. */
};
/*.......................................................................
* Create a new GlCharQueue object.
*
* Output:
* return GlCharQueue * The new object, or NULL on error.
*/
GlCharQueue *_new_GlCharQueue(void)
{
GlCharQueue *cq; /* The object to be returned */
/*
* Allocate the container.
*/
cq = malloc(sizeof(GlCharQueue));
if(!cq) {
errno = ENOMEM;
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_GlCharQueue().
*/
cq->err = NULL;
cq->bufmem = NULL;
cq->buffers.head = NULL;
cq->buffers.tail = NULL;
cq->nflush = cq->ntotal = 0;
/*
* Allocate a place to record error messages.
*/
cq->err = _new_ErrMsg();
if(!cq->err)
return _del_GlCharQueue(cq);
/*
* Allocate the freelist of CqCharBuff structures.
*/
cq->bufmem = _new_FreeList(sizeof(CqCharBuff), 1);
if(!cq->bufmem)
return _del_GlCharQueue(cq);
return cq;
}
/*.......................................................................
* Delete a GlCharQueue object.
*
* Input:
* cq GlCharQueue * The object to be deleted.
* Output:
* return GlCharQueue * The deleted object (always NULL).
*/
GlCharQueue *_del_GlCharQueue(GlCharQueue *cq)
{
if(cq) {
cq->err = _del_ErrMsg(cq->err);
cq->bufmem = _del_FreeList(cq->bufmem, 1);
free(cq);
};
return NULL;
}
/*.......................................................................
* Append an array of n characters to a character queue.
*
* Input:
* cq GlCharQueue * The queue to append to.
* chars const char * The array of n characters to be appended.
* n int The number of characters in chars[].
* write_fn GL_WRITE_FN * The function to call to output characters,
* or 0 to simply discard the contents of the
* queue. This will be called whenever the
* buffer becomes full. If it fails to release
* any space, the buffer will be extended.
* data void * Anonymous data to pass to write_fn().
* Output:
* return int The number of characters successfully
* appended. This will only be < n on error.
*/
int _glq_append_chars(GlCharQueue *cq, const char *chars, int n,
GlWriteFn *write_fn, void *data)
{
int ndone = 0; /* The number of characters appended so far */
/*
* Check the arguments.
*/
if(!cq || !chars) {
errno = EINVAL;
return 0;
};
/*
* The appended characters may have to be split between multiple
* buffers, so loop for each buffer.
*/
while(ndone < n) {
int ntodo; /* The number of characters remaining to be appended */
int nleft; /* The amount of space remaining in cq->buffers.tail */
int nnew; /* The number of characters to append to cq->buffers.tail */
/*
* Compute the offset at which the next character should be written
* into the tail buffer segment.
*/
int boff = cq->ntotal % GL_CQ_SIZE;
/*
* Since we don't allocate a new buffer until we have at least one
* character to write into it, if boff is 0 at this point, it means
* that we hit the end of the tail buffer segment on the last append,
* so we need to allocate a new one.
*
* If allocating this new node will require a call to malloc(), as
* opposed to using a currently unused node in the freelist, first try
* flushing the current contents of the buffer to the terminal. When
* write_fn() uses blocking I/O, this stops the buffer size ever getting
* bigger than a single buffer node. When it is non-blocking, it helps
* to keep the amount of memory, but it isn't gauranteed to do so.
*/
if(boff == 0 && _idle_FreeListNodes(cq->bufmem) == 0) {
switch(_glq_flush_queue(cq, write_fn, data)) {
case GLQ_FLUSH_DONE:
break;
case GLQ_FLUSH_AGAIN:
errno = 0; /* Don't confuse the caller */
break;
default:
return ndone; /* Error */
};
boff = cq->ntotal % GL_CQ_SIZE;
};
/*
* Since we don't allocate a new buffer until we have at least one
* character to write into it, if boff is 0 at this point, it means
* that we hit the end of the tail buffer segment on the last append,
* so we need to allocate a new one.
*/
if(boff == 0) {
/*
* Allocate the new node.
*/
CqCharBuff *node = (CqCharBuff *) _new_FreeListNode(cq->bufmem);
if(!node) {
_err_record_msg(cq->err, "Insufficient memory to buffer output.",
END_ERR_MSG);
return ndone;
};
/*
* Initialize the node.
*/
node->next = NULL;
/*
* Append the new node to the tail of the list.
*/
if(cq->buffers.tail)
cq->buffers.tail->next = node;
else
cq->buffers.head = node;
cq->buffers.tail = node;
};
/*
* How much room is there for new characters in the current tail node?
*/
nleft = GL_CQ_SIZE - boff;
/*
* How many characters remain to be appended?
*/
ntodo = n - ndone;
/*
* How many characters should we append to the current tail node?
*/
nnew = nleft < ntodo ? nleft : ntodo;
/*
* Append the latest prefix of nnew characters.
*/
memcpy(cq->buffers.tail->bytes + boff, chars + ndone, nnew);
cq->ntotal += nnew;
ndone += nnew;
};
/*
* Return the count of the number of characters successfully appended.
*/
return ndone;
}
/*.......................................................................
* Discard the contents of a queue of characters.
*
* Input:
* cq GlCharQueue * The queue to clear.
*/
void _glq_empty_queue(GlCharQueue *cq)
{
if(cq) {
/*
* Return all list nodes to their respective free-lists.
*/
_rst_FreeList(cq->bufmem);
/*
* Mark the lists as empty.
*/
cq->buffers.head = cq->buffers.tail = NULL;
cq->nflush = cq->ntotal = 0;
};
}
/*.......................................................................
* Return a count of the number of characters currently in the queue.
*
* Input:
* cq GlCharQueue * The queue of interest.
* Output:
* return int The number of characters in the queue.
*/
int _glq_char_count(GlCharQueue *cq)
{
return (cq && cq->buffers.head) ? (cq->ntotal - cq->nflush) : 0;
}
/*.......................................................................
* Write as many characters as possible from the start of a character
* queue via a given output callback function, removing those written
* from the queue.
*
* Input:
* cq GlCharQueue * The queue to write characters from.
* write_fn GL_WRITE_FN * The function to call to output characters,
* or 0 to simply discard the contents of the
* queue.
* data void * Anonymous data to pass to write_fn().
* Output:
* return GlFlushState The status of the flush operation:
* GLQ_FLUSH_DONE - The flush operation
* completed successfully.
* GLQ_FLUSH_AGAIN - The flush operation
* couldn't be completed
* on this call. Call this
* function again when the
* output channel can accept
* further output.
* GLQ_FLUSH_ERROR Unrecoverable error.
*/
GlqFlushState _glq_flush_queue(GlCharQueue *cq, GlWriteFn *write_fn,
void *data)
{
/*
* Check the arguments.
*/
if(!cq) {
errno = EINVAL;
return GLQ_FLUSH_ERROR;
};
/*
* If possible keep writing until all of the chained buffers have been
* emptied and removed from the list.
*/
while(cq->buffers.head) {
/*
* Are we looking at the only node in the list?
*/
int is_tail = cq->buffers.head == cq->buffers.tail;
/*
* How many characters more than an exact multiple of the buffer-segment
* size have been added to the buffer so far?
*/
int nmodulo = cq->ntotal % GL_CQ_SIZE;
/*
* How many characters of the buffer segment at the head of the list
* have been used? Note that this includes any characters that have
* already been flushed. Also note that if nmodulo==0, this means that
* the tail buffer segment is full. The reason for this is that we
* don't allocate new tail buffer segments until there is at least one
* character to be added to them.
*/
int nhead = (!is_tail || nmodulo == 0) ? GL_CQ_SIZE : nmodulo;
/*
* How many characters remain to be flushed from the buffer
* at the head of the list?
*/
int nbuff = nhead - (cq->nflush % GL_CQ_SIZE);
/*
* Attempt to write this number.
*/
int nnew = write_fn(data, cq->buffers.head->bytes +
cq->nflush % GL_CQ_SIZE, nbuff);
/*
* Was anything written?
*/
if(nnew > 0) {
/*
* Increment the count of the number of characters that have
* been flushed from the head of the queue.
*/
cq->nflush += nnew;
/*
* If we succeded in writing all of the contents of the current
* buffer segment, remove it from the queue.
*/
if(nnew == nbuff) {
/*
* If we just emptied the last node left in the list, then the queue is
* now empty and should be reset.
*/
if(is_tail) {
_glq_empty_queue(cq);
} else {
/*
* Get the node to be removed from the head of the list.
*/
CqCharBuff *node = cq->buffers.head;
/*
* Make the node that follows it the new head of the queue.
*/
cq->buffers.head = node->next;
/*
* Return it to the freelist.
*/
node = (CqCharBuff *) _del_FreeListNode(cq->bufmem, node);
};
};
/*
* If the write blocked, request that this function be called again
* when space to write next becomes available.
*/
} else if(nnew==0) {
return GLQ_FLUSH_AGAIN;
/*
* I/O error.
*/
} else {
_err_record_msg(cq->err, "Error writing to terminal", END_ERR_MSG);
return GLQ_FLUSH_ERROR;
};
};
/*
* To get here the queue must now be empty.
*/
return GLQ_FLUSH_DONE;
}
/*.......................................................................
* Return extra information (ie. in addition to that provided by errno)
* about the last error to occur in any of the public functions of this
* module.
*
* Input:
* cq GlCharQueue * The container of the history list.
* Output:
* return const char * A pointer to the internal buffer in which
* the error message is temporarily stored.
*/
const char *_glq_last_error(GlCharQueue *cq)
{
return cq ? _err_get_msg(cq->err) : "NULL GlCharQueue argument";
}
yuma123_2.14/libtecla/pathutil.c 0000664 0001750 0001750 00000036330 14770023131 016705 0 ustar vladimir vladimir /*
* Copyright (c) 2000, 2001, 2002, 2003, 2004 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.
*/
/*
* If file-system access is to be excluded, this module has no function,
* so all of its code should be excluded.
*/
#ifndef WITHOUT_FILE_SYSTEM
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "pathutil.h"
/*.......................................................................
* Create a new PathName object.
*
* Output:
* return PathName * The new object, or NULL on error.
*/
PathName *_new_PathName(void)
{
PathName *path; /* The object to be returned */
/*
* Allocate the container.
*/
path = (PathName *) malloc(sizeof(PathName));
if(!path) {
errno = ENOMEM;
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_PathName().
*/
path->name = NULL;
path->dim = 0;
/*
* Figure out the maximum length of an expanded pathname.
*/
path->dim = _pu_pathname_dim();
if(path->dim == 0)
return _del_PathName(path);
/*
* Allocate the pathname buffer.
*/
path->name = (char *)malloc(path->dim * sizeof(char));
if(!path->name) {
errno = ENOMEM;
return _del_PathName(path);
};
return path;
}
/*.......................................................................
* Delete a PathName object.
*
* Input:
* path PathName * The object to be deleted.
* Output:
* return PathName * The deleted object (always NULL).
*/
PathName *_del_PathName(PathName *path)
{
if(path) {
if(path->name)
free(path->name);
free(path);
};
return NULL;
}
/*.......................................................................
* Return the pathname to a zero-length string.
*
* Input:
* path PathName * The pathname container.
* Output:
* return char * The cleared pathname buffer, or NULL on error.
*/
char *_pn_clear_path(PathName *path)
{
/*
* Check the arguments.
*/
if(!path) {
errno = EINVAL;
return NULL;
};
path->name[0] = '\0';
return path->name;
}
/*.......................................................................
* Append a string to a pathname, increasing the size of the pathname
* buffer if needed.
*
* Input:
* path PathName * The pathname container.
* string const char * The string to be appended to the pathname.
* Note that regardless of the slen argument,
* this should be a '\0' terminated string.
* slen int The maximum number of characters to append
* from string[], or -1 to append the whole
* string.
* remove_escapes int If true, remove the backslashes that escape
* spaces, tabs, backslashes etc..
* Output:
* return char * The pathname string path->name[], which may
* have been reallocated, or NULL if there was
* insufficient memory to extend the pathname.
*/
char *_pn_append_to_path(PathName *path, const char *string, int slen,
int remove_escapes)
{
int pathlen; /* The length of the pathname */
int i;
/*
* Check the arguments.
*/
if(!path || !string) {
errno = EINVAL;
return NULL;
};
/*
* Get the current length of the pathname.
*/
pathlen = strlen(path->name);
/*
* How many characters should be appended?
*/
if(slen < 0 || slen > strlen(string))
slen = strlen(string);
/*
* Resize the pathname if needed.
*/
if(!_pn_resize_path(path, pathlen + slen))
return NULL;
/*
* Append the string to the output pathname, removing any escape
* characters found therein.
*/
if(remove_escapes) {
int is_escape = 0;
for(i=0; iname[pathlen++] = string[i];
};
/*
* Terminate the string.
*/
path->name[pathlen] = '\0';
} else {
/*
* Append the string directly to the pathname.
*/
memcpy(path->name + pathlen, string, slen);
path->name[pathlen + slen] = '\0';
};
return path->name;
}
/*.......................................................................
* Prepend a string to a pathname, increasing the size of the pathname
* buffer if needed.
*
* Input:
* path PathName * The pathname container.
* string const char * The string to be prepended to the pathname.
* Note that regardless of the slen argument,
* this should be a '\0' terminated string.
* slen int The maximum number of characters to prepend
* from string[], or -1 to append the whole
* string.
* remove_escapes int If true, remove the backslashes that escape
* spaces, tabs, backslashes etc..
* Output:
* return char * The pathname string path->name[], which may
* have been reallocated, or NULL if there was
* insufficient memory to extend the pathname.
*/
char *_pn_prepend_to_path(PathName *path, const char *string, int slen,
int remove_escapes)
{
int pathlen; /* The length of the pathname */
int shift; /* The number of characters to shift the suffix by */
int i,j;
/*
* Check the arguments.
*/
if(!path || !string) {
errno = EINVAL;
return NULL;
};
/*
* Get the current length of the pathname.
*/
pathlen = strlen(path->name);
/*
* How many characters should be appended?
*/
if(slen < 0 || slen > strlen(string))
slen = strlen(string);
/*
* Work out how far we need to shift the original path string to make
* way for the new prefix. When removing escape characters, we need
* final length of the new prefix, after unescaped backslashes have
* been removed.
*/
if(remove_escapes) {
int is_escape = 0;
for(shift=0,i=0; iname + shift, path->name, pathlen+1);
/*
* Copy the new prefix into the vacated space at the beginning of the
* output pathname, removing any escape characters if needed.
*/
if(remove_escapes) {
int is_escape = 0;
for(i=j=0; iname[j++] = string[i];
};
} else {
memcpy(path->name, string, slen);
};
return path->name;
}
/*.......................................................................
* If needed reallocate a given pathname buffer to allow a string of
* a given length to be stored in it.
*
* Input:
* path PathName * The pathname container object.
* length size_t The required length of the pathname buffer,
* not including the terminating '\0'.
* Output:
* return char * The pathname buffer, or NULL if there was
* insufficient memory.
*/
char *_pn_resize_path(PathName *path, size_t length)
{
/*
* Check the arguments.
*/
if(!path) {
errno = EINVAL;
return NULL;
};
/*
* If the pathname buffer isn't large enough to accomodate a string
* of the specified length, attempt to reallocate it with the new
* size, plus space for a terminating '\0'. Also add a bit of
* head room to prevent too many reallocations if the initial length
* turned out to be very optimistic.
*/
if(length + 1 > path->dim) {
size_t dim = length + 1 + PN_PATHNAME_INC;
char *name = (char *) realloc(path->name, dim);
if(!name)
return NULL;
path->name = name;
path->dim = dim;
};
return path->name;
}
/*.......................................................................
* Estimate the largest amount of space needed to store a pathname.
*
* Output:
* return size_t The number of bytes needed, including space for the
* terminating '\0'.
*/
size_t _pu_pathname_dim(void)
{
int maxlen; /* The return value excluding space for the '\0' */
/*
* If the POSIX PATH_MAX macro is defined in limits.h, use it.
*/
#ifdef PATH_MAX
maxlen = PATH_MAX;
/*
* If we have pathconf, use it.
*/
#elif defined(_PC_PATH_MAX)
errno = 0;
maxlen = pathconf(FS_ROOT_DIR, _PC_PATH_MAX);
if(maxlen <= 0 || errno)
maxlen = MAX_PATHLEN_FALLBACK;
/*
* None of the above approaches worked, so substitute our fallback
* guess.
*/
#else
maxlen = MAX_PATHLEN_FALLBACK;
#endif
/*
* Return the amount of space needed to accomodate a pathname plus
* a terminating '\0'.
*/
return maxlen + 1;
}
/*.......................................................................
* Return non-zero if the specified path name refers to a directory.
*
* Input:
* pathname const char * The path to test.
* Output:
* return int 0 - Not a directory.
* 1 - pathname[] refers to a directory.
*/
int _pu_path_is_dir(const char *pathname)
{
struct stat statbuf; /* The file-statistics return buffer */
/*
* Look up the file attributes.
*/
if(stat(pathname, &statbuf) < 0)
return 0;
/*
* Is the file a directory?
*/
return S_ISDIR(statbuf.st_mode) != 0;
}
/*.......................................................................
* Return non-zero if the specified path name refers to a regular file.
*
* Input:
* pathname const char * The path to test.
* Output:
* return int 0 - Not a regular file.
* 1 - pathname[] refers to a regular file.
*/
int _pu_path_is_file(const char *pathname)
{
struct stat statbuf; /* The file-statistics return buffer */
/*
* Look up the file attributes.
*/
if(stat(pathname, &statbuf) < 0)
return 0;
/*
* Is the file a regular file?
*/
return S_ISREG(statbuf.st_mode) != 0;
}
/*.......................................................................
* Return non-zero if the specified path name refers to an executable.
*
* Input:
* pathname const char * The path to test.
* Output:
* return int 0 - Not an executable file.
* 1 - pathname[] refers to an executable file.
*/
int _pu_path_is_exe(const char *pathname)
{
struct stat statbuf; /* The file-statistics return buffer */
/*
* Look up the file attributes.
*/
if(stat(pathname, &statbuf) < 0)
return 0;
/*
* Is the file a regular file which is executable by the current user.
*/
return S_ISREG(statbuf.st_mode) != 0 &&
(statbuf.st_mode & (S_IXOTH | S_IXGRP | S_IXUSR)) &&
access(pathname, X_OK) == 0;
}
/*.......................................................................
* 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.
*/
char *_pu_start_of_path(const char *string, int back_from)
{
int i, j;
/*
* Check the arguments.
*/
if(!string || back_from < 0) {
errno = EINVAL;
return NULL;
};
/*
* 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;
}
/*.......................................................................
* Find the length of a potential filename starting from a given
* point. This looks forwards from the specified index in a given string,
* stopping at the first unescaped space or the end of the line.
*
* Input:
* string const char * The string to search backwards in.
* start_from int The index of the first character of the pathname
* in string[].
* Output:
* return char * The pointer to the character that follows
* the potential pathname, or NULL on error.
*/
char *_pu_end_of_path(const char *string, int start_from)
{
int c; /* The character being examined */
int escaped = 0; /* True when the next character is escaped */
int i;
/*
* Check the arguments.
*/
if(!string || start_from < 0) {
errno = EINVAL;
return NULL;
};
/*
* Search forwards from the specified index.
*/
for(i=start_from; (c=string[i]) != '\0'; i++) {
if(escaped) {
escaped = 0;
} else if(isspace(c)) {
break;
} else if(c == '\\') {
escaped = 1;
};
};
return (char *)string + i;
}
/*.......................................................................
* Return non-zero if the specified path name refers to an existing file.
*
* Input:
* pathname const char * The path to test.
* Output:
* return int 0 - The file doesn't exist.
* 1 - The file does exist.
*/
int _pu_file_exists(const char *pathname)
{
struct stat statbuf;
return stat(pathname, &statbuf) == 0;
}
#endif /* ifndef WITHOUT_FILE_SYSTEM */
yuma123_2.14/libtecla/stringrp.c 0000664 0001750 0001750 00000021130 14770023131 016713 0 ustar vladimir vladimir /*
* Copyright (c) 2000, 2001, 2002, 2003, 2004 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 "freelist.h"
#include "stringrp.h"
/*
* StringSegment objects store lots of small strings in larger
* character arrays. Since the total length of all of the strings can't
* be known in advance, an extensible list of large character arrays,
* called string-segments are used.
*/
typedef struct StringSegment StringSegment;
struct StringSegment {
StringSegment *next; /* A pointer to the next segment in the list */
char *block; /* An array of characters to be shared between strings */
int unused; /* The amount of unused space at the end of block[] */
};
/*
* StringGroup is typedef'd in stringrp.h.
*/
struct StringGroup {
FreeList *node_mem; /* The StringSegment free-list */
int block_size; /* The dimension of each character array block */
StringSegment *head; /* The list of character arrays */
};
/*
* Specify how many StringSegment's to allocate at a time.
*/
#define STR_SEG_BLK 20
/*.......................................................................
* Create a new StringGroup object.
*
* Input:
* segment_size int The length of each of the large character
* arrays in which multiple strings will be
* stored. This sets the length of longest
* string that can be stored, and for efficiency
* should be at least 10 times as large as
* the average string that will be stored.
* Output:
* return StringGroup * The new object, or NULL on error.
*/
StringGroup *_new_StringGroup(int segment_size)
{
StringGroup *sg; /* The object to be returned */
/*
* Check the arguments.
*/
if(segment_size < 1) {
errno = EINVAL;
return NULL;
};
/*
* Allocate the container.
*/
sg = (StringGroup *) malloc(sizeof(StringGroup));
if(!sg) {
errno = ENOMEM;
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_StringGroup().
*/
sg->node_mem = NULL;
sg->head = NULL;
sg->block_size = segment_size;
/*
* Allocate the free list that is used to allocate list nodes.
*/
sg->node_mem = _new_FreeList(sizeof(StringSegment), STR_SEG_BLK);
if(!sg->node_mem)
return _del_StringGroup(sg);
return sg;
}
/*.......................................................................
* Delete a StringGroup object.
*
* Input:
* sg StringGroup * The object to be deleted.
* Output:
* return StringGroup * The deleted object (always NULL).
*/
StringGroup *_del_StringGroup(StringGroup *sg)
{
if(sg) {
StringSegment *node;
/*
* Delete the character arrays.
*/
for(node=sg->head; node; node=node->next) {
if(node->block)
free(node->block);
node->block = NULL;
};
/*
* Delete the list nodes that contained the string segments.
*/
sg->node_mem = _del_FreeList(sg->node_mem, 1);
sg->head = NULL; /* Already deleted by deleting sg->node_mem */
/*
* Delete the container.
*/
free(sg);
};
return NULL;
}
/*.......................................................................
* Make a copy of a string in the specified string group, and return
* a pointer to the copy.
*
* Input:
* sg StringGroup * The group to store the string in.
* string const char * The string to be recorded.
* remove_escapes int If true, omit backslashes which escape
* other characters when making the copy.
* Output:
* return char * The pointer to the copy of the string,
* or NULL if there was insufficient memory.
*/
char *_sg_store_string(StringGroup *sg, const char *string, int remove_escapes)
{
char *copy; /* The recorded copy of string[] */
/*
* Check the arguments.
*/
if(!sg || !string)
return NULL;
/*
* Get memory for the string.
*/
copy = _sg_alloc_string(sg, strlen(string));
if(copy) {
/*
* If needed, remove backslash escapes while copying the input string
* into the cache string.
*/
if(remove_escapes) {
int escaped = 0; /* True if the next character should be */
/* escaped. */
const char *src = string; /* A pointer into the input string */
char *dst = copy; /* A pointer into the cached copy of the */
/* string. */
while(*src) {
if(!escaped && *src == '\\') {
escaped = 1;
src++;
} else {
escaped = 0;
*dst++ = *src++;
};
};
*dst = '\0';
/*
* If escapes have already been removed, copy the input string directly
* into the cache.
*/
} else {
strcpy(copy, string);
};
};
/*
* Return a pointer to the copy of the string (or NULL if the allocation
* failed).
*/
return copy;
}
/*.......................................................................
* Allocate memory for a string of a given length.
*
* Input:
* sg StringGroup * The group to store the string in.
* length int The required length of the string.
* Output:
* return char * The pointer to the copy of the string,
* or NULL if there was insufficient memory.
*/
char *_sg_alloc_string(StringGroup *sg, int length)
{
StringSegment *node; /* A node of the list of string segments */
char *copy; /* The allocated string */
/*
* If the string is longer than block_size, then we can't record it.
*/
if(length > sg->block_size || length < 0)
return NULL;
/*
* See if there is room to record the string in one of the existing
* string segments. Do this by advancing the node pointer until we find
* a node with length+1 bytes unused, or we get to the end of the list.
*/
for(node=sg->head; node && node->unused <= length; node=node->next)
;
/*
* If there wasn't room, allocate a new string segment.
*/
if(!node) {
node = (StringSegment *) _new_FreeListNode(sg->node_mem);
if(!node)
return NULL;
/*
* Initialize the segment.
*/
node->next = NULL;
node->block = NULL;
node->unused = sg->block_size;
/*
* Attempt to allocate the string segment character array.
*/
node->block = (char *) malloc(sg->block_size);
if(!node->block)
return NULL;
/*
* Prepend the node to the list.
*/
node->next = sg->head;
sg->head = node;
};
/*
* Get memory for the string.
*/
copy = node->block + sg->block_size - node->unused;
node->unused -= length + 1;
/*
* Return a pointer to the string memory.
*/
return copy;
}
/*.......................................................................
* Delete all of the strings that are currently stored by a specified
* StringGroup object.
*
* Input:
* sg StringGroup * The group of strings to clear.
*/
void _clr_StringGroup(StringGroup *sg)
{
StringSegment *node; /* A node in the list of string segments */
/*
* Mark all of the string segments as unoccupied.
*/
for(node=sg->head; node; node=node->next)
node->unused = sg->block_size;
return;
}
yuma123_2.14/libtecla/stringrp.h 0000664 0001750 0001750 00000006736 14770023131 016737 0 ustar vladimir vladimir #ifndef stringrp_h
#define stringrp_h
/*
* Copyright (c) 2000, 2001, 2002, 2003, 2004 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.
*/
/*
* StringGroup objects provide memory for modules that need to
* allocate lots of small strings without needing to free any of them
* individually, but rather is happy to free them all at the same
* time. Taking advantage of these properties, StringGroup objects
* avoid the heap fragmentation that tends to occur when lots of small
* strings are allocated directly from the heap and later free'd. They
* do this by allocating a list of large character arrays in each of
* which multiple strings are stored. Thus instead of allocating lots
* of small strings, a few large character arrays are allocated. When
* the strings are free'd on mass, this list of character arrays is
* maintained, ready for subsequent use in recording another set of
* strings.
*/
typedef struct StringGroup StringGroup;
/*
* The following constructor allocates a string-allocation object.
* The segment_size argument specifies how long each string segment
* array should be. This should be at least 10 times the length of
* the average string to be recorded in the string group, and
* sets the length of the longest string that can be stored.
*/
StringGroup *_new_StringGroup(int segment_size);
/*
* Delete all of the strings that are currently stored by a specified
* StringGroup object.
*/
void _clr_StringGroup(StringGroup *sg);
/*
* Make a copy of the specified string, returning a pointer to
* the copy, or NULL if there was insufficient memory. If the
* remove_escapes argument is non-zero, backslashes that escape
* other characters will be removed.
*/
char *_sg_store_string(StringGroup *sg, const char *string, int remove_escapes);
/*
* Allocate memory for a string of a given length.
*/
char *_sg_alloc_string(StringGroup *sg, int length);
/*
* Delete a StringGroup object (and all of the strings that it
* contains).
*/
StringGroup *_del_StringGroup(StringGroup *sg);
#endif
yuma123_2.14/libtecla/demo2.c 0000664 0001750 0001750 00000033060 14770023131 016056 0 ustar vladimir vladimir /*
* Copyright (c) 2000, 2001, 2002, 2003, 2004 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"
/*
* If the library is being built with file-system access excluded, this
* demo program won't have anything to demonstrate.
*/
#ifdef WITHOUT_FILE_SYSTEM
int main(int argc, char *argv[])
{
fprintf(stderr, "\n"
" This program normally demonstrates tecla's path-lookup\n"
" facility. However libtecla has been installed with\n"
" file-system facilities explicitly excluded, so there is\n"
" nothing to demonstrate.\n\n");
return 1;
}
#else
/*
* 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);
/* The function which displays the introductory text of the demo */
static void show_demo_introduction(GetLine *gl);
/*.......................................................................
* 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("\n Welcome to the path-search demo of libtecla version %d.%d.%d\n",
major, minor, micro);
/*
* Display some introductory text, left-justifying it within the current
* width of the terminal and enclosing it in a box of asterixes.
*/
show_demo_introduction(res->gl);
/*
* 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;
}
/*.......................................................................
* Display introductory text to the user, formatted according to the
* current terminal width and enclosed in a box of asterixes.
*
* Input:
* gl GetLine * The resource object of gl_get_line().
*/
static void show_demo_introduction(GetLine *gl)
{
int start; /* The column in which gl_display_text() left the cursor */
int i;
/*
* Break the indtroductory text into an array of strings, so as to
* avoid overflowing any compiler string limits.
*/
const char *doc[] = {
"This program demonstrates the use of the pca_lookup_file() function ",
"for finding executables in the UNIX PATH. It also demonstrates ",
"tab completion of the names of executables found in the path. For ",
"example, if you type:\n\n ta\n\nthen hit the tab key, you will be ",
"presented with a list of executables such as tar and tail whose names ",
"start with the string \"ta\". If you decide to add an \"r\" to select ",
"the tar command, then you type return, the full pathname of the tar ",
"program will be printed.\n\nThe file demo2.c contains the code ",
"of this program, and is fully commented to enable its use as ",
"a working example of how to use the facilities documented in the ",
"pca_lookup_file man page.\n"};
/*
* Form the top line of the documentation box by filling the area of
* the line between a " *" prefix and a "* " suffix with asterixes.
*/
printf("\n");
gl_display_text(gl, 0, " *", "* ", '*', 80, 0, "\n");
/*
* Justify the documentation text within margins of asterixes.
*/
for(start=0,i=0; i= 0; i++)
start = gl_display_text(gl, 0, " * ", " * ", ' ', 80, start,doc[i]);
/*
* Draw the bottom line of the documentation box.
*/
gl_display_text(gl, 0, " *", "* ", '*', 80, 0, "\n");
printf("\n");
}
#endif /* ifndef WITHOUT_FILE_SYSTEM */
yuma123_2.14/libtecla/configure.in 0000664 0001750 0001750 00000053632 14770023131 017224 0 ustar vladimir vladimir dnl This is the input file which autoconf uses to construct a
dnl "configure" script for the tecla library. It is a bourne shell
dnl script which autoconf pre-processes with the m4 preprocessor to
dnl expand autoconf-defined m4 macros such as AC_INIT(). The
dnl following line just initializes autoconf. Autoconf interprets the
dnl single argument as the name of an arbitrary file, which it uses to
dnl ensure that it is being run correctly from the directory which
dnl contains the libtecla source code.
AC_INIT(getline.c)
dnl Here we set the major version number of the tecla library.
dnl Incrementing this number implies that a change has been made to
dnl the library's public interface, which makes it binary incompatible
dnl with programs that were linked with previous shared versions of
dnl the tecla library. Incompatible changes of this type should be
dnl avoided at all costs, so it is hoped that the major version number
dnl won't ever have to change. The major version number must be a
dnl small integer number, preferably a single numeric digit.
AC_SUBST(MAJOR_VER)
MAJOR_VER="1"
dnl Set the minor version number of the tecla library. This number
dnl should be incremented by one whenever additional functionality,
dnl such as new functions or modules, are added to the library. The
dnl idea is that a program that was linked with a shared library of
dnl the same major version number, but a lower minor version number,
dnl will continue to function when the run-time loader links it
dnl against the updated version. The minor version number must be a
dnl small integer number, which should be reset to 0 whenever the
dnl major version number is incremented.
AC_SUBST(MINOR_VER)
MINOR_VER="6"
dnl Set the micro version number of the tecla library. This is
dnl incremented whenever modifications to the library are made which
dnl make no changes to the public interface, but which fix bugs and/or
dnl improve the behind-the-scenes implementation. The micro version
dnl number should be reset to 0 whenever the minor version number is
dnl incremented. The micro version number must be a small integer
dnl number.
AC_SUBST(MICRO_VER)
MICRO_VER="1"
dnl The AC_PROG_CC line looks for a C compiler, and if gcc is chosen,
dnl sets the $GCC shell variable to "yes". Make sure that CFLAGS is
dnl set to something first, to prevent AC_PROG_CC from substituting -g
dnl for the optimization level.
CFLAGS="$CFLAGS"
AC_PROG_CC
dnl Apparently not all implementations of the 'make' command define
dnl the MAKE variable. The following directive creates a variable
dnl called SET_MAKE which when expanded in a makefile is either empty
dnl if the local 'make' command was found to define the MAKE variable,
dnl or contains an assignment which will give the MAKE variable the
dnl value 'make'.
AC_PROG_MAKE_SET
dnl The following directive causes autoconf to see if symbolic links
dnl are supported on the current filesystem. If so, it sets the
dnl variable LN_S to "ln -s". Otherwise it sets LN_S to just "ln".
dnl This allows us to create symbolic links where possible, but falls
dnl back to creating hard links where symbolic links aren't available.
AC_PROG_LN_S
dnl The following macro searches for the best implementation of awk
dnl on the host system, and records it in the AWK shell variable.
AC_PROG_AWK
dnl If ranlib is needed on the target system, the RANLIB make variable
dnl is set to ranlib. Otherwise it is set to :, which is the do-nothing
dnl command of the bourne shell.
dnl Note that we do not use AC_PROG_RANLIB because (at least in
dnl autoconf 2.53) this does not check for cross-compilation.
AC_CHECK_TOOL(RANLIB, ranlib)
dnl Set LD as appropriate, especially when cross-compiling
AC_CHECK_TOOL(LD, ld)
dnl The following directive tells autoconf to figure out the target
dnl system type and assign a canonical name for this to the $target
dnl shell variable. This is used below in the target-specific case
dnl statement.
AC_CANONICAL_SYSTEM
dnl In early versions of Solaris, some libraries are in /usr/ccs/lib,
dnl where gcc doesn't look. The tests below for the curses library
dnl would thus fail without this directory being added to the search
dnl path. We thus add it here before the tests. Note that in the
dnl following, since [ and ] are m4 quotes, and m4 will remove the
dnl outermost quotes when it processes this file, we have to double
dnl them up here to get [0-6] to appear in the output configure
dnl script.
case $target_os in
solaris2.[[0-6]]|solaris2.[[0-6]].*)
LIBS="$LIBS -L/usr/ccs/lib"
;;
esac
dnl Recent versions of gcc place /usr/local/include at the head of the
dnl system include-file search path. This causes problems when include
dnl files that have the same name as standard system include files are
dnl placed in this directory by third-party packages. To avoid this,
dnl move /usr/local/include to the end of the search path.
if test "$GCC"_ = "yes"_; then
touch foo.c
fix=`$CC -E -Wp,-v foo.c 2>&1 | $AWK '
/^#include <...> search starts here:/ {in_list=1;ndir=0}
/ *\// && in_list {path[[ndir++]] = $1}
/^End of search list/ {in_list=0}
END {
if(path[[0]] ~ /\/usr\/local\/include/) {
for(dir=1; dir])], [AC_CHECK_HEADERS(ncurses/curses.h, [AC_CHECK_HEADERS(ncurses/term.h,[],[],[#include ])])])
dnl The following variable lists the targets that will be created if
dnl the user runs make without any arguments. Initially we assume
dnl that we can create both the normal and the reentrant versions
dnl of the library.
AC_SUBST(TARGETS)
TARGETS="normal reentrant"
dnl Check for reentrant functions by attempting to compile and link a
dnl temporary program which calls them, being sure to include the
dnl appropriate headers and define _POSIX_C_SOURCE, just in case any
dnl of the functions are defined as macros. In the following,
dnl AC_CACHE_CHECK outputs the message "checking for reentrant
dnl functions". If this check has been done before, it assigns the
dnl cached yes/no value to tecla_cv_reentrant. Otherwise it uses
dnl AC_TRY_LINK() to attempt to compile and link the specified dummy
dnl program, and sets tecla_cv_reentrant to yes or no, depending on
dnl whether this succeeds. Finally it caches the value of
dnl tecla_cv_reentrant in the file config.cache, and writes "yes" or
dnl "no" to the terminal.
AC_CACHE_CHECK(for reentrant functions, tecla_cv_reentrant, [
KEPT_CFLAGS="$CFLAGS"
CFLAGS="$CFLAGS -D_POSIX_C_SOURCE=199506L"
AC_TRY_LINK([
#include
#include
#include
#include
#include
], [
(void) readdir_r(NULL, NULL, NULL);
(void) getpwuid_r(geteuid(), NULL, NULL, 0, NULL);
(void) getpwnam_r(NULL, NULL, NULL, 0, NULL);
], tecla_cv_reentrant=yes, tecla_cv_reentrant=no)
CFLAGS="$KEPT_CFLAGS"
])
dnl If the necessary reentrant functions weren't found to be
dnl available, default to only compiling the non-reentrant version of
dnl the library.
if test $tecla_cv_reentrant = no; then
TARGETS="normal"
fi
dnl If sys/select.h exists, arrange for the HAVE_SYS_SELECT_H C-macro to
dnl be defined when compiling the library.
AC_CHECK_HEADER(sys/select.h, AC_DEFINE(HAVE_SYS_SELECT_H))
dnl Check for the select system call with the normal arguments,
dnl by attempting to compile and link a temporary program which
dnl calls it, being sure to include the appropriate headers.
dnl In the following, AC_CACHE_CHECK outputs the message
dnl "checking for select system call". If this check has been done
dnl before, it assigns the cached yes/no value to tecla_cv_select.
dnl Otherwise it uses AC_TRY_LINK() to attempt to compile and link
dnl the specified dummy program, and sets tecla_cv_select to yes
dnl or no, depending on whether this succeeds. Finally it caches
dnl the value of tecla_cv_select in the file config.cache, and
dnl writes "yes" or "no" to the terminal.
AC_CACHE_CHECK(for select system call, tecla_cv_select, [
AC_TRY_LINK([
#include
#include
#include
#ifdef HAVE_SYS_SELECT_H
#include
#endif
], [
fd_set fds;
int nready;
FD_ZERO(&fds);
FD_SET(1, &fds);
nready = select(2, &fds, &fds, &fds, NULL);
], tecla_cv_select=yes, tecla_cv_select=no)
])
dnl If the select function was available, arrange for HAVE_SELECT to
dnl be defined by CFLAGS.
if test $tecla_cv_select = yes; then
AC_DEFINE(HAVE_SELECT)
fi
dnl Check if this system supports the system V pseudo terminal interface.
AC_CACHE_CHECK(for SysV pseudo-terminals, tecla_cv_sysv_pty, [
AC_TRY_LINK([
#include
#include
#include
], [
char *name = ptsname(0);
int i1 = grantpt(0);
int i2 = unlockpt(0);
int i3 = ioctl(0, I_PUSH, "ptem");
return 0;
], tecla_cv_sysv_pty=yes, tecla_cv_sysv_pty=no)
])
dnl If the system-V pseudo-terminal interface is available, arrange
dnl for HAVE_SYSV_PTY to be defined by CFLAGS.
if test $tecla_cv_sysv_pty = yes; then
AC_DEFINE(HAVE_SYSV_PTY)
fi
dnl The following variable contains the extension to append to
dnl "libtecla" and "libtecla_r" when creating shared libraries on the
dnl target platform. This is system dependent and is ignored if
dnl LINK_SHARED remains an empty string. On most platforms that
dnl support shared libaries, this will be .so.$MAJOR_VER, where
dnl MAJOR_VER is the major version number described above, which on
dnl some systems, tells the run-time loader if the program being
dnl loaded is binary compatible with a given version of the library
dnl (see the discussion of MAJOR_VER near the top of this file).
dnl The following empty default can be overriden on a system by system
dnl basis later in this file.
AC_SUBST(SHARED_EXT)
SHARED_EXT=""
dnl When a shared library is installed with the extension $SHARED_EXT,
dnl you can optionally produce other copies of this library with
dnl different extensions. This is done using symbolic or hard links,
dnl depending on what is available on the current filesystem, and the
dnl extensions to use for these links are listed in the following
dnl variable, separated by spaces. The following empty default can be
dnl overriden on a system by system basis later in this file.
AC_SUBST(SHARED_ALT)
SHARED_ALT=""
dnl The following variable lists extra compilation flags needed to
dnl create object files that can be included in shared libraries.
dnl Normally one would include a flag to tell the C compiler to
dnl compile position-independent code. This option commonly includes
dnl the acronym 'pic'.
AC_SUBST(SHARED_CFLAGS)
SHARED_CFLAGS=""
dnl On systems that support shared libraries, the following variable
dnl provides the command needed to make a shared library. In this
dnl variable, $$@ will be replaced with the name of the shared
dnl library, $$(LIB_OBJECTS) will be replaced with a space separated
dnl list of the object files that are to be included in the library,
dnl and libtecla$$(SUFFIX) will be the name of the library being
dnl built, minus the system-specific extension (eg. libtecla or
dnl libtecla_r). If LINK_SHARED is left as an empty string, shared
dnl library creation will not attempted. If your system supports
dnl shared library creation, you should override the default value of
dnl this variable in the target-specific case statement later in this
dnl file.
AC_SUBST(LINK_SHARED)
LINK_SHARED=""
dnl When compiling the reentrant version of the library, the following
dnl compiler flags are presented to the compiler, in addition to those
dnl that are used when compiling the non-reentrant version of the
dnl library. The PREFER_REENTRANT macro is an internal libtecla macro
dnl whose presence reports when the reentrant version of the library
dnl is being compiled. This allows the code to determine when to
dnl disable features that can't portably be implemented reentrantly,
dnl such as username completion. The existence of the _POSIX_C_SOURCE
dnl macro can't be reliably used for this purpose, since some systems
dnl define it by default for all code.
AC_SUBST(DEFS_R)
DEFS_R="-D_POSIX_C_SOURCE=199506L -DPREFER_REENTRANT"
dnl For man pages relating to library features, the following two
dnl variables determine in which sub-directory of the top-level man
dnl directory the man pages should go, and what file-name extensions
dnl these files should have. On systems where the following defaults
dnl are not valid, the default values should be overriden in the
dnl target-specific case statement later in this file.
AC_SUBST(LIBR_MANDIR)
AC_SUBST(LIBR_MANEXT)
LIBR_MANDIR="man3"
LIBR_MANEXT="3"
dnl For man pages relating to library functions, the following two
dnl variables serve the same purpose as the previously described
dnl LIBR_MANDIR and LIBR_MANEXT variables.
AC_SUBST(FUNC_MANDIR)
AC_SUBST(FUNC_MANEXT)
FUNC_MANDIR="man3"
FUNC_MANEXT="3"
dnl For man pages relating to programs, the following two variables
dnl serve the same purpose as the previously described LIBR_MANDIR
dnl and LIBR_MANEXT variables.
AC_SUBST(PROG_MANDIR)
AC_SUBST(PROG_MANEXT)
PROG_MANDIR="man1"
PROG_MANEXT="1"
dnl For man pages on miscellaneous topics, the following two variables
dnl serve the same purpose as the previously described LIBR_MANDIR
dnl and LIBR_MANEXT variables.
AC_SUBST(MISC_MANDIR)
AC_SUBST(MISC_MANEXT)
MISC_MANDIR="man7"
MISC_MANEXT="7"
dnl For man pages relating to configuration files, the following two
dnl variables serve the same purpose as the previously described
dnl LIBR_MANDIR and LIBR_MANEXT variables.
AC_SUBST(FILE_MANDIR)
AC_SUBST(FILE_MANEXT)
FILE_MANDIR="man5"
FILE_MANEXT="5"
dnl If the application doesn't want the user to have access to the
dnl filesystem, it can remove all action functions that list, read or
dnl write files, by including the configuration argument
dnl --without-file-actions.
AC_ARG_WITH(file-actions, AC_HELP_STRING([--with-file-actions], [Should users of gl_get_line() have access to the filesystem (default=yes)]),
AC_DEFINE(HIDE_FILE_SYSTEM), )
dnl If the target system either has no file-system, or file-system access
dnl isn't needed, libtecla can be made smaller by excluding all file and
dnl directory access code. This is done by adding the configuration
dnl argument --without-file-system.
AC_ARG_WITH(file-system, AC_HELP_STRING([--with-file-system], [Does the target have a filesystem (default=yes)]),
AC_DEFINE(WITHOUT_FILE_SYSTEM), )
dnl The following bourne shell case statement is where system
dnl dependencies can be added. In particular, if your system supports
dnl shared library creation, the following switch is the place to
dnl configure it. To do so you will first need to find out what target
dnl type was just assigned by the AC_CANONICAL_SYSTEM macro executed
dnl previously. The target type of your current system can be
dnl determined by cd'ing to the top level directory of the tecla
dnl distribution, and typing the command "sh config.guess". This will
dnl report what autoconf thinks the system type is. Note that this
dnl will be very specific, so if you know that the configuration
dnl parameters that you are about to provide apply to different
dnl versions of the current system type, you can express this in the
dnl case statement by using a wild-card in place of the version
dnl number, or by using an | alternation to list one or more version
dnl names. Beware that autoconf uses [] as quote characters, so if you
dnl want to use a regexp character range like [a-z], you should write
dnl this as [[a-z]].
case $target in
*solaris*)
AC_DEFINE(__EXTENSIONS__)
SHARED_EXT=".so.${MAJOR_VER}"
SHARED_ALT=".so"
LINK_SHARED="$LD"' -G -M $$(srcdir)/libtecla.map -o $$@ -h $$(@F) -z defs -i $$(LIB_OBJECTS) $$(LIBS) -lc'
SHARED_CFLAGS="-Kpic"
case $CC in
*/cc|cc) SHARED_CFLAGS="$SHARED_CFLAGS -xstrconst" ;;
esac
case $target_cpu in
sparc) SHARED_CFLAGS="$SHARED_CFLAGS -xregs=no%appl"
esac
case $target_os in
solaris2.[[89]]*|solaris2.1[[0-9]]*)
LIBR_MANEXT=3lib
FUNC_MANEXT=3tecla
LIBR_MANDIR=man$LIBR_MANEXT
FUNC_MANDIR=man$FUNC_MANEXT
esac
MISC_MANDIR="man5"
MISC_MANEXT="5"
FILE_MANDIR="man4"
FILE_MANEXT="4"
;;
*linux*)
SHARED_EXT=".so.${MAJOR_VER}.${MINOR_VER}.${MICRO_VER}"
SHARED_ALT=".so .so.${MAJOR_VER}"
dnl See if the installed version of Gnu ld accepts version scripts.
AC_CACHE_CHECK([for --version-script in GNU ld], tecla_cv_gnu_ld_script, [
if (echo 'void dummy(void) {return;}' > dummy.c; $CC -c -fpic dummy.c; \
$LD -o dummy.so dummy.o -shared --version-script=$srcdir/libtecla.map) 1>&2 2>/dev/null; then
tecla_cv_gnu_ld_script=yes
else
tecla_cv_gnu_ld_script=no
fi
rm -f dummy.c dummy.o dummy.so
])
if test $tecla_cv_gnu_ld_script = yes; then
VERSION_OPT='--version-script=$$(srcdir)/libtecla.map'
else
VERSION_OPT=''
fi
LINK_SHARED="$LD"' -o $$@ -soname libtecla$$(SUFFIX).so.'${MAJOR_VER}' -shared '$VERSION_OPT' $$(LIB_OBJECTS) $$(LIBS) -lc'
SHARED_CFLAGS="-fpic"
dnl Reenable the inclusion of symbols which get undefined when POSIX_C_SOURCE
dnl is specified.
CFLAGS="-D_SVID_SOURCE -D_BSD_SOURCE $CFLAGS"
;;
*hpux*)
SHARED_EXT=".${MAJOR_VER}"
SHARED_ALT=".sl"
LINK_SHARED="$LD"' -b +h $$(@F) +k +vshlibunsats -o $$@ -c libtecla.map.opt $$(LIB_OBJECTS) $$(LIBS) -lc'
SHARED_CFLAGS="+z"
MISC_MANEXT=5
FILE_MANEXT=4
MISC_MANDIR=man$MISC_MANEXT
FILE_MANDIR=man$FILE_MANEXT
;;
*darwin*)
SHARED_EXT=".${MAJOR_VER}.${MINOR_VER}.${MICRO_VER}.dylib"
SHARED_ALT=".dylib .${MAJOR_VER}.dylib"
LINK_SHARED='$(CC) -o $$@ -dynamiclib -flat_namespace -undefined suppress -compatibility_version '${MAJOR_VER}.${MINOR_VER}' -current_version '${MAJOR_VER}.${MINOR_VER}.${MICRO_VER}' -install_name '${libdir}'/$$@ $$(LIB_OBJECTS)'
SHARED_CFLAGS=""
;;
*dec-osf*)
AC_DEFINE(_OSF_SOURCE)
;;
*freebsd*)
SHARED_EXT=".so.${MAJOR_VER}"
SHARED_ALT=".so"
VERSION_OPT='--version-script=$$(srcdir)/libtecla.map'
LINK_SHARED='ld -o $$@ -soname libtecla$$(SUFFIX).so.'${MAJOR_VER}' -shared '$VERSION_OPT' $$(LIB_OBJECTS) $$(LIBS) -lc'
SHARED_CFLAGS="-fpic"
;;
mips-sgi-irix*)
DEFS_R="$DEFS_R -D_XOPEN_SOURCE=500"
if test "$RANLIB"_ = "_"; then
RANLIB=":"
fi
;;
esac
dnl The following statement checks to see if the GNU C compiler has
dnl been chosen instead of the normal compiler of the host operating
dnl system. If it has, and shared library creation has been
dnl configured, it replaces the shared-library-specific C compilation
dnl flags with those supported by gcc. Also append the gcc run-time
dnl library to the shared library link line.
if test "$GCC"_ = "yes"_ && test "$LINK_SHARED"_ != "_" ; then
SHARED_CFLAGS="-fpic"
case $target in
sparc-*-solaris*)
SHARED_CFLAGS="$SHARED_CFLAGS -mno-app-regs"
;;
*darwin*)
SHARED_CFLAGS=""
;;
esac
LINK_SHARED="$LINK_SHARED `gcc -print-libgcc-file-name`"
fi
dnl The following variable will list which types of libraries,
dnl "static", and possibly "shared", are to be created and installed.
AC_SUBST(TARGET_LIBS)
dnl If shared library creation has been configured, add shared
dnl libraries to the list of libraries to be built.
if test "$LINK_SHARED"_ != "_"; then
TARGET_LIBS="static shared"
else
TARGET_LIBS="static"
LINK_SHARED="@:"
fi
dnl Set the shell variable and Makefile variable, MAKE_MAN_PAGES, to
dnl "yes" if man pages are desired. By default they are, but if the
dnl user specifies --with-man-pages=no or --without-man-pages, then
dnl they won't be preprocessed by the configure script or installed
dnl by the Makefile.
AC_SUBST(MAKE_MAN_PAGES)
AC_ARG_WITH(man-pages, AC_HELP_STRING([--with-man-pages], [Are man pages desired (default=yes)]),
MAKE_MAN_PAGES="$withval", MAKE_MAN_PAGES="yes")
dnl Create the list of files to be generated by the configure script.
OUTPUT_FILES="Makefile"
rm -rf man/man*
if test "$MAKE_MAN_PAGES"_ = "yes"_; then
for area in libr func misc prog file; do
for page in man/$area/*.in; do
OUTPUT_FILES="$OUTPUT_FILES `echo $page | sed 's/\.in$//'`"
done
done
fi
dnl The following directive must always be the last line of any
dnl autoconf script. It causes autoconf to create the configure
dnl script, which for each argument of AC_OUTPUT, will look for a
dnl filename formed by appending ".in" to the argument, preprocess
dnl that file, replacing @VAR@ directives with the corresponding value
dnl of the specified shell variable VAR, as set above in this file,
dnl and write the resulting output to the filename given in the
dnl argument. Note that only shell variables that were exported above
dnl with the AC_SUBST() directive will be substituted in @VAR@
dnl directives (some macros like AC_PROG_CC also call AC_SUBST for you
dnl for the variables that they output).
AC_OUTPUT($OUTPUT_FILES)
yuma123_2.14/libtecla/INSTALL 0000664 0001750 0001750 00000023534 14770023131 015742 0 ustar vladimir vladimir To compile and optionally install the library, it is first necessary
to create a makefile for your system, by typing:
./configure
The Makefile that this generates is designed to install the files of
the library in subdirectories of /usr/local/. If you would prefer to
install them under a different directory, you can type:
./configure --prefix /wherever
Where you would replace /wherever with your chosen directory. Other
command-line options are available, and can be listed by typing:
./configure --help
Having run the configure script, you are then ready to make the
library. To do this, just type:
make
What 'make' does depends on whether the configure script knows about
your system. If the configure script doesn't know anything specific
about your system, it will arrange for 'make' to produce the static
tecla library, called libtecla.a, and if possible, the reentrant
version of this called libtecla_r.a. If it does know about your
system, it will also create shared libraries if possible. If you are
on a system that isn't known, and you would like shared libraries to
be compiled, please read the file called PORTING to see how this can
be achieved.
To install the library, its include file and it manual pages, type:
make install
Note that this will also compile the library if you haven't already
done so.
Having compiled the library, if you wish, you can test it by running
the demo programs. After building the library, you should find two
programs, called demo and demo2, in the current directory.
The first of the demos programs reads input lines from the user, and
writes what was typed back to the screen. While typing a line of
input, you can experiment with line editing, tab completion, history
recall etc.. For details about these line editing features, see the
man page gl_get_line(3). If you haven't installed this yet, you can
see it anyway by typing:
nroff -man man3/gl_get_line.3 | more
The second demo program, called demo2, demonstrates command-completion
with the UNIX PATH. If you type in a partial command name, and press
TAB, the command name will be completed if possible, and possible
completions will be listed if it is ambiguous. When you then enter the
line, the demo program then prints out the full pathname of the
command that you typed. If you type anything after the command name,
filename completion with the tab key reverts to its default behavior
of completing filenames in the current directory.
COMPILING IN A DIFFERENT DIRECTORY
----------------------------------
If you unpack the distribution in a directory which is visible from
multiple hosts which have different architectures, you have the option
of compiling the library for the different architectures in different
directories. You might for example create a sub-directory for each
architecture, under the top level directory of the distribution. You
would then log in to a host of one of these architectures, cd to the
sub-directory that you created for it, and type:
../configure
The configure script then creates a makefile in the current directory
which is designed to build the library, object files, demos etc for
the architecture of the current host, in the current directory, using
the original source code in ../. You then repeat this procedure on
hosts of other architectures.
The compilation directories don't have to be sub-directories of the
top level directory of the distribution. That was just described as an
example. They can be anywhere you like.
Every rule in the makefiles that are generated by the configure
script, cites the paths of the target and source files explicitly, so
this procedure should work on any system, without the need for vpath
makefile support.
EMBEDDING IN OTHER PACKAGE DISTRIBUTIONS
----------------------------------------
If you distribute the library with another package, which has its own
heirarchy and configuration procedures, the following installation
options may be of interest to you. At first glance, the use of a GNU
configure script by the tecla library, may appear to reduce your
options for controlling what gets made, and where it gets installed,
but this isn't the case, because many of the parameters configured by
the configure script are assigned to makefile variables which can be
overriden when you run make.
Configure script options:
If the users of your package won't benefit from having access to the
tecla man pages, you can shorten the length of time taken to run the
configure script by telling this script not to preprocess the man
pages. This is done as follows.
./configure --without-man-pages
Note that this option also causes the makefile man-page installation
targets to do nothing.
Similarly, if you don't want your users to have access to the
filesystem while they are editing input lines using gl_get_line(),
then use the following configuration argument.
./configure --without-file-actions
This will completely remove the "expand-filename", "read-from-file",
"read-init-files", and "list-glob" action functions. It will also
arrange that the default behavior of actions such as "complete-word"
and "list-or-eof" be changed to show no completions, instead of the
normal default of showing filename completions.
If you are using a system that doesn't have a file-system, such as an
embedded system, then libtecla can be built with all code that
accesses the filesystem removed. This will make the library a bit
smaller, and potentially avoid running into problems of missing system
functions related to file-system access. This is done with the
following configuration option.
./configure --without-file-system
Beware that this not only prevents the user from accessing the
filesystem while editing an input line in gl_get_line(), but also
removes all public file-related functions, such as the pathname
expansion module. When using gl_get_line() in this configuration,
the functions that load and save history from and to files, are
stubs that report an error if called upon to read files. The
gl_configure_getline() function can still be called upon to
configure gl_get_line() via its app_string argument, but passing it
a filename in either the app_file or user_file arguments will result
in an error being reported.
Now lets say that you have your own configuration script in the parent
directory of the libtecla top-level directory, and that you don't want
tecla's man pages to be generated. In your configuration script, you
would first need to have a line similar to the following:
(cd libtecla; ./configure --without-man-pages)
Now, from your makefile or whatever script you use to build your
application, you would need to make the tecla library. Assuming that
your makefile or build script is in the parent directory of the
libtecla distribution, then the following line tells make to just
build the non-reentrant, static version of the tecla library, and then
to install it and the tecla include file in sub-directories called lib
and include in your current directory.
(cd libtecla; make LIBDIR=../lib INCDIR=../include TARGETS=normal TARGET_LIBS="static" install_lib install_inc)
In this statement the LIBDIR=../lib component means that on installing
the library, the make command should place the library in the
directory libtecla/../lib. Similarly INCDIR tells make where to place
the include files. The install_lib and install_inc targets tell make
to install the libraries and the include file. Because the install_man
and install_bin targets have been omitted in this example, the man
pages and programs aren't installed. If you were to include these
additional targets then you could use the MANDIR and BINDIR variables,
respectively to control where they were installed.
The TARGETS variable is used to specify which of the normal and
reentrant versions of the library are compiled. This can contain one
or both of the words "normal" and "reentrant". If you don't specify
this when you invoke make, the default value generated by the
configure script will be used. Depending on whether reentrant POSIX
functions are available for compilation of the reentrant version, this
will be either "normal" or "normal reentrant".
The TARGET_LIBS variable is used to specify which of the static and
shared libraries are to be built. This can contain one or both of the
words "static" and "shared". If you don't specify this when you invoke
make, the default value generated by the configure script will be
used. Depending on whether the configure script knows how to create
shared libraries on the target system, this will be either "static" or
"static shared". Beware that shared libraries aren't supported on many
systems, so requiring "shared" will limit which systems you can
compile your package on. Also note that unless your package installs
the tecla library in a directory which all users of your program will
have access to, you should only compile the static version.
Instructions for adding shared-library creation rules for new systems
are included in the PORTING file.
The OPT variable can be used to change the default optimization from
the default of "-O" to something else.
The DEMOS variable controls whether the demo programs are built.
Normally this has the value "demos", which tells the makefile to build
the demo programs. Setting it to an empty string stops the demos from
being built.
The PROGRAMS variable is used to specify which programs are to be
built and subsequently installed. All available programs are built by
default. Currently there is only one such program, selected by
specifying the word "enhance". This program uses tecla-enhanced
pseudo-terminals to layer command line editing on top of third party
programs.
The PROGRAMS_R variable serves the same purpose as the PROGRAMS
variable, except that programs listed here are linked with the
reentrant version of the library, and should be specified with a _r
suffix. Currently this variable is empty by default.
Martin Shepherd (mcs@astro.caltech.edu)
yuma123_2.14/libtecla/expand.h 0000664 0001750 0001750 00000003727 14770023131 016343 0 ustar vladimir vladimir #ifndef expand_h
#define expand_h
/*
* Copyright (c) 2000, 2001, 2002, 2003, 2004 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.
*/
/*
* This header is not for use by external applicatons. It contains
* internal immplementation features of the libtecla library, which
* may change incompatibly between releases.
*/
/*
* Print a list of expansions via a callback function.
*/
int _ef_output_expansions(FileExpansion *result, GlWriteFn *write_fn,
void *data, int term_width);
#endif
yuma123_2.14/libtecla/errmsg.h 0000664 0001750 0001750 00000005624 14770023131 016361 0 ustar vladimir vladimir #ifndef errmsg_h
#define errmsg_h
/*
* Copyright (c) 2000, 2001, 2002, 2003, 2004 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.
*/
/*
* Set the longest expected length of an error message (excluding its
* '\0' terminator. Since any message over a nominal terminal width of
* 80 characters is going to look a mess, it makes no sense to support
* huge lengths. Note that many uses of strings declared with this
* macro assume that it will be at least 81, so don't reduce it below
* this limit.
*/
#define ERR_MSG_LEN 128
/*
* Provide an opaque typedef to the error-message object.
*/
typedef struct ErrMsg ErrMsg;
/*
* The following token is used to terminate the argument lists of calls
* to _err_record_msg().
*/
#define END_ERR_MSG ((const char *)0)
/*
* Allocate a new error-message buffer.
*/
ErrMsg *_new_ErrMsg(void);
/*
* Delete an error message buffer.
*/
ErrMsg *_del_ErrMsg(ErrMsg *err);
/*
* Concatenate a list of string arguments into the specified buffer, buff[],
* which has an allocated size of buffdim characters.
* The last argument must be END_ERR_MSG to terminate the argument list.
*/
void _err_record_msg(ErrMsg *err, ...);
/*
* Replace the current error message with an empty string.
*/
void _err_clear_msg(ErrMsg *err);
/*
* Return a pointer to the error message buffer. This is
* a '\0' terminated character array containing ERR_MSG_LEN+1
* elements.
*/
char *_err_get_msg(ErrMsg *err);
#endif
yuma123_2.14/libtecla/keytab.c 0000664 0001750 0001750 00000073641 14770023131 016340 0 ustar vladimir vladimir /*
* Copyright (c) 2000, 2001, 2002, 2003, 2004 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 "keytab.h"
#include "strngmem.h"
#include "getline.h"
#include "errmsg.h"
#include "hash.h"
/*
* When allocating or reallocating the key-binding table, how
* many entries should be added?
*/
#define KT_TABLE_INC 100
/*
* Define the size of the hash table that is used to associate action
* names with action functions. This should be a prime number.
*/
#define KT_HASH_SIZE 113
/*
* Define a binary-symbol-table object.
*/
struct KeyTab {
ErrMsg *err; /* Information about the last error */
int size; /* The allocated dimension of table[] */
int nkey; /* The current number of members in the table */
KeySym *table; /* The table of lexically sorted key sequences */
HashTable *actions; /* The hash table of actions */
StringMem *smem; /* Memory for allocating strings */
};
static int _kt_extend_table(KeyTab *kt);
static int _kt_parse_keybinding_string(const char *keyseq,
char *binary, int *nc);
static int _kt_compare_strings(const char *s1, int n1, const char *s2, int n2);
static void _kt_assign_action(KeySym *sym, KtBinder binder, KtKeyFn *keyfn,
void *data);
static char _kt_backslash_escape(const char *string, const char **endp);
static int _kt_is_emacs_meta(const char *string);
static int _kt_is_emacs_ctrl(const char *string);
static KtKeyMatch _kt_locate_keybinding(KeyTab *kt, const char *binary_keyseq,
int nc, int *first, int *last);
/*.......................................................................
* Create a new key-binding symbol table.
*
* Output:
* return KeyTab * The new object, or NULL on error.
*/
KeyTab *_new_KeyTab(void)
{
KeyTab *kt; /* The object to be returned */
/*
* Allocate the container.
*/
kt = (KeyTab *) malloc(sizeof(KeyTab));
if(!kt) {
errno = ENOMEM;
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_KeyTab().
*/
kt->err = NULL;
kt->size = KT_TABLE_INC;
kt->nkey = 0;
kt->table = NULL;
kt->actions = NULL;
kt->smem = NULL;
/*
* Allocate a place to record error messages.
*/
kt->err = _new_ErrMsg();
if(!kt->err)
return _del_KeyTab(kt);
/*
* Allocate the table.
*/
kt->table = (KeySym *) malloc(sizeof(kt->table[0]) * kt->size);
if(!kt->table) {
errno = ENOMEM;
return _del_KeyTab(kt);
};
/*
* Allocate a hash table of actions.
*/
kt->actions = _new_HashTable(NULL, KT_HASH_SIZE, IGNORE_CASE, NULL, 0);
if(!kt->actions)
return _del_KeyTab(kt);
/*
* Allocate a string allocation object. This allows allocation of
* small strings without fragmenting the heap.
*/
kt->smem = _new_StringMem(KT_TABLE_INC);
if(!kt->smem)
return _del_KeyTab(kt);
return kt;
}
/*.......................................................................
* Delete a KeyTab object.
*
* Input:
* kt KeyTab * The object to be deleted.
* Output:
* return KeyTab * The deleted object (always NULL).
*/
KeyTab *_del_KeyTab(KeyTab *kt)
{
if(kt) {
if(kt->table)
free(kt->table);
kt->actions = _del_HashTable(kt->actions);
kt->smem = _del_StringMem(kt->smem, 1);
kt->err = _del_ErrMsg(kt->err);
free(kt);
};
return NULL;
}
/*.......................................................................
* Increase the size of the table to accomodate more keys.
*
* Input:
* kt KeyTab * The table to be extended.
* Output:
* return int 0 - OK.
* 1 - Error.
*/
static int _kt_extend_table(KeyTab *kt)
{
/*
* Attempt to increase the size of the table.
*/
KeySym *newtab = (KeySym *) realloc(kt->table, sizeof(kt->table[0]) *
(kt->size + KT_TABLE_INC));
/*
* Failed?
*/
if(!newtab) {
_err_record_msg(kt->err, "Can't extend keybinding table", END_ERR_MSG);
errno = ENOMEM;
return 1;
};
/*
* Install the resized table.
*/
kt->table = newtab;
kt->size += KT_TABLE_INC;
return 0;
}
/*.......................................................................
* Add, update or remove a keybinding to the table.
*
* Input:
* kt KeyTab * The table to add the binding to.
* binder KtBinder The source of the binding.
* keyseq const char * The key-sequence to bind.
* action char * The action to associate with the key sequence, or
* NULL to remove the action associated with the
* key sequence.
* Output:
* return int 0 - OK.
* 1 - Error.
*/
int _kt_set_keybinding(KeyTab *kt, KtBinder binder, const char *keyseq,
const char *action)
{
KtKeyFn *keyfn; /* The action function */
void *data; /* The callback data of the action function */
/*
* Check arguments.
*/
if(kt==NULL || !keyseq) {
errno = EINVAL;
if(kt)
_err_record_msg(kt->err, "NULL argument(s)", END_ERR_MSG);
return 1;
};
/*
* Lookup the function that implements the specified action.
*/
if(!action) {
keyfn = 0;
data = NULL;
} else {
Symbol *sym = _find_HashSymbol(kt->actions, action);
if(!sym) {
_err_record_msg(kt->err, "Unknown key-binding action: ", action,
END_ERR_MSG);
errno = EINVAL;
return 1;
};
keyfn = (KtKeyFn *) sym->fn;
data = sym->data;
};
/*
* Record the action in the table.
*/
return _kt_set_keyfn(kt, binder, keyseq, keyfn, data);
}
/*.......................................................................
* Add, update or remove a keybinding to the table, specifying an action
* function directly.
*
* Input:
* kt KeyTab * The table to add the binding to.
* binder KtBinder The source of the binding.
* keyseq char * The key-sequence to bind.
* keyfn KtKeyFn * The action function, or NULL to remove any existing
* action function.
* data void * A pointer to anonymous data to be passed to keyfn
* whenever it is called.
* Output:
* return int 0 - OK.
* 1 - Error.
*/
int _kt_set_keyfn(KeyTab *kt, KtBinder binder, const char *keyseq,
KtKeyFn *keyfn, void *data)
{
const char *kptr; /* A pointer into keyseq[] */
char *binary; /* The binary version of keyseq[] */
int nc; /* The number of characters in binary[] */
int first,last; /* The first and last entries in the table which */
/* minimally match. */
int size; /* The size to allocate for the binary string */
int i;
/*
* Check arguments.
*/
if(kt==NULL || !keyseq) {
errno = EINVAL;
if(kt)
_err_record_msg(kt->err, "NULL argument(s)", END_ERR_MSG);
return 1;
};
/*
* Work out a pessimistic estimate of how much space will be needed
* for the binary copy of the string, noting that binary meta characters
* embedded in the input string get split into two characters.
*/
for(size=0,kptr = keyseq; *kptr; kptr++)
size += IS_META_CHAR(*kptr) ? 2 : 1;
/*
* Allocate a string that has the length of keyseq[].
*/
binary = _new_StringMemString(kt->smem, size + 1);
if(!binary) {
errno = ENOMEM;
_err_record_msg(kt->err, "Insufficient memory to record key sequence",
END_ERR_MSG);
return 1;
};
/*
* Convert control and octal character specifications to binary characters.
*/
if(_kt_parse_keybinding_string(keyseq, binary, &nc)) {
binary = _del_StringMemString(kt->smem, binary);
return 1;
};
/*
* Lookup the position in the table at which to insert the binding.
*/
switch(_kt_locate_keybinding(kt, binary, nc, &first, &last)) {
/*
* If an exact match for the key-sequence is already in the table,
* simply replace its binding function (or delete the entry if
* the new binding is 0).
*/
case KT_EXACT_MATCH:
if(keyfn) {
_kt_assign_action(kt->table + first, binder, keyfn, data);
} else {
_del_StringMemString(kt->smem, kt->table[first].keyseq);
memmove(kt->table + first, kt->table + first + 1,
(kt->nkey - first - 1) * sizeof(kt->table[0]));
kt->nkey--;
};
binary = _del_StringMemString(kt->smem, binary);
break;
/*
* If an ambiguous match has been found and we are installing a
* callback, then our new key-sequence would hide all of the ambiguous
* matches, so we shouldn't allow it.
*/
case KT_AMBIG_MATCH:
if(keyfn) {
_err_record_msg(kt->err, "Can't bind \"", keyseq,
"\", because it is a prefix of another binding",
END_ERR_MSG);
binary = _del_StringMemString(kt->smem, binary);
errno = EPERM;
return 1;
};
break;
/*
* If the entry doesn't exist, create it.
*/
case KT_NO_MATCH:
/*
* Add a new binding?
*/
if(keyfn) {
KeySym *sym;
/*
* We will need a new entry, extend the table if needed.
*/
if(kt->nkey + 1 > kt->size) {
if(_kt_extend_table(kt)) {
binary = _del_StringMemString(kt->smem, binary);
return 1;
};
};
/*
* Make space to insert the new key-sequence before 'last'.
*/
if(last < kt->nkey) {
memmove(kt->table + last + 1, kt->table + last,
(kt->nkey - last) * sizeof(kt->table[0]));
};
/*
* Insert the new binding in the vacated position.
*/
sym = kt->table + last;
sym->keyseq = binary;
sym->nc = nc;
for(i=0; iactions + i;
action->fn = 0;
action->data = NULL;
};
sym->binder = -1;
_kt_assign_action(sym, binder, keyfn, data);
kt->nkey++;
};
break;
case KT_BAD_MATCH:
binary = _del_StringMemString(kt->smem, binary);
return 1;
break;
};
return 0;
}
/*.......................................................................
* Perform a min-match lookup of a key-binding.
*
* Input:
* kt KeyTab * The keybinding table to lookup in.
* binary_keyseq char * The binary key-sequence to lookup.
* nc int the number of characters in keyseq[].
* Input/Output:
* first,last int * If there is an ambiguous or exact match, the indexes
* of the first and last symbols that minimally match
* will be assigned to *first and *last respectively.
* If there is no match, then first and last will
* bracket the location where the symbol should be
* inserted.
* Output:
* return KtKeyMatch One of the following enumerators:
* KT_EXACT_MATCH - An exact match was found.
* KT_AMBIG_MATCH - An ambiguous match was found.
* KT_NO_MATCH - No match was found.
* KT_BAD_MATCH - An error occurred while searching.
*/
static KtKeyMatch _kt_locate_keybinding(KeyTab *kt, const char *binary_keyseq,
int nc, int *first, int *last)
{
int mid; /* The index at which to bisect the table */
int bot; /* The lowest index of the table not searched yet */
int top; /* The highest index of the table not searched yet */
int test; /* The return value of strcmp() */
/*
* Perform a binary search for the key-sequence.
*/
bot = 0;
top = kt->nkey - 1;
while(top >= bot) {
mid = (top + bot)/2;
test = _kt_compare_strings(kt->table[mid].keyseq, kt->table[mid].nc,
binary_keyseq, nc);
if(test > 0)
top = mid - 1;
else if(test < 0)
bot = mid + 1;
else {
*first = *last = mid;
return KT_EXACT_MATCH;
};
};
/*
* An exact match wasn't found, but top is the index just below the
* index where a match would be found, and bot is the index just above
* where the match ought to be found.
*/
*first = top;
*last = bot;
/*
* See if any ambiguous matches exist, and if so make *first and *last
* refer to the first and last matches.
*/
if(*last < kt->nkey && kt->table[*last].nc > nc &&
_kt_compare_strings(kt->table[*last].keyseq, nc, binary_keyseq, nc)==0) {
*first = *last;
while(*last+1 < kt->nkey && kt->table[*last+1].nc > nc &&
_kt_compare_strings(kt->table[*last+1].keyseq, nc, binary_keyseq, nc)==0)
(*last)++;
return KT_AMBIG_MATCH;
};
/*
* No match.
*/
return KT_NO_MATCH;
}
/*.......................................................................
* Lookup the sub-array of key-bindings who's key-sequences minimally
* match a given key-sequence.
*
* Input:
* kt KeyTab * The keybinding table to lookup in.
* binary_keyseq char * The binary key-sequence to lookup.
* nc int the number of characters in keyseq[].
* Input/Output:
* matches KeySym ** The array of minimally matching symbols
* can be found in (*matches)[0..nmatch-1], unless
* no match was found, in which case *matches will
* be set to NULL.
* nmatch int The number of ambiguously matching symbols. This
* will be 0 if there is no match, 1 for an exact
* match, and a number greater than 1 for an ambiguous
* match.
* Output:
* return KtKeyMatch One of the following enumerators:
* KT_EXACT_MATCH - An exact match was found.
* KT_AMBIG_MATCH - An ambiguous match was found.
* KT_NO_MATCH - No match was found.
* KT_BAD_MATCH - An error occurred while searching.
*/
KtKeyMatch _kt_lookup_keybinding(KeyTab *kt, const char *binary_keyseq,
int nc, KeySym **matches, int *nmatch)
{
KtKeyMatch status; /* The return status */
int first,last; /* The indexes of the first and last matching entry */
/* in the symbol table. */
/*
* Check the arguments.
*/
if(!kt || !binary_keyseq || !matches || !nmatch || nc < 0) {
errno = EINVAL;
if(kt)
_err_record_msg(kt->err, "NULL argument(s)", END_ERR_MSG);
return KT_BAD_MATCH;
};
/*
* Lookup the indexes of the binding-table entries that bracket the
* target key-sequence.
*/
status = _kt_locate_keybinding(kt, binary_keyseq, nc, &first, &last);
/*
* Translate the indexes into the corresponding subarray of matching
* table entries.
*/
switch(status) {
case KT_EXACT_MATCH:
case KT_AMBIG_MATCH:
*matches = kt->table + first;
*nmatch = last - first + 1;
break;
default:
*matches = NULL;
*nmatch = 0;
break;
};
return status;
}
/*.......................................................................
* Convert a keybinding string into a uniq binary representation.
*
* Control characters can be given directly in their binary form,
* expressed as either ^ or C-, followed by the character, expressed in
* octal, like \129 or via C-style backslash escapes, with the addition
* of '\E' to denote the escape key. Similarly, meta characters can be
* given directly in binary or expressed as M- followed by the character.
* Meta characters are recorded as two characters in the binary output
* string, the first being the escape key, and the second being the key
* that was modified by the meta key. This means that binding to
* \EA or ^[A or M-A are all equivalent.
*
* Input:
* keyseq char * The key sequence being added.
* Input/Output:
* binary char * The binary version of the key sequence will be
* assigned to binary[], which must have at least
* as many characters as keyseq[] plus the number
* of embedded binary meta characters.
* nc int * The number of characters assigned to binary[]
* will be recorded in *nc.
* Output:
* return int 0 - OK.
* 1 - Error.
*/
static int _kt_parse_keybinding_string(const char *keyseq, char *binary,
int *nc)
{
const char *iptr = keyseq; /* Pointer into keyseq[] */
char *optr = binary; /* Pointer into binary[] */
char c; /* An intermediate character */
/*
* Parse the input characters until they are exhausted or the
* output string becomes full.
*/
while(*iptr) {
/*
* Check for special characters.
*/
switch(*iptr) {
case '^': /* A control character specification */
/*
* Convert the caret expression into the corresponding control
* character unless no character follows the caret, in which case
* record a literal caret.
*/
if(iptr[1]) {
/*
* Get the next, possibly escaped, character.
*/
if(iptr[1] == '\\') {
c = _kt_backslash_escape(iptr+2, &iptr);
} else {
c = iptr[1];
iptr += 2;
};
/*
* Convert the character to a control character.
*/
*optr++ = MAKE_CTRL(c);
} else {
*optr++ = *iptr++;
};
break;
/*
* A backslash-escaped character?
*/
case '\\':
/*
* Convert the escape sequence to a binary character.
*/
*optr++ = _kt_backslash_escape(iptr+1, &iptr);
break;
/*
* Possibly an emacs-style meta character?
*/
case 'M':
if(_kt_is_emacs_meta(iptr)) {
*optr++ = GL_ESC_CHAR;
iptr += 2;
} else {
*optr++ = *iptr++;
};
break;
/*
* Possibly an emacs-style control character specification?
*/
case 'C':
if(_kt_is_emacs_ctrl(iptr)) {
*optr++ = MAKE_CTRL(iptr[2]);
iptr += 3;
} else {
*optr++ = *iptr++;
};
break;
default:
/*
* Convert embedded meta characters into an escape character followed
* by the meta-unmodified character.
*/
if(IS_META_CHAR(*iptr)) {
*optr++ = GL_ESC_CHAR;
*optr++ = META_TO_CHAR(*iptr);
iptr++;
/*
* To allow keysequences that start with printable characters to
* be distinguished from the cursor-key keywords, prepend a backslash
* to the former. This same operation is performed in gl_interpret_char()
* before looking up a keysequence that starts with a printable character.
*/
} else if(iptr==keyseq && !IS_CTRL_CHAR(*iptr) &&
strcmp(keyseq, "up") != 0 && strcmp(keyseq, "down") != 0 &&
strcmp(keyseq, "left") != 0 && strcmp(keyseq, "right") != 0) {
*optr++ = '\\';
*optr++ = *iptr++;
} else {
*optr++ = *iptr++;
};
};
};
/*
* How many characters were placed in the output array?
*/
*nc = optr - binary;
return 0;
}
/*.......................................................................
* Add, remove or modify an action.
*
* Input:
* kt KeyTab * The key-binding table.
* action char * The name of the action.
* fn KtKeyFn * The function that implements the action, or NULL
* to remove an existing action.
* data void * A pointer to arbitrary callback data to pass to the
* action function whenever it is called.
* Output:
* return int 0 - OK.
* 1 - Error.
*/
int _kt_set_action(KeyTab *kt, const char *action, KtKeyFn *fn, void *data)
{
Symbol *sym; /* The symbol table entry of the action */
/*
* Check the arguments.
*/
if(!kt || !action) {
errno = EINVAL;
if(kt)
_err_record_msg(kt->err, "NULL argument(s)", END_ERR_MSG);
return 1;
};
/*
* If no function was provided, delete an existing action.
*/
if(!fn) {
sym = _del_HashSymbol(kt->actions, action);
return 0;
};
/*
* If the action already exists, replace its action function.
*/
sym = _find_HashSymbol(kt->actions, action);
if(sym) {
sym->fn = (void (*)(void))fn;
sym->data = data;
return 0;
};
/*
* Add a new action.
*/
if(!_new_HashSymbol(kt->actions, action, 0, (void (*)(void))fn, data, 0)) {
_err_record_msg(kt->err, "Insufficient memory to record key-binding action",
END_ERR_MSG);
return 1;
};
return 0;
}
/*.......................................................................
* Compare two strings of specified length which may contain embedded
* ascii NUL's.
*
* Input:
* s1 char * The first of the strings to be compared.
* n1 int The length of the string in s1.
* s2 char * The second of the strings to be compared.
* n2 int The length of the string in s2.
* Output:
* return int < 0 if(s1 < s2)
* 0 if(s1 == s2)
* > 0 if(s1 > s2)
*/
static int _kt_compare_strings(const char *s1, int n1, const char *s2, int n2)
{
int i;
/*
* Find the first character where the two strings differ.
*/
for(i=0; i= KTB_NBIND)
return;
/*
* Record the action according to its source.
*/
action = sym->actions + binder;
action->fn = keyfn;
action->data = data;
/*
* Find the highest priority binding source that has supplied an
* action. Note that the actions[] array is ordered in order of
* descreasing priority, so the first entry that contains a function
* is the one to use.
*/
for(i=0; iactions[i].fn; i++)
;
/*
* Record the index of this action for use during lookups.
*/
sym->binder = i < KTB_NBIND ? i : -1;
return;
}
/*.......................................................................
* Remove all key bindings that came from a specified source.
*
* Input:
* kt KeyTab * The table of key bindings.
* binder KtBinder The source of the bindings to be cleared.
*/
void _kt_clear_bindings(KeyTab *kt, KtBinder binder)
{
int oldkey; /* The index of a key in the original binding table */
int newkey; /* The index of a key in the updated binding table */
/*
* If there is no table, then no bindings exist to be deleted.
*/
if(!kt)
return;
/*
* Clear bindings of the given source.
*/
for(oldkey=0; oldkeynkey; oldkey++)
_kt_assign_action(kt->table + oldkey, binder, 0, NULL);
/*
* Delete entries that now don't have a binding from any source.
*/
newkey = 0;
for(oldkey=0; oldkeynkey; oldkey++) {
KeySym *sym = kt->table + oldkey;
if(sym->binder < 0) {
_del_StringMemString(kt->smem, sym->keyseq);
} else {
if(oldkey != newkey)
kt->table[newkey] = *sym;
newkey++;
};
};
/*
* Record the number of keys that were kept.
*/
kt->nkey = newkey;
return;
}
/*.......................................................................
* Translate a backslash escape sequence to a binary character.
*
* Input:
* string const char * The characters that follow the backslash.
* Input/Output:
* endp const char ** If endp!=NULL, on return *endp will be made to
* point to the character in string[] which follows
* the escape sequence.
* Output:
* return char The binary character.
*/
static char _kt_backslash_escape(const char *string, const char **endp)
{
char c; /* The output character */
/*
* Is the backslash followed by one or more octal digits?
*/
switch(*string) {
case '0': case '1': case '2': case '3':
case '4': case '5': case '6': case '7':
c = strtol(string, (char **)&string, 8);
break;
case 'a':
c = '\a';
string++;
break;
case 'b':
c = '\b';
string++;
break;
case 'e': case 'E': /* Escape */
c = GL_ESC_CHAR;
string++;
break;
case 'f':
c = '\f';
string++;
break;
case 'n':
c = '\n';
string++;
break;
case 'r':
c = '\r';
string++;
break;
case 't':
c = '\t';
string++;
break;
case 'v':
c = '\v';
string++;
break;
case '\0':
c = '\\';
break;
default:
c = *string++;
break;
};
/*
* Report the character which follows the escape sequence.
*/
if(endp)
*endp = string;
return c;
}
/*.......................................................................
* Return non-zero if the next two characters are M- and a third character
* follows. Otherwise return 0.
*
* Input:
* string const char * The sub-string to scan.
* Output:
* return int 1 - The next two characters are M- and these
* are followed by at least one character.
* 0 - The next two characters aren't M- or no
* character follows a M- pair.
*/
static int _kt_is_emacs_meta(const char *string)
{
return *string++ == 'M' && *string++ == '-' && *string;
}
/*.......................................................................
* Return non-zero if the next two characters are C- and a third character
* follows. Otherwise return 0.
*
* Input:
* string const char * The sub-string to scan.
* Output:
* return int 1 - The next two characters are C- and these
* are followed by at least one character.
* 0 - The next two characters aren't C- or no
* character follows a C- pair.
*/
static int _kt_is_emacs_ctrl(const char *string)
{
return *string++ == 'C' && *string++ == '-' && *string;
}
/*.......................................................................
* Merge an array of bindings with existing bindings.
*
* Input:
* kt KeyTab * The table of key bindings.
* binder KtBinder The source of the bindings.
* bindings const KtKeyBinding * The array of bindings.
* n int The number of bindings in bindings[].
* Output:
* return int 0 - OK.
* 1 - Error.
*/
int _kt_add_bindings(KeyTab *kt, KtBinder binder, const KtKeyBinding *bindings,
unsigned n)
{
int i;
/*
* Check the arguments.
*/
if(!kt || !bindings) {
errno = EINVAL;
if(kt)
_err_record_msg(kt->err, "NULL argument(s)", END_ERR_MSG);
return 1;
};
/*
* Install the array of bindings.
*/
for(i=0; ierr, "NULL argument(s)", END_ERR_MSG);
return 1;
};
/*
* Lookup the symbol table entry of the action.
*/
sym = _find_HashSymbol(kt->actions, action);
if(!sym)
return 1;
/*
* Return the function and ccallback data associated with the action.
*/
if(fn)
*fn = (KtKeyFn *) sym->fn;
if(data)
*data = sym->data;
return 0;
}
/*.......................................................................
* Return extra information (ie. in addition to that provided by errno)
* about the last error to occur in any of the public functions of this
* module.
*
* Input:
* kt KeyTab * The table of key bindings.
* Output:
* return const char * A pointer to the internal buffer in which
* the error message is temporarily stored.
*/
const char *_kt_last_error(KeyTab *kt)
{
return kt ? _err_get_msg(kt->err) : "NULL KeyTab argument";
}
yuma123_2.14/libtecla/libtecla.map 0000664 0001750 0001750 00000010112 14770023131 017153 0 ustar vladimir vladimir # This mapfile (or version script) lists the public symbols that are
# publically exported by each version of the tecla library. This file
# has the format required by the Sun and Linux linkers, and also acts
# as a template from which map files for other systems can be derived
# with awk or sed.
#
# Under Solaris and Linux, this map file is used by ld during shared
# library creation. It has two purposes:
#
# 1. It specifies which symbols in the library are to be made visible
# to applications. This has the dual benefits of reducing namespace
# polution, and of preventing applications from using private
# internal library functions that might change or disappear in
# future releases.
#
# 2. The information listed in this file is recorded in the shared
# library, such that when an application is linked against it, the
# linker can record a dependency in the application which says
# which is the earliest library version which included all of the
# symbols that the application needs. This means that if the
# application is copied to another system that has an earlier
# version of the library, the linker can quickly determine whether
# the earlier version contains all of the symbols that it needs.
#
# Under Linux, mapfiles can also be used to allow multiple
# incompatible versions of a given function to exist in a library,
# thus supporting applications that were compiled against different
# incompatible versions of the library. Since this feature (and the
# inclusion of .symver directives) isn't supported by Solaris, it
# can't be included in this file. Non backwards compatibility in the
# ABI must instead be handled in the more traditional way, by
# incrementing the major version number.
#
# When a new minor release is made, a new tecla_1.x specification
# should be added which inherits the symbols of the previous release
# and lists newly added functions. For example, below you will find
# the following clause:
#
# tecla_1.3 {
# global:
# ef_list_expansions;
# } tecla_1.2;
#
# This says that ef_list_expansions is the name of a public function
# that was added in the 1.3 release, and that the symbols defined in
# the previous tecla_1.2 clause have been inherited by tecla_1.3.
#
# For more details see the following URL:
#
# http://www.usenix.org/publications/library/proceedings/als2000/browndavid.html
#-------------------------------------------------------------------------------
tecla_1.2 {
global:
cfc_file_start;
cfc_literal_escapes;
cfc_set_check_fn;
cpl_add_completion;
cpl_check_exe;
cpl_complete_word;
cpl_file_completions;
cpl_init_FileArgs;
cpl_last_error;
cpl_list_completions;
cpl_record_error;
del_CplFileConf;
del_ExpandFile;
del_GetLine;
del_PathCache;
del_PcaPathConf;
del_WordCompletion;
ef_expand_file;
ef_last_error;
gl_change_terminal;
gl_customize_completion;
gl_get_line;
new_CplFileConf;
new_ExpandFile;
new_GetLine;
new_PathCache;
new_PcaPathConf;
new_WordCompletion;
pca_last_error;
pca_lookup_file;
pca_path_completions;
pca_scan_path;
pca_set_check_fn;
ppc_file_start;
ppc_literal_escapes;
local:
*;
};
tecla_1.3 {
global:
ef_list_expansions;
} tecla_1.2;
tecla_1.4 {
global:
gl_configure_getline;
gl_save_history;
gl_load_history;
gl_group_history;
gl_show_history;
gl_resize_history;
gl_limit_history;
gl_clear_history;
gl_toggle_history;
gl_watch_fd;
libtecla_version;
gl_terminal_size;
gl_state_of_history;
gl_range_of_history;
gl_size_of_history;
gl_lookup_history;
gl_echo_mode;
gl_replace_prompt;
gl_prompt_style;
gl_ignore_signal;
gl_trap_signal;
gl_last_signal;
} tecla_1.3;
tecla_l.5 {
global:
gl_inactivity_timeout;
gl_completion_action;
gl_register_action;
gl_display_text;
gl_error_message;
gl_return_status;
gl_set_term_size;
gl_list_signals;
gl_catch_blocked;
gl_io_mode;
gl_raw_io;
gl_normal_io;
gl_tty_signals;
gl_abandon_line;
gl_handle_signal;
gl_pending_io;
gl_bind_keyseq;
cpl_recall_matches;
gl_erase_terminal;
} tecla_1.4;
tecla_1.6 {
global:
gl_append_history;
gl_automatic_history;
gl_query_char;
gl_read_char;
} tecla_l.5;
yuma123_2.14/libtecla/strngmem.c 0000664 0001750 0001750 00000015252 14770023131 016707 0 ustar vladimir vladimir /*
* Copyright (c) 2000, 2001, 2002, 2003, 2004 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 "strngmem.h"
#include "freelist.h"
struct StringMem {
unsigned long nmalloc; /* The number of strings allocated with malloc */
FreeList *fl; /* The free-list */
};
/*.......................................................................
* Create a string free-list container and the first block of its free-list.
*
* Input:
* blocking_factor int The blocking_factor argument specifies how
* many strings of length SM_STRLEN
* bytes (see stringmem.h) are allocated in each
* free-list block.
* For example if blocking_factor=64 and
* SM_STRLEN=16, then each new
* free-list block will take 1K of memory.
* Output:
* return StringMem * The new free-list container, or NULL on
* error.
*/
StringMem *_new_StringMem(unsigned blocking_factor)
{
StringMem *sm; /* The container to be returned. */
/*
* Check arguments.
*/
if(blocking_factor < 1) {
errno = EINVAL;
return NULL;
};
/*
* Allocate the container.
*/
sm = (StringMem *) malloc(sizeof(StringMem));
if(!sm) {
errno = ENOMEM;
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_StringMem().
*/
sm->nmalloc = 0;
sm->fl = NULL;
/*
* Allocate the free-list.
*/
sm->fl = _new_FreeList(SM_STRLEN, blocking_factor);
if(!sm->fl)
return _del_StringMem(sm, 1);
/*
* Return the free-list container.
*/
return sm;
}
/*.......................................................................
* Delete a string free-list.
*
* Input:
* sm StringMem * The string free-list to be deleted, or NULL.
* force int If force==0 then _del_StringMem() will complain
* and refuse to delete the free-list if any
* of nodes have not been returned to the free-list.
* If force!=0 then _del_StringMem() will not check
* whether any nodes are still in use and will
* always delete the list.
* Output:
* return StringMem * Always NULL (even if the list couldn't be
* deleted).
*/
StringMem *_del_StringMem(StringMem *sm, int force)
{
if(sm) {
/*
* Check whether any strings have not been returned to the free-list.
*/
if(!force && (sm->nmalloc > 0 || _busy_FreeListNodes(sm->fl) > 0)) {
errno = EBUSY;
return NULL;
};
/*
* Delete the free-list.
*/
sm->fl = _del_FreeList(sm->fl, force);
/*
* Delete the container.
*/
free(sm);
};
return NULL;
}
/*.......................................................................
* Allocate an array of 'length' chars.
*
* Input:
* sm StringMem * The string free-list to allocate from.
* length size_t The length of the new string (including '\0').
* Output:
* return char * The new string or NULL on error.
*/
char *_new_StringMemString(StringMem *sm, size_t length)
{
char *string; /* The string to be returned */
int was_malloc; /* True if malloc was used to allocate the string */
/*
* Check arguments.
*/
if(!sm)
return NULL;
if(length < 1)
length = 1;
/*
* Allocate the new node from the free list if possible.
*/
if(length < SM_STRLEN) {
string = (char *)_new_FreeListNode(sm->fl);
if(!string)
return NULL;
was_malloc = 0;
} else {
string = (char *) malloc(length+1); /* Leave room for the flag byte */
if(!string)
return NULL;
/*
* Count malloc allocations.
*/
was_malloc = 1;
sm->nmalloc++;
};
/*
* Use the first byte of the string to record whether the string was
* allocated with malloc or from the free-list. Then return the rest
* of the string for use by the user.
*/
string[0] = (char) was_malloc;
return string + 1;
}
/*.......................................................................
* Free a string that was previously returned by _new_StringMemString().
*
* Input:
* sm StringMem * The free-list from which the string was originally
* allocated.
* s char * The string to be returned to the free-list, or NULL.
* Output:
* return char * Always NULL.
*/
char *_del_StringMemString(StringMem *sm, char *s)
{
int was_malloc; /* True if the string originally came from malloc() */
/*
* Is there anything to be deleted?
*/
if(s && sm) {
/*
* Retrieve the true string pointer. This is one less than the one
* returned by _new_StringMemString() because the first byte of the
* allocated memory is reserved by _new_StringMemString as a flag byte
* to say whether the memory was allocated from the free-list or directly
* from malloc().
*/
s--;
/*
* Get the origination flag.
*/
was_malloc = s[0];
if(was_malloc) {
free(s);
s = NULL;
sm->nmalloc--;
} else {
s = (char *) _del_FreeListNode(sm->fl, s);
};
};
return NULL;
}
yuma123_2.14/libtecla/Makefile.am 0000664 0001750 0001750 00000003412 14770023131 016736 0 ustar vladimir vladimir noinst_LTLIBRARIES = libtecla.la
noinst_HEADERS= \
$(top_srcdir)/libtecla/chrqueue.h \
$(top_srcdir)/libtecla/cplfile.h \
$(top_srcdir)/libtecla/cplmatch.h \
$(top_srcdir)/libtecla/direader.h \
$(top_srcdir)/libtecla/errmsg.h \
$(top_srcdir)/libtecla/expand.h \
$(top_srcdir)/libtecla/freelist.h \
$(top_srcdir)/libtecla/getline.h \
$(top_srcdir)/libtecla/hash.h \
$(top_srcdir)/libtecla/history.h \
$(top_srcdir)/libtecla/homedir.h \
$(top_srcdir)/libtecla/ioutil.h \
$(top_srcdir)/libtecla/keytab.h \
$(top_srcdir)/libtecla/libtecla.h \
$(top_srcdir)/libtecla/pathutil.h \
$(top_srcdir)/libtecla/stringrp.h \
$(top_srcdir)/libtecla/strngmem.h
libtecla_la_SOURCES = \
$(top_srcdir)/libtecla/chrqueue.c \
$(top_srcdir)/libtecla/cplfile.c \
$(top_srcdir)/libtecla/cplmatch.c \
$(top_srcdir)/libtecla/direader.c \
$(top_srcdir)/libtecla/enhance.c \
$(top_srcdir)/libtecla/errmsg.c \
$(top_srcdir)/libtecla/expand.c \
$(top_srcdir)/libtecla/freelist.c \
$(top_srcdir)/libtecla/getline.c \
$(top_srcdir)/libtecla/hash.c \
$(top_srcdir)/libtecla/history.c \
$(top_srcdir)/libtecla/homedir.c \
$(top_srcdir)/libtecla/ioutil.c \
$(top_srcdir)/libtecla/keytab.c \
$(top_srcdir)/libtecla/pathutil.c \
$(top_srcdir)/libtecla/pcache.c \
$(top_srcdir)/libtecla/stringrp.c \
$(top_srcdir)/libtecla/strngmem.c \
$(top_srcdir)/libtecla/version.c
libtecla_la_CPPFLAGS = -I$(top_srcdir)/netconf/src/agt -I$(top_srcdir)/netconf/src/mgr -I$(top_srcdir)/libtecla -DSTDC_HEADERS=1 -DHAVE_SYS_TYPES_H=1 -DHAVE_SYS_STAT_H=1 -DHAVE_STDLIB_H=1 -DHAVE_STRING_H=1 -DHAVE_MEMORY_H=1 -DHAVE_STRINGS_H=1 -DHAVE_INTTYPES_H=1 -DHAVE_STDINT_H=1 -DHAVE_UNISTD_H=1 -DUSE_TERMINFO=1 -DHAVE_CURSES_H=1 -DHAVE_TERM_H=1 -DHAVE_SYS_SELECT_H=1 -DHAVE_SELECT=1 -DHAVE_SYSV_PTY=1 -D_SVID_SOURCE -D_BSD_SOURCE
libtecla_la_LDFLAGS = -static
yuma123_2.14/libtecla/RELEASE.NOTES 0000664 0001750 0001750 00000070203 14770023131 016576 0 ustar vladimir vladimir This file lists major changes which accompany each new release.
Version 1.6.1:
This is primarily a minor bug-fix release.
One added feature is the ability to call gl_normal_io() from
callbacks registered by gl_watch_fd() and
gl_inactivity_timeout(). This allows these callbacks to cleanly
suspend line editing before either reading from the terminal, or
writing to the terminal; and then subsequently causes the input line
to be automatically redisplayed, and line-editing to be resumed by
gl_get_line(), as soon as the callback returns.
Another minor change is that if the terminal type specified in the
TERM environment variable is set to "dumb", gl_get_line() now treats
the terminal as though it were a non-interactive stream, rather than
treating it as a VT100-compatible terminal. This means that it
doesn't either prompt for input, or perform any command-line
editing, even when it really is interacting with a terminal. This is
aimed at the rare situation where a third-pary program that connects
to libtecla through an embedded pseudo-terminal, needs to be forced
to behave as though it weren't talking to a terminal, in order that
it be useable in non-interactive scripts.
Note that in the previous release, the optional configuration
function, gl_tty_signals(), was incorrectly swapping the suspend and
terminal signal handlers before installing them.
A configuration problem that prevented select() from being used
under MacOS X, has been fixed.
Although not documented in the man page, it was meant to be possible
to take the input line that one call to gl_get_line() returned, and
ask the next call to gl_get_line() to present it back to the user
for re-editing, simply by passing the pointer returned by one call
to gl_get_line() as the start_line argument of the next call to
gl_get_line(). This feature unfortunately stopped working in 1.6.0,
so this release restores it, and officially documents it in the man
page documentation of gl_get_line().
In the previous version of the library, calling gl_terminal_size()
on a system without SIGWINCH support, would crash the
application. This has been fixed.
Libtecla now apparently compiles cleanly under IRIX.
Version 1.6.0:
This release is primarily a bug-fix release. However there are also
four new functions, so the minor version number has been
incremented to reflect this.
Two of the new functions are gl_automatic_history() and
gl_append_history(). The former of these functions allows the
application to tell gl_get_line() not to automatically archive
entered lines in the history list. The second of these functions
allows the application to explicitly append a line to the history
list. Thus together, these two functions allow the calling
application to take over control of what is placed in the history
list.
The third new function is gl_query_char(), which prompts the user
for a single character reply, which the user can then type without
having to hit return to enter it. Unless echoing is disabled, the
character that is entered is then displayed after the prompt,
and a newline is started.
Finally, the 4th new function is gl_read_char(), which also reads
a single character from the user, but doesn't prompt the user, write
anything to the terminal, or disturb any partially entered input
line. It is thus safe to call this function not only from between
calls to gl_get_line(), but also from application callback
functions, even if gl_normal_io() hasn't been called.
When using the history-search-backwards or history-search-forwards
actions, if the search prefix that the user typed, contains any of
the *,? or [ globbing characters, it is now treated as a glob
pattern to be matched against historical lines, instead of a simple
prefix.
I have added a --without-file-system option to the configure
script. This is intended for use in embedded systems that either
don't have filesystems, or where the file-system code in libtecla is
seen as unwanted bloat. See the INSTALL document for details.
Similarly, I also added a --without-file-actions option to the
configure script. This allows the application author/installer to
prevent users of gl_get_line() from accessing the filesystem with
the builtin actions of gl_get_line(). It does this by removing a
number of action functions, such as expand-filename, and list-glob,
and by changing the default behavior of other actions, such as
complete-word and list-or-eof, to show no completions.
Now to the bugs that have been fixed. Version 1.5.0 had a lot of big
internal changes, so there are a number of bugs that needed to be
fixed. There was a bug which caused a crash if gl_load_history()
was called multiple times. There was another bug which caused a
prompt not to be displayed on the next line after switching from
reading input from a file to reading from the terminal. Also, in
tecla configuration files, backslash escaped characters within
key-binding key-sequences weren't being escaped. Thus ^\\ got
interpretted as a control-\ followed by a \ character instead of as
a control-\. There was a bug in the history recall mechanism which
caused the search prefix to be forgotten in certain complicated
usage scenarios. There was a minor memory leak in the
gl_configure_getline() function. Finally, if gl_get_line() was
aborted by a signal, or any other abnormal event, the value of errno
which originally indicated what had happened, got zeroed by the
code that restored the terminal to a usable state. Thus the
application couldn't figure out what had caused the error, apart
from by looking at gl_return_status(). All of these bugs have been
fixed.
In the Makefile, there were a number of places where install-sh was
invoked without a path prefix. This has now been remedied.
A fully functional workaround for a bug in Solaris' terminal I/O
code has also been implemented. This bug, which only manifested
itself in libtecla's uncommonly used non-blocking server I/O mode,
caused characters entered while in normal I/O mode, between calls to
gl_get_line() to be invisible to the next call to gl_get_line(),
until the user typed at least one more key after raw terminal mode
was restored.
The Gnu autoconf config.guess and config.sub scripts have been
updated to their latest versions. Apparently the old versions that I
was previously using were too old to know about certain BSD ports.
Version 1.5.0:
This release includes several major new features for those using
gl_get_line(), shared library support in Darwin, better cross
compilation support, and various minor bug fixes.
The biggest new feature is the option of a non-blocking I/O mode, in
which gl_get_line() can safely be called from an application's
external event-loop to incrementally read input lines from the user.
This feature is documented in the gl_io_mode(3) man page.
In addition, there is now support for the definition of additional
word-completion action functions, which can then be bound to
different keys. See the documentation of the gl_completion_action()
function in the gl_get_line(3) man page.
Externally defined action functions can also be defined, although
presently they don't have write access to the input line, so they
are restricted to operations that display information text to the
terminal, or modify the environment of the calling application in
some way. See the documentation of the gl_register_action() function
in the gl_get_line(3) man page.
Some of the non-blocking I/O support functions can also be used for
improved signal handling in the normal blocking mode. In particular,
the gl_list_signals() and gl_catch_blocked() functions make it
easier to write reliable signal handling around gl_get_line(). The
new "RELIABLE SIGNAL HANDLING" section of the gl_get_line(3) man
page is intended as an introduction to this subject.
Programs can now clear the terminal between calls to gl_get_line(),
by calling the new gl_erase_terminal() function.
The gl_display_text() function, now used in the demos to display
introductory banners, is provided for formatting text according to
the width of the terminal.
It is now possible to install inactivity timeout callbacks in
gl_get_line(), using the new gl_inactivity_timeout() function.
The new gl_set_term_size() function allows the application to
explicitly set the terminal size, for cases, such as when one is
using a terminal at the end of a serial lineq, where the terminal
driver doesn't send the process a SIGWINCH when the terminal size
changes.
The new gl_bind_keyseq() function provides a convenient
alternative to gl_configure_getline(), for binding or unbinding
one key-sequence at a time.
gl_get_line()s signal handling, file-descriptor event-handling,
inactivity-timeout handling and server-mode non-blocking I/O
features now not only work when input is coming from a terminal, but
now also work when input is coming from non-interactive streams,
such as files and pipes.
The history implementation has been re-written to make it more
efficient and easier to modify. The biggest user-level change is
that when recalling history lines using a search prefix, the same
line is no longer returned more than once in a row. Previously this
duplicate elimination only worked when one was recalling a line
without specifying a search prefix, and this was naively performed
by preventing neighboring duplicates from existing in the history
list, rather than by skipping duplicates at search time.
In previous versions of the library, when gl_get_line() and its
associated public functions detected invalid arguments, or couldn't
allocate memory, etc, error messages were written to stderr. This
isn't appropriate for library functions, so instead of writing such
messages to stderr, these messages are now recorded in buffers
within the affected GetLine object. The latest error message can
then subsequently be queried by calling gl_error_message(). The use
of errno has also been expanded, and a new function called
gl_return_status() has been provided to expand on the cause of the
last return from gl_get_line().
User level usage and configuration information has now been split
out of the gl_get_line(3) man page into a separate tecla(7) man
page. The enhance(3) man page has also been renamed to enhance(1).
When expanding "~/", gl_get_line() now checks for, and returns the
value of the HOME environment variable, if it exists, in preference
to looking up the directory of the current user in the password
file.
When the terminal was resized to a narrower width, previous versions
of gl_get_line() would redraw the line higher up the terminal. This
bug has been fixed. A bug in history recall has also been fixed, in
which an error message was being generated if one attempted to
recall a line while the cursor was at the end of the longest
possible input line. A more serious bug, in which callbacks
registered by gl_watch_fd() weren't being called for write-events,
has also been fixed. Finally, a few minor fixes have been made to
improve support under QNX and Mac OS X.
Beware that in this release, much of the underlying code has
undergone some radical re-work, so although backwards compatibility
of all documented features has been preserved, there may be some
lingering bugs that could break existing programs. So, if you plan
to use this version in production code, please test it as far as
possible within your application before releasing it to your
clients, and as always, please report any unexpected behavior.
Version 1.4.1:
This is a maintenance release. It includes minor changes to support
Mac OS X (Darwin), the QNX real-time operating system, and Cygwin
under Windows. It also fixes an oversight that was preventing the
tab key from inserting tab characters when users unbound the
complete-word action from it.
Version 1.4.0:
The contents of the history list can now be saved and restored with
the new gl_save_history() and gl_load_history() functions.
Event handlers can now be registered to watch for and respond to I/O
on arbitrary file descriptors while gl_get_line() is waiting for
terminal input from the user. See the gl_get_line(3) man page
for details on gl_watch_fd().
As an optional alternative to getting configuration information only
from ~/.teclarc, the new gl_configure_getline() function allows
configuration commands to be taken from any of, a string, a
specified application-specific file, and/or a specified
user-specific file. See the gl_get_line(3) man page for details.
The version number of the library can now be queried using the
libtecla_version() function. See the libtecla(3) man page.
The new gl_group_history() function allows applications to group
different types of input line in the history buffer, and arrange for
only members of the appropriate group to be recalled on a given call
to gl_get_line(). See the gl_get_line(3) man page.
The new gl_show_history() function displays the current history list
to a given stdio output stream. See the gl_get_line(3) man page.
new_GetLine() now allows you to specify a history buffer size of
zero, thus requesting that no history buffer be allocated. You can
subsequently resize or delete the history buffer at any time, by
calling gl_resize_history(), limit the number of lines that are
allowed in the buffer by calling gl_limit_history(), clear either
all history lines from the history list, or just the history lines
that are associated with the current history group, by calling
gl_clear_history, and toggle the history mechanism on and off by
calling gl_toggle_history().
The new gl_terminal_size() function can be used to query the
current terminal size. It can also be used to supply a default
terminal size on systems where no mechanism is available for
looking up the size.
The contents and configuration of the history list can now be
obtained by the calling application, by calling the new
gl_lookup_history(), gl_state_of_history(), gl_range_of_history()
and gl_size_of_history() functions. See the gl_get_line(3) man page.
Echoing of the input line as it is typed, can now be turned on and
off via the new gl_echo_mode() function. While echoing is disabled,
newly entered input lines are omitted from the history list. See
the gl_get_line(3) man page.
While the default remains to display the prompt string literally,
the new gl_prompt_style() function can be used to enable text
attribute formatting directives in prompt strings, such as
underlining, bold font, and highlighting directives.
Signal handling in gl_get_line() is now customizable. The default
signal handling behavior remains essentially the same, except that
the SIGTSTP, SIGTTIN and SIGTTOU are now forwarded to the
corresponding signal handler of the calling program, instead of
causing a SIGSTOP to be sent to the application. It is now possible
to remove signals from the list that are trapped by gl_get_line(),
as well as add new signals to this list. The signal and terminal
environments in which the signal handler of the calling program is
invoked, and what gl_get_line() does after the signal handler
returns, is now customizable on a per signal basis. You can now also
query the last signal that was caught by gl_get_line(). This is
useful when gl_get_line() aborts with errno=EINTR, and you need to
know which signal caused it to abort.
Key-sequences bound to action functions can now start with printable
characters. Previously only keysequences starting with control or
meta characters were permitted.
gl_get_line() is now 8-bit clean. If the calling program has
correctly called setlocale(LC_CTYPE,""), then the user can select an
alternate locale by setting the standard LC_CTYPE, LC_ALL, or LANG
environment variables, and international characters can then be
entered directly, either by using a non-US keyboard, or by using a
compose key on a standard US keyboard. Note that in locales in which
meta characters become printable, meta characters no longer match
M-c bindings, which then have to be entered using their escape-c
equivalents. Fortunately most modern terminal emulators either
output the escape-c version by default when the meta key is used, or
can be configured to do so (see the gl_get_line(3) man page), so in
most cases you can continue to use the meta key.
Completion callback functions can now tell gl_get_line() to return
the input line immediately after a successful tab completion, simply
by setting the last character of the optional continuation suffix to
a newline character (ie. in the call to cpl_add_completion()).
It is now safe to create and use multiple GetLine objects, albeit
still only from a single thread. In conjunction with the new
gl_configure_getline() function, this optionally allows multiple
GetLine objects with different bindings to be used to implement
different input modes.
The edit-mode configuration command now accepts the argument,
none. This tells gl_get_line() to revert to using just the native
line editing facilities provided by the terminal driver. This could
be used if the termcap or terminfo entry of the host terminal were
badly corrupted.
Application callback functions invoked by gl_get_line() can now
change the displayed prompt using the gl_replace_prompt() function.
Their is now an optional program distributed with the library. This
is a beta release of a program which adds tecla command-line editing
to virtually any third party application without the application
needing to be linked to the library. See the enhance(3) man page for
further details. Although built and installed by default, the
INSTALL document explains how to prevent this.
The INSTALL document now explains how you can stop the demo programs
from being built and installed.
NetBSD/termcap fixes. Mike MacFaden reported two problems that he
saw when compiling libtecla under NetBSD. Both cases were related to
the use of termcap. Most systems use terminfo, so this problem has
gone unnoticed until now, and won't have affected the grand majority
of users. The configure script had a bug which prevented the check
for CPP working properly, and getline.c wouldn't compile due to an
undeclared variable when USE_TERMCAP was defined. Both problems have
now been fixed. Note that if you successfully compiled version
1.3.3, this problem didn't affect you.
An unfortunate and undocumented binding of the key-sequence M-O was
shadowing the arrow-key bindings on systems that use ^[OA etc. I
have removed this binding (the documented lower case M-o binding
remains bound). Under the KDE konsole terminal this was causing the
arrow keys to do something other than expected.
There was a bug in the history list code which could result in
strange entries appearing at the start of the history list once
enough history lines had been added to the list to cause the
circular history buffer to wrap. This is now fixed.
Version 1.3.3:
Signal handling has been re-written, and documentation of its
behaviour has been added to the gl_get_line(3) man page. In addition
to eliminating race conditions, and appropriately setting errno for
those signals that abort gl_get_line(), many more signals are now
intercepted, making it less likely that the terminal will be left in
raw mode by a signal that isn't trapped by gl_get_line().
A bug was also fixed that was leaving the terminal in raw mode if
the editing mode was changed interactively between vi and emacs.
This was only noticeable when running programs from old shells that
don't reset terminal modes.
Version 1.3.2:
Tim Eliseo contributed a number of improvements to vi mode,
including a fuller set of vi key-bindings, implementation of the vi
constraint that the cursor can't backup past the point at which
input mode was entered, and restoration of overwritten characters
when backspacing in overwrite mode. There are also now new bindings
to allow users to toggle between vi and emacs modes interactively.
The terminal bell is now used in some circumstances, such as when an
unrecognized key sequence is entered. This can be turned off by the
new nobeep option in the tecla configuration file.
Unrelated to the above, a problem under Linux which prevented ^Q
from being used to resume terminal output after the user had pressed
^S, has been fixed.
Version 1.3.1:
In vi mode a bug was preventing the history-search-backward and
history-search-forward actions from doing anything when invoked on
empty lines. On empty lines they now act like up-history and
down-history respectively, as in emacs mode.
When creating shared libraries under Linux, the -soname directive
was being used incorrectly. The result is that Linux binaries linked
with the 1.2.3, 1.2.4 and 1.3.0 versions of the tecla shared
libraries, will refuse to see other versions of the shared library
until relinked with version 1.3.1 or higher.
The configure script can now handle the fact that under Solaris-2.6
and earlier, the only curses library is a static one that hides in
/usr/ccs/lib. Under Linux it now also caters for old versions of GNU
ld which don't accept version scripts.
The demos are now linked against the shared version of the library
if possible. Previously they were always linked with the static
version.
Version 1.3.0:
The major change in this release is the addition of an optional vi
command-line editing mode in gl_get_line(), along with lots of new
action functions to support its bindings. To enable this, first
create a ~/.teclarc file if you don't already have one, then add the
following line to it.
edit-mode vi
The default vi bindings, which are designed to mimic those of the vi
editor as closely as possible, are described in the gl_get_line(3)
man page.
A new convenience function called ef_list_expansions() has been
added for listing filename expansions. See the ef_list_expansions(3)
man page for details. This is used in a new list-glob binding, bound
to ^Xg in emacs mode, and ^G in vi input mode.
A bug has been fixed in the key-binding table expansion code. This
bug would have caused problems to anybody who defined more than
about 18 personalized key-bindings in their ~/.teclarc file.
Version 1.2.4:
Buffered I/O is now used for writing to terminals, and where
supported, cursor motion is done with move-n-positions terminfo
capabilities instead of doing lots of move-1-position requests. This
greatly improves how the library feels over slow links.
You can now optionally compile different architectures in different
directories, without having to make multiple copies of the
distribution. This is documented in the INSTALL file.
The ksh ~+ directive is now supported.
Thanks to Markus Gyger for the above improvements.
Documentation has been added to the INSTALL file describing features
designed to facilitate configuration and installation of the library
as part of larger packages. These features are intended to remove
the need to modify the tecla distribution's configuration and build
procedures when embedding the libtecla distribution in other package
distributions.
A previous fix to stop the cursor from warping when the last
character of the input line was in the last column of the terminal,
was only being used for the first terminal line of the input line.
It is now used for all subsequent lines as well, as originally
intended.
Version 1.2.3:
The installation procedure has been better automated with the
addition of an autoconf configure script. This means that installers
can now compile and install the library by typing:
./configure
make
make install
On all systems this makes at least the normal static version of the
tecla library. It also makes the reentrant version if reentrant
POSIX functions are detected. Under Solaris, Linux and HP-UX the
configuration script arranges for shared libraries to be compiled in
addition to the static libraries. It is hoped that installers will
return information about how to compile shared libraries on other
systems, for inclusion in future releases, and to this end, a new
PORTING guide has been provided.
The versioning number scheme has been changed. This release would
have been 1.2c, but instead will be refered to as 1.2.3. The
versioning scheme, based on conventions used by Sun Microsystems, is
described in configure.in.
The library was also tested under HP-UX, and this revealed two
serious bugs, both of which have now been fixed.
The first bug prevented the library from writing control codes to
terminals on big-endian machines, with the exception of those
running under Solaris. This was due to an int variable being used
where a char was needed.
The second bug had the symptom that on systems that don't use the
newline character as the control code for moving the cursor down a
line, a newline wasn't started when the user hit enter.
Version 1.2b:
Two more minor bug fixes:
Many terminals don't wrap the cursor to the next line when a
character is written to the rightmost terminal column. Instead, they
delay starting a new line until one more character is written, at
which point they move the cursor two positions. gl_get_line()
wasn't aware of this, so cursor repositionings just after writing
the last character of a column, caused it to erroneously go up a
line. This has now been remedied, using a method that should work
regardless of whether a terminal exhibits this behavior or not.
Some systems dynamically record the current terminal dimensions in
environment variables called LINES and COLUMNS. On such systems,
during the initial terminal setup, these values should override the
static values read from the terminal information databases, and now
do. Previously they were only used if the dimensions returned by
terminfo/termcap looked bogus.
Version 1.2a:
This minor release fixes the following two bugs:
The initial terminal size and subsequent changes thereto, weren't
being noticed by gl_get_line(). This was because the test for the
existence of TIOCWINSZ was erroneously placed before the inclusion
of termios.h. One of the results was that on input lines that
spanned more than one terminal line, the cursor occasionally jumped
unexpectedly to the previous terminal line.
On entering a line that wrapped over multiple terminal lines,
gl_get_line() simply output a carriage-return line-feed at the point
at which the user pressed return. Thus if one typed in such a line,
then moved back onto one of the earlier terminal lines before
hitting return, the cursor was left on a line containing part of the
line that had just been entered. This didn't do any harm, but it
looked a mess.
Version 1.2:
A new facility for looking up and completing filenames in UNIX-style
paths has now been added (eg. you can search for, or complete
commands using the UNIX PATH environment variable). See the
pca_lookup_file(3) man page.
The already existing filename completion callback can now be made
selective in what types of files it lists. See the
cpl_complete_word(3) man page.
Due to its potential to break applications when changed, the use of
the publically defined CplFileArgs structure to configure the
cpl_file_completions() callback is now deprecated. The definition
of this structure has been frozen, and its documentation has been
removed from the man pages. It will remain supported, but if you
have used it, you are recommended to switch to the new method, which
involves a new opaque configuration object, allocated via a provided
constructor function, configured via accessor functions, and
eventually deleted with a provided destructor function. The
cpl_file_completions() callback distinguishes which structure type
it has been sent by virtue of a code placed at the start of the new
structure by the constructor. It is assumed that no existing
applications set the boolean 'escaped' member of the CplFileArgs
structure to 4568. The new method is documented in the
cpl_complete_word(3) man page.
Version 1.1j
This was the initial public release on freshmeat.org.
yuma123_2.14/libtecla/ioutil.h 0000664 0001750 0001750 00000006327 14770023131 016370 0 ustar vladimir vladimir #ifndef ioutil_h
#define ioutil_h
/*
* Copyright (c) 2000, 2001, 2002, 2003, 2004 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.
*/
/*.......................................................................
* Callback functions of the following type can be registered to write
* to a terminal, when the default blocking writes to a local terminal
* aren't appropriate. In particular, if you don't want gl_get_line()
* to block, then this function should return before writing the
* specified number of characters if doing otherwise would involve
* waiting.
*
* Input:
* data void * The anonymous data pointer that was registered with
* this callback function.
* s const char * The string to be written. Beware that this string
* may not have a terminating '\0' character.
* n int The length of the prefix of s[] to attempt to
* write.
* Output:
* return int The number of characters written from s[]. This
* should normally be a number in the range 0 to n.
* To signal that an I/O error occurred, return -1.
*/
#define GL_WRITE_FN(fn) int (fn)(void *data, const char *s, int n)
typedef GL_WRITE_FN(GlWriteFn);
/*
* The following output callback function requires a (FILE *) callback
* data argument, and writes to this stream using the fwrite stdio
* function.
*/
GL_WRITE_FN(_io_write_stdio);
/*
* Left justify text within the bounds of the terminal adding optional
* indentation, prefixes and suffixes to each line if requested.
*/
int _io_display_text(GlWriteFn *write_fn, void *data, int indentation,
const char *prefix, const char *suffix, int fill_char,
int term_width, int start, const char *string);
#endif
yuma123_2.14/libtecla/Makefile.rules 0000664 0001750 0001750 00000013314 14770023131 017475 0 ustar vladimir vladimir default: $(OBJDIR) $(TARGETS) $(DEMOS) $(PROGRAMS)
#-----------------------------------------------------------------------
# You shouldn't need to change anything in this file.
#-----------------------------------------------------------------------
# Create the directory in which the object files will be created.
$(OBJDIR):
mkdir $(OBJDIR)
# Construct the compilation command.
COMPILE = $(CC) -c $(CFLAGS) -o $@
LIB_OBJECTS = $(OBJDIR)/getline.o $(OBJDIR)/keytab.o $(OBJDIR)/freelist.o \
$(OBJDIR)/strngmem.o $(OBJDIR)/hash.o $(OBJDIR)/history.o \
$(OBJDIR)/direader.o $(OBJDIR)/homedir.o $(OBJDIR)/pathutil.o \
$(OBJDIR)/expand.o $(OBJDIR)/stringrp.o $(OBJDIR)/cplfile.o \
$(OBJDIR)/cplmatch.o $(OBJDIR)/pcache.o $(OBJDIR)/version.o \
$(OBJDIR)/chrqueue.o $(OBJDIR)/ioutil.o $(OBJDIR)/errmsg.o
# List the available demonstration programs.
DEMO_PROGS = demo$(SUFFIX) demo2$(SUFFIX) demo3$(SUFFIX)
# List all of the programs that this makefile can build.
PROGS = $(DEMO_PROGS) enhance$(SUFFIX)
static: libtecla$(SUFFIX).a
libtecla$(SUFFIX).a: $(LIB_OBJECTS)
ar -ru $@ $(LIB_OBJECTS); \
$(RANLIB) $@; \
rm -f $(PROGS)
shared: libtecla$(SUFFIX)$(SHARED_EXT)
libtecla$(SUFFIX)$(SHARED_EXT): $(LIB_OBJECTS) $(srcdir)/libtecla.map \
libtecla.map.opt
$(LINK_SHARED)
@endings="$(SHARED_ALT)" ; \
for alt in $$endings ; do \
lnk="libtecla$(SUFFIX)$$alt"; \
echo "rm -f $$lnk; $(LN_S) $@ $$lnk"; \
rm -f $$lnk; $(LN_S) $@ $$lnk; \
done; \
rm -f $(PROGS)
libtecla.map.opt: $(srcdir)/libtecla.map
sed -n 's/^[ ]*\([_a-zA-Z0-9]*\)[ ]*;.*/+e \1/p' $? >$@
demos: $(DEMO_PROGS)
demo$(SUFFIX): $(OBJDIR)/demo.o
LD_RUN_PATH="$(LIBDIR):$$LD_RUN_PATH:`pwd`" $(CC) $(CFLAGS) -o $@ \
$(OBJDIR)/demo.o -L. -ltecla$(SUFFIX) $(LIBS)
demo2$(SUFFIX): $(OBJDIR)/demo2.o
LD_RUN_PATH="$(LIBDIR):$$LD_RUN_PATH:`pwd`" $(CC) $(CFLAGS) -o $@ \
$(OBJDIR)/demo2.o -L. -ltecla$(SUFFIX) $(LIBS)
demo3$(SUFFIX): $(OBJDIR)/demo3.o
LD_RUN_PATH="$(LIBDIR):$$LD_RUN_PATH:`pwd`" $(CC) $(CFLAGS) -o $@ \
$(OBJDIR)/demo3.o -L. -ltecla$(SUFFIX) $(LIBS)
enhance$(SUFFIX): $(OBJDIR)/enhance.o
LD_RUN_PATH="$(LIBDIR):$$LD_RUN_PATH:`pwd`" $(CC) $(CFLAGS) -o $@ \
$(OBJDIR)/enhance.o -L. -ltecla$(SUFFIX) $(LIBS)
#-----------------------------------------------------------------------
# Object file dependencies.
#-----------------------------------------------------------------------
$(OBJDIR)/getline.o: $(srcdir)/getline.c $(srcdir)/pathutil.h \
$(srcdir)/libtecla.h $(OBJDIR)/keytab.h $(srcdir)/history.h \
$(srcdir)/freelist.h $(srcdir)/stringrp.h $(srcdir)/getline.h \
$(srcdir)/ioutil.h $(srcdir)/chrqueue.h $(srcdir)/cplmatch.h \
$(srcdir)/expand.h $(srcdir)/errmsg.h
$(COMPILE) $(srcdir)/getline.c
$(OBJDIR)/keytab.o: $(srcdir)/keytab.c $(OBJDIR)/keytab.h \
$(srcdir)/strngmem.h $(srcdir)/getline.h $(srcdir)/errmsg.h \
$(srcdir)/hash.h
$(COMPILE) $(srcdir)/keytab.c
$(OBJDIR)/strngmem.o: $(srcdir)/strngmem.c $(srcdir)/strngmem.h \
$(srcdir)/freelist.h
$(COMPILE) $(srcdir)/strngmem.c
$(OBJDIR)/freelist.o: $(srcdir)/freelist.c $(srcdir)/freelist.h
$(COMPILE) $(srcdir)/freelist.c
$(OBJDIR)/hash.o: $(srcdir)/hash.c $(srcdir)/hash.h $(srcdir)/strngmem.h \
$(srcdir)/freelist.h
$(COMPILE) $(srcdir)/hash.c
$(OBJDIR)/history.o: $(srcdir)/history.c $(srcdir)/ioutil.h \
$(srcdir)/history.h $(srcdir)/freelist.h $(srcdir)/errmsg.h
$(COMPILE) $(srcdir)/history.c
$(OBJDIR)/expand.o: $(srcdir)/expand.c $(srcdir)/freelist.h \
$(srcdir)/direader.h $(srcdir)/pathutil.h $(srcdir)/homedir.h \
$(srcdir)/stringrp.h $(srcdir)/libtecla.h $(srcdir)/ioutil.h \
$(srcdir)/expand.h $(srcdir)/errmsg.h
$(COMPILE) $(srcdir)/expand.c
$(OBJDIR)/direader.o: $(srcdir)/direader.c $(srcdir)/direader.h \
$(srcdir)/errmsg.h
$(COMPILE) $(srcdir)/direader.c
$(OBJDIR)/homedir.o: $(srcdir)/homedir.c $(srcdir)/pathutil.h \
$(srcdir)/homedir.h $(srcdir)/errmsg.h
$(COMPILE) $(srcdir)/homedir.c
$(OBJDIR)/pathutil.o: $(srcdir)/pathutil.c $(srcdir)/pathutil.h
$(COMPILE) $(srcdir)/pathutil.c
$(OBJDIR)/stringrp.o: $(srcdir)/stringrp.c $(srcdir)/freelist.h \
$(srcdir)/stringrp.h
$(COMPILE) $(srcdir)/stringrp.c
$(OBJDIR)/cplfile.o: $(srcdir)/cplfile.c $(srcdir)/libtecla.h \
$(srcdir)/direader.h $(srcdir)/homedir.h $(srcdir)/pathutil.h \
$(srcdir)/cplfile.h $(srcdir)/errmsg.h
$(COMPILE) $(srcdir)/cplfile.c
$(OBJDIR)/cplmatch.o: $(srcdir)/cplmatch.c $(srcdir)/libtecla.h \
$(srcdir)/ioutil.h $(srcdir)/stringrp.h $(srcdir)/pathutil.h \
$(srcdir)/cplfile.h $(srcdir)/cplmatch.h $(srcdir)/errmsg.h
$(COMPILE) $(srcdir)/cplmatch.c
$(OBJDIR)/pcache.o: $(srcdir)/pcache.c $(srcdir)/libtecla.h \
$(srcdir)/pathutil.h $(srcdir)/homedir.h $(srcdir)/freelist.h \
$(srcdir)/direader.h $(srcdir)/stringrp.h $(errmsg.h)
$(COMPILE) $(srcdir)/pcache.c
$(OBJDIR)/demo.o: $(srcdir)/demo.c $(srcdir)/libtecla.h
$(COMPILE) $(srcdir)/demo.c
$(OBJDIR)/demo2.o: $(srcdir)/demo2.c $(srcdir)/libtecla.h
$(COMPILE) $(srcdir)/demo2.c
$(OBJDIR)/demo3.o: $(srcdir)/demo3.c $(srcdir)/libtecla.h
$(COMPILE) $(srcdir)/demo3.c
$(OBJDIR)/version.o: $(srcdir)/version.c $(srcdir)/libtecla.h
$(COMPILE) $(srcdir)/version.c
$(OBJDIR)/enhance.o: $(srcdir)/enhance.c $(srcdir)/libtecla.h
$(COMPILE) $(srcdir)/enhance.c
$(OBJDIR)/chrqueue.o: $(srcdir)/chrqueue.c $(srcdir)/ioutil.h \
$(srcdir)/chrqueue.h $(srcdir)/freelist.h $(srcdir)/errmsg.h
$(COMPILE) $(srcdir)/chrqueue.c
$(OBJDIR)/ioutil.o: $(srcdir)/ioutil.c $(srcdir)/ioutil.h
$(COMPILE) $(srcdir)/ioutil.c
$(OBJDIR)/errmsg.o: $(srcdir)/errmsg.c $(srcdir)/errmsg.h
$(COMPILE) $(srcdir)/errmsg.c
#-----------------------------------------------------------------------
# Include file dependencies.
#-----------------------------------------------------------------------
$(OBJDIR)/keytab.h: $(srcdir)/keytab.h $(srcdir)/libtecla.h
cp $(srcdir)/keytab.h $@
yuma123_2.14/libtecla/freelist.c 0000664 0001750 0001750 00000027523 14770023131 016674 0 ustar vladimir vladimir /*
* Copyright (c) 2000, 2001, 2002, 2003, 2004 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 "freelist.h"
typedef struct FreeListBlock FreeListBlock;
struct FreeListBlock {
FreeListBlock *next; /* The next block in the list */
char *nodes; /* The array of free-list nodes */
};
struct FreeList {
size_t node_size; /* The size of a free-list node */
unsigned blocking_factor; /* The number of nodes per block */
long nbusy; /* The number of nodes that are in use */
long ntotal; /* The total number of nodes in the free list */
FreeListBlock *block; /* The head of the list of free-list blocks */
void *free_list; /* The free-list of nodes */
};
static FreeListBlock *_new_FreeListBlock(FreeList *fl);
static FreeListBlock *_del_FreeListBlock(FreeListBlock *fl);
static void _thread_FreeListBlock(FreeList *fl, FreeListBlock *block);
/*.......................................................................
* Allocate a new free-list from blocks of 'blocking_factor' objects of size
* node_size.
*
* Input:
* node_size size_t The size of the free-list nodes to be returned
* by _new_FreeListNode(). Use sizeof() to
* determine this.
* blocking_factor unsigned The number of objects of size 'object_size'
* to allocate per block.
* Output:
* return FreeList * The new freelist, or NULL on error.
*/
FreeList *_new_FreeList(size_t node_size, unsigned blocking_factor)
{
FreeList *fl; /* The new free-list container */
/*
* When a free-list node is on the free-list, it is used as a (void *)
* link field. Roundup node_size to a mulitple of the size of a void
* pointer. This, plus the fact that the array of nodes is obtained via
* malloc, which returns memory suitably aligned for any object, will
* ensure that the first sizeof(void *) bytes of each node will be
* suitably aligned to use as a (void *) link pointer.
*/
node_size = sizeof(void *) *
((node_size + sizeof(void *) - 1) / sizeof(void *));
/*
* Enfore a minimum block size.
*/
if(blocking_factor < 1)
blocking_factor = 1;
/*
* Allocate the container of the free list.
*/
fl = (FreeList *) malloc(sizeof(FreeList));
if(!fl) {
errno = ENOMEM;
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_FreeList().
*/
fl->node_size = node_size;
fl->blocking_factor = blocking_factor;
fl->nbusy = 0;
fl->ntotal = 0;
fl->block = NULL;
fl->free_list = NULL;
/*
* Allocate the first block of memory.
*/
fl->block = _new_FreeListBlock(fl);
if(!fl->block) {
errno = ENOMEM;
return _del_FreeList(fl, 1);
};
/*
* Add the new list of nodes to the free-list.
*/
fl->free_list = fl->block->nodes;
/*
* Return the free-list for use.
*/
return fl;
}
/*.......................................................................
* Re-thread a freelist to reclaim all allocated nodes.
* This function should not be called unless if it is known that none
* of the currently allocated nodes are still being used.
*
* Input:
* fl FreeList * The free-list to be reset, or NULL.
*/
void _rst_FreeList(FreeList *fl)
{
if(fl) {
FreeListBlock *block;
/*
* Re-thread the nodes of each block into individual free-lists.
*/
for(block=fl->block; block; block=block->next)
_thread_FreeListBlock(fl, block);
/*
* Link all of the block freelists into one large freelist.
*/
fl->free_list = NULL;
for(block=fl->block; block; block=block->next) {
/*
* Locate the last node of the current block.
*/
char *last_node = block->nodes + fl->node_size *
(fl->blocking_factor - 1);
/*
* Make the link-field of the last node point to the first
* node of the current freelist, then make the first node of the
* new block the start of the freelist.
*/
*(void **)last_node = fl->free_list;
fl->free_list = block->nodes;
};
/*
* All allocated nodes have now been returned to the freelist.
*/
fl->nbusy = 0;
};
}
/*.......................................................................
* Delete a free-list.
*
* Input:
* fl FreeList * The free-list to be deleted, or NULL.
* force int If force==0 then _del_FreeList() will complain
* and refuse to delete the free-list if any
* of nodes have not been returned to the free-list.
* If force!=0 then _del_FreeList() will not check
* whether any nodes are still in use and will
* always delete the list.
* Output:
* return FreeList * Always NULL (even if the list couldn't be
* deleted).
*/
FreeList *_del_FreeList(FreeList *fl, int force)
{
if(fl) {
/*
* Check whether any nodes are in use.
*/
if(!force && _busy_FreeListNodes(fl) != 0) {
errno = EBUSY;
return NULL;
};
/*
* Delete the list blocks.
*/
{
FreeListBlock *next = fl->block;
while(next) {
FreeListBlock *block = next;
next = block->next;
block = _del_FreeListBlock(block);
};
};
fl->block = NULL;
fl->free_list = NULL;
/*
* Discard the container.
*/
free(fl);
};
return NULL;
}
/*.......................................................................
* Allocate a new object from a free-list.
*
* Input:
* fl FreeList * The free-list to return an object from.
* Output:
* return void * A new object of the size that was specified via
* the node_size argument of _new_FreeList() when
* the free-list was created, or NULL if there
* is insufficient memory, or 'fl' is NULL.
*/
void *_new_FreeListNode(FreeList *fl)
{
void *node; /* The node to be returned */
/*
* Check arguments.
*/
if(!fl)
return NULL;
/*
* If the free-list has been exhausted extend it by allocating
* another block of nodes.
*/
if(!fl->free_list) {
FreeListBlock *block = _new_FreeListBlock(fl);
if(!block)
return NULL;
/*
* Prepend the new block to the list of free-list blocks.
*/
block->next = fl->block;
fl->block = block;
/*
* Add the new list of nodes to the free-list.
*/
fl->free_list = fl->block->nodes;
};
/*
* Remove and return a node from the front of the free list.
*/
node = fl->free_list;
fl->free_list = *(void **)node;
/*
* Record the loss of a node from the free-list.
*/
fl->nbusy++;
/*
* Return the node.
*/
return node;
}
/*.......................................................................
* Return an object to the free-list that it was allocated from.
*
* Input:
* fl FreeList * The free-list from which the object was taken.
* object void * The node to be returned.
* Output:
* return void * Always NULL.
*/
void *_del_FreeListNode(FreeList *fl, void *object)
{
/*
* Check arguments.
*/
if(!fl)
return NULL;
/*
* Return the node to the head of the free list.
*/
if(object) {
*(void **)object = fl->free_list;
fl->free_list = object;
/*
* Record the return of the node to the free-list.
*/
fl->nbusy--;
};
return NULL;
}
/*.......................................................................
* Return a count of the number of nodes that are currently allocated.
*
* Input:
* fl FreeList * The list to count wrt, or NULL.
* Output:
* return long The number of nodes (or 0 if fl==NULL).
*/
long _busy_FreeListNodes(FreeList *fl)
{
return fl ? fl->nbusy : 0;
}
/*.......................................................................
* Query the number of allocated nodes in the freelist which are
* currently unused.
*
* Input:
* fl FreeList * The list to count wrt, or NULL.
* Output:
* return long The number of unused nodes (or 0 if fl==NULL).
*/
long _idle_FreeListNodes(FreeList *fl)
{
return fl ? (fl->ntotal - fl->nbusy) : 0;
}
/*.......................................................................
* Allocate a new list of free-list nodes. On return the nodes will
* be linked together as a list starting with the node at the lowest
* address and ending with a NULL next pointer.
*
* Input:
* fl FreeList * The free-list to allocate the list for.
* Output:
* return FreeListBlock * The new linked block of free-list nodes,
* or NULL on error.
*/
static FreeListBlock *_new_FreeListBlock(FreeList *fl)
{
FreeListBlock *block; /* The new block to be returned */
/*
* Allocate the container.
*/
block = (FreeListBlock *) malloc(sizeof(FreeListBlock));
if(!block)
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_FreeListBlock().
*/
block->next = NULL;
block->nodes = NULL;
/*
* Allocate the block of nodes.
*/
block->nodes = (char *) malloc(fl->node_size * fl->blocking_factor);
if(!block->nodes)
return _del_FreeListBlock(block);
/*
* Initialize the block as a linked list of FreeListNode's.
*/
_thread_FreeListBlock(fl, block);
/*
* Update the record of the number of nodes in the freelist.
*/
fl->ntotal += fl->blocking_factor;
return block;
}
/*.......................................................................
* Link each node of a freelist block to the node that follows it.
*
* Input:
* fl FreeList * The freelist that contains the block.
* block FreeListBlock * The block to be threaded.
*/
static void _thread_FreeListBlock(FreeList *fl, FreeListBlock *block)
{
char *mem = block->nodes;
int i;
for(i=0; iblocking_factor - 1; i++, mem += fl->node_size)
*(void **)mem = mem + fl->node_size; /* Link to the next node */
*(void **)mem = NULL; /* Terminate the list */
}
/*.......................................................................
* Delete a free-list block.
*
* Input:
* fl FreeListBlock * The block to be deleted, or NULL.
* Output:
* return FreeListBlock * Always NULL.
*/
static FreeListBlock *_del_FreeListBlock(FreeListBlock *fl)
{
if(fl) {
fl->next = NULL;
if(fl->nodes)
free(fl->nodes);
fl->nodes = NULL;
free(fl);
};
return NULL;
}
yuma123_2.14/libtecla/ioutil.c 0000664 0001750 0001750 00000030260 14770023131 016354 0 ustar vladimir vladimir /*
* Copyright (c) 2000, 2001, 2002, 2003, 2004 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 "ioutil.h"
static int _io_pad_line(GlWriteFn *write_fn, void *data, int c, int n);
/*.......................................................................
* Display a left-justified string over multiple terminal lines,
* taking account of the specified width of the terminal. Optional
* indentation and an option prefix string can be specified to be
* displayed at the start of each new terminal line used, and if
* needed, a single paragraph can be broken across multiple calls.
* Note that literal newlines in the input string can be used to force
* a newline at any point, and that in order to allow individual
* paragraphs to be written using multiple calls to this function,
* unless an explicit newline character is specified at the end of the
* string, a newline will not be started at the end of the last word
* in the string. Note that when a new line is started between two
* words that are separated by spaces, those spaces are not output,
* whereas when a new line is started because a newline character was
* found in the string, only the spaces before the newline character
* are discarded.
*
* Input:
* write_fn GlWriteFn * The callback function to use to write the
* output.
* data void * A pointer to arbitrary data to be passed to
* write_fn() whenever it is called.
* fp FILE * The stdio stream to write to.
* indentation int The number of fill characters to use to
* indent the start of each new terminal line.
* prefix const char * An optional prefix string to write after the
* indentation margin at the start of each new
* terminal line. You can specify NULL if no
* prefix is required.
* suffix const char * An optional suffix string to draw at the end
* of the terminal line. The line will be padded
* where necessary to ensure that the suffix ends
* in the last column of the terminal line. If
* no suffix is desired, specify NULL.
* fill_char int The padding character to use when indenting
* and filling up to the suffix.
* term_width int The width of the terminal being written to.
* start int The number of characters already written to
* the start of the current terminal line. This
* is primarily used to allow individual
* paragraphs to be written over multiple calls
* to this function, but can also be used to
* allow you to start the first line of a
* paragraph with a different prefix or
* indentation than those specified above.
* string const char * The string to be written.
* Output:
* return int On error -1 is returned. Otherwise the
* return value is the terminal column index at
* which the cursor was left after writing the
* final word in the string. Successful return
* values can thus be passed verbatim to the
* 'start' arguments of subsequent calls to
* _io_display_text() to allow the printing of a
* paragraph to be broken across multiple calls
* to _io_display_text().
*/
int _io_display_text(GlWriteFn *write_fn, void *data, int indentation,
const char *prefix, const char *suffix, int fill_char,
int term_width, int start, const char *string)
{
int ndone; /* The number of characters written from string[] */
int nnew; /* The number of characters to be displayed next */
int was_space; /* True if the previous character was a space or tab */
int last = start; /* The column number of the last character written */
int prefix_len; /* The length of the optional line prefix string */
int suffix_len; /* The length of the optional line prefix string */
int margin_width; /* The total number of columns used by the indentation */
/* margin and the prefix string. */
int i;
/*
* Check the arguments?
*/
if(!string || !write_fn) {
errno = EINVAL;
return -1;
};
/*
* Enforce sensible values on the arguments.
*/
if(term_width < 0)
term_width = 0;
if(indentation > term_width)
indentation = term_width;
else if(indentation < 0)
indentation = 0;
if(start > term_width)
start = term_width;
else if(start < 0)
start = 0;
/*
* Get the length of the prefix string.
*/
prefix_len = prefix ? strlen(prefix) : 0;
/*
* Get the length of the suffix string.
*/
suffix_len = suffix ? strlen(suffix) : 0;
/*
* How many characters are devoted to indenting and prefixing each line?
*/
margin_width = indentation + prefix_len;
/*
* Write as many terminal lines as are needed to display the whole string.
*/
for(ndone=0; string[ndone]; start=0) {
last = start;
/*
* Write spaces from the current position in the terminal line to the
* width of the requested indentation margin.
*/
if(indentation > 0 && last < indentation) {
if(_io_pad_line(write_fn, data, fill_char, indentation - last))
return -1;
last = indentation;
};
/*
* If a prefix string has been specified, display it unless we have
* passed where it should end in the terminal output line.
*/
if(prefix_len > 0 && last < margin_width) {
int pstart = last - indentation;
int plen = prefix_len - pstart;
if(write_fn(data, prefix+pstart, plen) != plen)
return -1;
last = margin_width;
};
/*
* Locate the end of the last complete word in the string before
* (term_width - start) characters have been seen. To handle the case
* where a single word is wider than the available space after the
* indentation and prefix margins, always make sure that at least one
* word is printed after the margin, regardless of whether it won't
* fit on the line. The two exceptions to this rule are if an embedded
* newline is found in the string or the end of the string is reached
* before any word has been seen.
*/
nnew = 0;
was_space = 0;
for(i=ndone; string[i] && (last+i-ndone < term_width - suffix_len ||
(nnew==0 && last==margin_width)); i++) {
if(string[i] == '\n') {
if(!was_space)
nnew = i-ndone;
break;
} else if(isspace((int) string[i])) {
if(!was_space) {
nnew = i-ndone+1;
was_space = 1;
};
} else {
was_space = 0;
};
};
/*
* Does the end of the string delimit the last word that will fit on the
* output line?
*/
if(nnew==0 && string[i] == '\0')
nnew = i-ndone;
/*
* Write the new line.
*/
if(write_fn(data, string+ndone, nnew) != nnew)
return -1;
ndone += nnew;
last += nnew;
/*
* Start a newline unless we have reached the end of the input string.
* In the latter case, in order to give the caller the chance to
* concatenate multiple calls to _io_display_text(), omit the newline,
* leaving it up to the caller to write this.
*/
if(string[ndone] != '\0') {
/*
* If a suffix has been provided, pad out the end of the line with spaces
* such that the suffix will end in the right-most terminal column.
*/
if(suffix_len > 0) {
int npad = term_width - suffix_len - last;
if(npad > 0 && _io_pad_line(write_fn, data, fill_char, npad))
return -1;
last += npad;
if(write_fn(data, suffix, suffix_len) != suffix_len)
return -1;
last += suffix_len;
};
/*
* Start a new line.
*/
if(write_fn(data, "\n", 1) != 1)
return -1;
/*
* Skip any spaces and tabs that follow the last word that was written.
*/
while(string[ndone] && isspace((int)string[ndone]) &&
string[ndone] != '\n')
ndone++;
/*
* If the terminating character was a literal newline character,
* skip it in the input string, since we just wrote it.
*/
if(string[ndone] == '\n')
ndone++;
last = 0;
};
};
/*
* Return the column number of the last character printed.
*/
return last;
}
/*.......................................................................
* Write a given number of spaces to the specified stdio output string.
*
* Input:
* write_fn GlWriteFn * The callback function to use to write the
* output.
* data void * A pointer to arbitrary data to be passed to
* write_fn() whenever it is called.
* c int The padding character.
* n int The number of spaces to be written.
* Output:
* return int 0 - OK.
* 1 - Error.
*/
static int _io_pad_line(GlWriteFn *write_fn, void *data, int c, int n)
{
enum {FILL_SIZE=20};
char fill[FILL_SIZE+1];
/*
* Fill the buffer with the specified padding character.
*/
memset(fill, c, FILL_SIZE);
fill[FILL_SIZE] = '\0';
/*
* Write the spaces using the above literal string of spaces as
* many times as needed to output the requested number of spaces.
*/
while(n > 0) {
int nnew = n <= FILL_SIZE ? n : FILL_SIZE;
if(write_fn(data, fill, nnew) != nnew)
return 1;
n -= nnew;
};
return 0;
}
/*.......................................................................
* The following is an output callback function which uses fwrite()
* to write to the stdio stream specified via its callback data argument.
*
* Input:
* data void * The stdio stream to write to, specified via a
* (FILE *) pointer cast to (void *).
* s const char * The string to be written.
* n int The length of the prefix of s[] to attempt to
* write.
* Output:
* return int The number of characters written from s[]. This
* should normally be a number in the range 0 to n.
* To signal that an I/O error occurred, return -1.
*/
GL_WRITE_FN(_io_write_stdio)
{
int ndone; /* The total number of characters written */
int nnew; /* The number of characters written in the latest write */
/*
* The callback data is the stdio stream to write to.
*/
FILE *fp = (FILE *) data;
/*
* Because of signals we may need to do more than one write to output
* the whole string.
*/
for(ndone=0; ndone