This commit was generated by cvs2svn to track changes on a CVS vendor
[external/binutils.git] / readline / tilde.c
1 /* tilde.c -- Tilde expansion code (~/foo := $HOME/foo). */
2
3 /* Copyright (C) 1988,1989 Free Software Foundation, Inc.
4
5    This file is part of GNU Readline, a library for reading lines
6    of text with interactive input and history editing.
7
8    Readline is free software; you can redistribute it and/or modify it
9    under the terms of the GNU General Public License as published by the
10    Free Software Foundation; either version 1, or (at your option) any
11    later version.
12
13    Readline is distributed in the hope that it will be useful, but
14    WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16    General Public License for more details.
17
18    You should have received a copy of the GNU General Public License
19    along with Readline; see the file COPYING.  If not, write to the Free
20    Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
21
22 #if defined (__GNUC__)
23 #  undef alloca
24 #  define alloca __builtin_alloca
25 #else /* !__GNUC__ */
26 #  if defined (_AIX)
27      #pragma alloca
28 #  else /* !_AIX */
29 #    if defined (HAVE_ALLOCA_H)
30 #      include <alloca.h>
31 #    endif /* HAVE_ALLOCA_H */
32 #  endif /* !AIX */
33 #endif /* !__GNUC__ */
34
35 #if defined (HAVE_STRING_H)
36 #  include <string.h>
37 #else /* !HAVE_STRING_H */
38 #  include <strings.h>
39 #endif /* !HAVE_STRING_H */  
40
41 #if defined (HAVE_STDLIB_H)
42 #  include <stdlib.h>
43 #else
44 #  include "ansi_stdlib.h"
45 #endif /* HAVE_STDLIB_H */
46
47 #include <tilde/tilde.h>
48 #include <pwd.h>
49
50 #if !defined (sgi) && !defined (isc386)
51 extern struct passwd *getpwnam (), *getpwuid ();
52 #endif /* !sgi */
53
54 #if !defined (savestring)
55 extern char *xmalloc ();
56 #  ifndef strcpy
57 extern char *strcpy ();
58 #  endif
59 #define savestring(x) strcpy (xmalloc (1 + strlen (x)), (x))
60 #endif /* !savestring */
61
62 #if !defined (NULL)
63 #  if defined (__STDC__)
64 #    define NULL ((void *) 0)
65 #  else
66 #    define NULL 0x0
67 #  endif /* !__STDC__ */
68 #endif /* !NULL */
69
70 #if defined (TEST) || defined (STATIC_MALLOC)
71 static char *xmalloc (), *xrealloc ();
72 #else
73 extern char *xmalloc (), *xrealloc ();
74 #endif /* TEST || STATIC_MALLOC */
75
76 /* The default value of tilde_additional_prefixes.  This is set to
77    whitespace preceding a tilde so that simple programs which do not
78    perform any word separation get desired behaviour. */
79 static char *default_prefixes[] =
80   { " ~", "\t~", (char *)NULL };
81
82 /* The default value of tilde_additional_suffixes.  This is set to
83    whitespace or newline so that simple programs which do not
84    perform any word separation get desired behaviour. */
85 static char *default_suffixes[] =
86   { " ", "\n", (char *)NULL };
87
88 /* If non-null, this contains the address of a function to call if the
89    standard meaning for expanding a tilde fails.  The function is called
90    with the text (sans tilde, as in "foo"), and returns a malloc()'ed string
91    which is the expansion, or a NULL pointer if there is no expansion. */
92 Function *tilde_expansion_failure_hook = (Function *)NULL;
93
94 /* When non-null, this is a NULL terminated array of strings which
95    are duplicates for a tilde prefix.  Bash uses this to expand
96    `=~' and `:~'. */
97 char **tilde_additional_prefixes = default_prefixes;
98
99 /* When non-null, this is a NULL terminated array of strings which match
100    the end of a username, instead of just "/".  Bash sets this to
101    `:' and `=~'. */
102 char **tilde_additional_suffixes = default_suffixes;
103
104 /* Find the start of a tilde expansion in STRING, and return the index of
105    the tilde which starts the expansion.  Place the length of the text
106    which identified this tilde starter in LEN, excluding the tilde itself. */
107 static int
108 tilde_find_prefix (string, len)
109      char *string;
110      int *len;
111 {
112   register int i, j, string_len;
113   register char **prefixes = tilde_additional_prefixes;
114
115   string_len = strlen (string);
116   *len = 0;
117
118   if (!*string || *string == '~')
119     return (0);
120
121   if (prefixes)
122     {
123       for (i = 0; i < string_len; i++)
124         {
125           for (j = 0; prefixes[j]; j++)
126             {
127               if (strncmp (string + i, prefixes[j], strlen (prefixes[j])) == 0)
128                 {
129                   *len = strlen (prefixes[j]) - 1;
130                   return (i + *len);
131                 }
132             }
133         }
134     }
135   return (string_len);
136 }
137
138 /* Find the end of a tilde expansion in STRING, and return the index of
139    the character which ends the tilde definition.  */
140 static int
141 tilde_find_suffix (string)
142      char *string;
143 {
144   register int i, j, string_len;
145   register char **suffixes = tilde_additional_suffixes;
146
147   string_len = strlen (string);
148
149   for (i = 0; i < string_len; i++)
150     {
151       if (string[i] == '/' || !string[i])
152         break;
153
154       for (j = 0; suffixes && suffixes[j]; j++)
155         {
156           if (strncmp (string + i, suffixes[j], strlen (suffixes[j])) == 0)
157             return (i);
158         }
159     }
160   return (i);
161 }
162
163 /* Return a new string which is the result of tilde expanding STRING. */
164 char *
165 tilde_expand (string)
166      char *string;
167 {
168   char *result, *tilde_expand_word ();
169   int result_size, result_index;
170
171   result_size = result_index = 0;
172   result = (char *)NULL;
173
174   /* Scan through STRING expanding tildes as we come to them. */
175   while (1)
176     {
177       register int start, end;
178       char *tilde_word, *expansion;
179       int len;
180
181       /* Make START point to the tilde which starts the expansion. */
182       start = tilde_find_prefix (string, &len);
183
184       /* Copy the skipped text into the result. */
185       if ((result_index + start + 1) > result_size)
186         result = (char *)xrealloc (result, 1 + (result_size += (start + 20)));
187
188       strncpy (result + result_index, string, start);
189       result_index += start;
190
191       /* Advance STRING to the starting tilde. */
192       string += start;
193
194       /* Make END be the index of one after the last character of the
195          username. */
196       end = tilde_find_suffix (string);
197
198       /* If both START and END are zero, we are all done. */
199       if (!start && !end)
200         break;
201
202       /* Expand the entire tilde word, and copy it into RESULT. */
203       tilde_word = (char *)xmalloc (1 + end);
204       strncpy (tilde_word, string, end);
205       tilde_word[end] = '\0';
206       string += end;
207
208       expansion = tilde_expand_word (tilde_word);
209       free (tilde_word);
210
211       len = strlen (expansion);
212       if ((result_index + len + 1) > result_size)
213         result = (char *)xrealloc (result, 1 + (result_size += (len + 20)));
214
215       strcpy (result + result_index, expansion);
216       result_index += len;
217       free (expansion);
218     }
219
220   result[result_index] = '\0';
221
222   return (result);
223 }
224
225 /* Do the work of tilde expansion on FILENAME.  FILENAME starts with a
226    tilde.  If there is no expansion, call tilde_expansion_failure_hook. */
227 char *
228 tilde_expand_word (filename)
229      char *filename;
230 {
231   char *dirname;
232
233   dirname = filename ? savestring (filename) : (char *)NULL;
234
235   if (dirname && *dirname == '~')
236     {
237       char *temp_name;
238       if (!dirname[1] || dirname[1] == '/')
239         {
240           /* Prepend $HOME to the rest of the string. */
241           char *temp_home = (char *)getenv ("HOME");
242
243           /* If there is no HOME variable, look up the directory in
244              the password database. */
245           if (!temp_home)
246             {
247               struct passwd *entry;
248
249               entry = getpwuid (getuid ());
250               if (entry)
251                 temp_home = entry->pw_dir;
252             }
253
254           temp_name = (char *)alloca (1 + strlen (&dirname[1])
255                                       + (temp_home ? strlen (temp_home) : 0));
256           temp_name[0] = '\0';
257           if (temp_home)
258             strcpy (temp_name, temp_home);
259           strcat (temp_name, &dirname[1]);
260           free (dirname);
261           dirname = savestring (temp_name);
262         }
263       else
264         {
265           struct passwd *user_entry;
266           char *username = (char *)alloca (257);
267           int i, c;
268
269           for (i = 1; c = dirname[i]; i++)
270             {
271               if (c == '/')
272                 break;
273               else
274                 username[i - 1] = c;
275             }
276           username[i - 1] = '\0';
277
278           if (!(user_entry = getpwnam (username)))
279             {
280               /* If the calling program has a special syntax for
281                  expanding tildes, and we couldn't find a standard
282                  expansion, then let them try. */
283               if (tilde_expansion_failure_hook)
284                 {
285                   char *expansion;
286
287                   expansion =
288                     (char *)(*tilde_expansion_failure_hook) (username);
289
290                   if (expansion)
291                     {
292                       temp_name = (char *)alloca (1 + strlen (expansion)
293                                                   + strlen (&dirname[i]));
294                       strcpy (temp_name, expansion);
295                       strcat (temp_name, &dirname[i]);
296                       free (expansion);
297                       goto return_name;
298                     }
299                 }
300               /* We shouldn't report errors. */
301             }
302           else
303             {
304               temp_name = (char *)alloca (1 + strlen (user_entry->pw_dir)
305                                           + strlen (&dirname[i]));
306               strcpy (temp_name, user_entry->pw_dir);
307               strcat (temp_name, &dirname[i]);
308             return_name:
309               free (dirname);
310               dirname = savestring (temp_name);
311             }
312             endpwent ();
313         }
314     }
315   return (dirname);
316 }
317
318 \f
319 #if defined (TEST)
320 #undef NULL
321 #include <stdio.h>
322
323 main (argc, argv)
324      int argc;
325      char **argv;
326 {
327   char *result, line[512];
328   int done = 0;
329
330   while (!done)
331     {
332       printf ("~expand: ");
333       fflush (stdout);
334
335       if (!gets (line))
336         strcpy (line, "done");
337
338       if ((strcmp (line, "done") == 0) ||
339           (strcmp (line, "quit") == 0) ||
340           (strcmp (line, "exit") == 0))
341         {
342           done = 1;
343           break;
344         }
345
346       result = tilde_expand (line);
347       printf ("  --> %s\n", result);
348       free (result);
349     }
350   exit (0);
351 }
352
353 static void memory_error_and_abort ();
354
355 static char *
356 xmalloc (bytes)
357      int bytes;
358 {
359   char *temp = (char *)malloc (bytes);
360
361   if (!temp)
362     memory_error_and_abort ();
363   return (temp);
364 }
365
366 static char *
367 xrealloc (pointer, bytes)
368      char *pointer;
369      int bytes;
370 {
371   char *temp;
372
373   if (!pointer)
374     temp = (char *)malloc (bytes);
375   else
376     temp = (char *)realloc (pointer, bytes);
377
378   if (!temp)
379     memory_error_and_abort ();
380
381   return (temp);
382 }
383
384 static void
385 memory_error_and_abort ()
386 {
387   fprintf (stderr, "readline: Out of virtual memory!\n");
388   abort ();
389 }
390
391 /*
392  * Local variables:
393  * compile-command: "gcc -g -DTEST -o tilde tilde.c"
394  * end:
395  */
396 #endif /* TEST */