Listtree.mcc: implement proxying of DisplayHook
[AROS.git] / workbench / c / Search.c
blobef69606bf68bbae138693a4fa19723a47d59c29c
1 /*
2 Copyright © 1995-2008, The AROS Development Team. All rights reserved.
3 $Id$
4 */
6 /*****************************************************************************
8 NAME
10 Search
12 SYNOPSIS
14 Search [FROM] {(name | pattern} [SEARCH] (string | pattern) [ALL]
15 [NONUM] [QUIET] [QUICK] [FILE] [PATTERN] [LINES=Number]
17 LOCATION
21 FUNCTION
23 Search looks through the files contained in the FROM directory for
24 a specified string (SEARCH); in case the ALL switch is specified,
25 the subdirectories of the FROM directory are also searched. The name
26 of all files containing the SEARCH string is displayed together with
27 the numbers of the lines where the string occurred.
28 If CTRL-C is pressed, the search will be abandoned. CTRL-D will
29 abandon searching the current file.
31 INPUTS
33 NONUM -- no line numbers are printed
34 QUIET -- don't display the name of the file being searched
35 QUICK -- more compact output
36 FILE -- look for a file with a specific name rather than a string
37 in a file
38 PATTERN -- use pattern matching when searching
39 CASE -- use case sensitive pattern matching when searching
40 LINES -- extra lines after a line match which should be shown
42 RESULT
44 If the object is found, the condition flag is set to 0. Otherwise it's
45 set to WARN.
47 NOTES
49 EXAMPLE
51 BUGS
53 SEE ALSO
55 INTERNALS
57 HISTORY
59 Author: Neil Cafferkey
60 Placed in the public domain by Neil Cafferkey.
61 Changes by: Johan 'S.Duvan' Alfredsson
63 ******************************************************************************/
65 #include <proto/exec.h>
66 #include <proto/dos.h>
67 #include <proto/locale.h>
68 #include <exec/memory.h>
69 #include <dos/dos.h>
70 #include <libraries/locale.h>
72 #include <string.h>
74 // ***** Layout and version parameters ***********
76 #define LOCALE_VERSION 38
77 #define PATH_BUF_SIZE 512
78 #define SPACES_SIZE (160 + 1)
79 #define MARGIN 3
80 #define INDENT 5
81 #define DIR_MARGIN 2
84 // ***** Command line arguments *******************
86 enum
88 ARG_FROM,
89 ARG_SEARCH,
90 ARG_ALL,
91 ARG_NONUM,
92 ARG_QUIET,
93 ARG_QUICK,
94 ARG_FILE,
95 ARG_PATTERN,
96 ARG_CASE,
97 ARG_LINES,
98 ARG_COUNT
101 int LocaleBase_version = LOCALE_VERSION;
103 // ***** Prototypes for internal functions *******
105 VOID PrintFullName(TEXT *buffer, UWORD cut_off, struct AnchorPath *anchor);
106 UWORD GetDirName(struct AnchorPath *anchor, TEXT *buffer);
107 BOOL FindString(struct AnchorPath *anchor, IPTR *args, TEXT *pattern,
108 struct Locale *locale, UBYTE *pi);
109 BOOL MatchStringNoCase(TEXT *string, TEXT *text, TEXT *text_end, UBYTE *pi,
110 struct Locale *locale);
111 BOOL MatchString(TEXT *string, TEXT *text, TEXT *text_end, UBYTE *pi,
112 struct Locale *locale);
115 // ***** String information (version, messages) ***
117 const TEXT template[] =
118 "FROM/M,SEARCH/A,ALL/S,NONUM/S,QUIET/S,QUICK/S,FILE/S,PATTERN/S,CASE/S,LINES/N";
119 const TEXT version_string[] = "$VER: Search 42.4 (6.4.2008)";
120 const TEXT locale_name[] = "locale.library";
122 const TEXT control_codes[] = { 0x9b, 'K', 13 };
123 const TEXT wild_card[] = { '#', '?'};
124 const TEXT new_line[] = "\n";
125 const TEXT abandon_msg[] = "** File abandoned\n";
127 const STRPTR default_from[] = {"", 0};
129 int __nocommandline;
131 int main(void)
133 IPTR args[ARG_COUNT] = {0};
134 struct RDArgs *read_args;
135 struct AnchorPath *anchor;
136 LONG error;
137 LONG return_code = RETURN_WARN;
138 TEXT *text, *spaces, *pattern = NULL, *path_buffer,
139 *user_pattern = NULL, *p, ch, **from;
140 BOOL found, success = TRUE, new_dir, print_names;
141 UWORD indent = 0, pat_buf_length = 0, cut_off = 0, pat_length = 0;
142 UBYTE k, q;
143 struct Locale *locale;
145 /* Allocate buffers */
147 spaces = AllocMem(SPACES_SIZE, MEMF_CLEAR);
148 anchor = AllocMem(sizeof(struct AnchorPath), MEMF_CLEAR);
149 path_buffer = AllocMem(PATH_BUF_SIZE,MEMF_ANY);
151 if(anchor && spaces && path_buffer)
153 locale = OpenLocale(NULL);
155 for(text = spaces + SPACES_SIZE - 1; text > spaces; *(--text) = ' ');
157 /* Parse arguments */
159 read_args = ReadArgs((STRPTR)template, args, NULL);
161 if(locale && read_args)
163 if ( ! args[ARG_FROM] )
165 /* /M ignores the default value, so we must set it after
166 ReadArgs() */
167 args[ARG_FROM] = (IPTR)default_from;
170 /* Prepare the pattern to be matched */
172 pat_length = strlen((TEXT *)args[ARG_SEARCH]);
173 pat_buf_length = pat_length * 2 + 3;
174 user_pattern = AllocMem(pat_length + 5, MEMF_CLEAR);
175 pattern = AllocMem(pat_buf_length, MEMF_ANY);
177 if(user_pattern && pattern)
179 if(args[ARG_PATTERN] || args[ARG_FILE])
181 if(args[ARG_FILE])
182 text = user_pattern;
183 else
185 text = user_pattern + 2;
186 CopyMem(wild_card, user_pattern, 2);
187 CopyMem(wild_card, text + pat_length, 2);
190 CopyMem((TEXT *)args[ARG_SEARCH], text, pat_length);
191 if (args[ARG_CASE])
193 if (ParsePattern(user_pattern, pattern, pat_buf_length) < 0)
195 success = FALSE;
198 else
200 if(ParsePatternNoCase(user_pattern, pattern,
201 pat_buf_length) < 0)
202 success = FALSE;
205 else
207 /* Copy the search string and convert it to uppercase */
209 text = pattern;
211 for(p = (TEXT *)args[ARG_SEARCH]; (ch = *p) != '\0'; p++)
212 *(text++) = ConvToUpper(locale, ch);
214 *text = '\0';
216 /* Construct prefix table for Knuth-Morris-Pratt
217 algorithm */
219 *user_pattern = 0;
220 k = 0;
222 for(q = 1; q < pat_length; q++)
224 while(k && (pattern[k] != pattern[q]))
225 k = user_pattern[k - 1];
227 if(pattern[k] == pattern[q])
228 k++;
230 user_pattern[q] = k;
234 else
235 success = FALSE;
237 /* Get the next starting point */
239 for(from = (TEXT **)args[ARG_FROM]; from && *from && success;
240 from++)
243 /* Initialise file search */
245 anchor->ap_BreakBits = SIGBREAKF_CTRL_C;
246 anchor->ap_FoundBreak = 0;
247 anchor->ap_Flags = 0;
248 error = MatchFirst(*from, anchor);
250 /* Work out if more than one file is being searched */
252 print_names = ((TEXT **)args[ARG_FROM])[1]
253 || (anchor->ap_Flags & APF_ITSWILD);
255 /* Enter sub-dir if the pattern was an explicitly named dir */
257 if(!(anchor->ap_Flags & APF_ITSWILD)
258 && (anchor->ap_Info.fib_DirEntryType > 0))
259 anchor->ap_Flags |= APF_DODIR;
261 /* Set flag to get name of starting directory */
263 new_dir = TRUE;
265 /* Traverse the directory */
267 while(!error && success)
269 found = FALSE;
271 if(anchor->ap_Info.fib_DirEntryType > 0)
273 /* Enter sub-dir if the ALL switch was supplied and
274 we're not on the way out of it */
276 if(!(anchor->ap_Flags & APF_DIDDIR))
278 if(!(args[ARG_FILE] || args[ARG_QUIET] ||
279 args[ARG_QUICK]))
281 WriteChars(spaces, MARGIN + INDENT * indent +
282 DIR_MARGIN);
283 text = (TEXT *)&(anchor->ap_Info.fib_FileName);
284 VPrintf("%s (dir)\n", (IPTR *)&text);
287 if(args[ARG_ALL] || (anchor->ap_Flags & APF_DODIR))
289 anchor->ap_Flags |= APF_DODIR;
290 indent++;
291 print_names = TRUE;
294 else
296 indent--;
299 new_dir = TRUE;
300 anchor->ap_Flags &= ~APF_DIDDIR;
302 else
304 /* Deal with a file */
306 if(anchor->ap_Flags & APF_DirChanged)
307 new_dir = TRUE;
309 if(new_dir)
311 if(!(cut_off = GetDirName(anchor, path_buffer)))
312 success = FALSE;
314 new_dir = FALSE;
317 if(args[ARG_FILE])
319 found = MatchPatternNoCase(pattern,
320 (TEXT *)&(anchor->ap_Info.fib_FileName));
322 else
324 if(args[ARG_QUICK])
326 PrintFullName(path_buffer, cut_off, anchor);
327 WriteChars((STRPTR)control_codes, 3);
329 else if(!args[ARG_QUIET] && print_names)
331 WriteChars(spaces, MARGIN + INDENT*indent);
332 text = (TEXT *)&(anchor->ap_Info.fib_FileName);
333 VPrintf("%s..\n", (IPTR *)&text);
336 found = FindString(anchor, args, pattern, locale,
337 user_pattern);
340 if(found)
342 if((args[ARG_FILE] || args[ARG_QUIET]) &&
343 !args[ARG_QUICK])
345 PrintFullName(path_buffer, cut_off, anchor);
346 PutStr(new_line);
349 return_code = RETURN_OK;
353 error = MatchNext(anchor);
355 if(error && (error != ERROR_NO_MORE_ENTRIES))
357 success = FALSE;
358 SetIoErr(error);
363 /* Clear line for next shell prompt */
365 if(args[ARG_QUICK] && !args[ARG_FILE])
366 WriteChars((STRPTR)control_codes, 2);
368 MatchEnd(anchor);
370 if(success)
371 SetIoErr(0);
374 FreeArgs(read_args);
376 CloseLocale(locale);
378 else
380 SetIoErr(ERROR_NO_FREE_STORE);
383 /* Free memory */
385 if(anchor)
386 FreeMem(anchor, sizeof(struct AnchorPath));
387 if(spaces)
388 FreeMem(spaces, SPACES_SIZE);
389 if(path_buffer)
390 FreeMem(path_buffer, PATH_BUF_SIZE);
391 if(user_pattern)
392 FreeMem(user_pattern, pat_length + 5);
393 if(pattern)
394 FreeMem(pattern, pat_buf_length);
396 /* Check and reset signals */
398 if(SetSignal(0, -1) & SIGBREAKF_CTRL_C)
399 SetIoErr(ERROR_BREAK);
401 /* Exit */
403 if((error = IoErr()) != 0)
405 PrintFault(error, NULL);
406 return RETURN_FAIL;
409 return return_code;
414 VOID PrintFullName(TEXT *buffer, UWORD cut_off, struct AnchorPath *anchor)
416 buffer[cut_off] = '\0';
418 if(AddPart(buffer, (TEXT *)&(anchor->ap_Info.fib_FileName), PATH_BUF_SIZE))
420 PutStr(buffer);
423 return;
427 UWORD GetDirName(struct AnchorPath *anchor, TEXT *buffer)
429 if(NameFromLock(anchor->ap_Current->an_Lock, buffer, PATH_BUF_SIZE))
430 return strlen(buffer);
432 return 0;
436 BOOL FindString(struct AnchorPath *anchor, IPTR *args, TEXT *pattern,
437 struct Locale *locale, UBYTE *pi)
439 BOOL found = FALSE, end_early = FALSE, line_matches, at_end;
440 BPTR old_lock, file;
441 TEXT *p, *q, *r, *line, *buffer = NULL, ch;
442 ULONG max_line_length = 0, line_length, offset = 0, file_size, buf_size,
443 line_start = 0, line_count = 1, sigs, lines_to_show = 0;
444 LONG read_length = 1;
446 /* Move into the file's directory */
448 old_lock = CurrentDir(anchor->ap_Current->an_Lock);
450 /* Open the file for reading */
452 if((file = Open(anchor->ap_Info.fib_FileName, MODE_OLDFILE)) != BNULL)
454 /* Get a buffer for the file */
456 file_size = anchor->ap_Info.fib_Size;
457 buf_size = file_size + 1;
459 while(!buffer && buf_size)
461 if(!(buffer = AllocMem(buf_size, MEMF_ANY)))
462 buf_size >>= 1;
465 /* Check size of buffer */
467 if((buf_size <= file_size) && buffer)
469 /* Get length of longest line */
471 while(read_length > 0)
473 read_length = Read(file, buffer, buf_size - 1);
474 q = buffer + read_length;
476 if(!read_length)
477 q++;
479 for(p = buffer; p < q; p++)
481 if((*p=='\n')||!read_length)
483 line_length = offset + (p - buffer) - line_start;
485 if(line_length > max_line_length)
486 max_line_length = line_length;
488 line_start = offset + (p - buffer) + 1;
492 offset += read_length;
495 /* Ensure buffer is big enough for longest line */
497 if(buf_size <= max_line_length)
499 FreeMem(buffer, buf_size);
500 buf_size = max_line_length + 1;
501 buffer = AllocMem(buf_size, MEMF_ANY);
505 /* Test every line against the pattern */
507 if(buffer && pattern)
510 read_length = Seek(file, 0, OFFSET_BEGINNING) + 1;
512 while(((read_length = Read(file, buffer, buf_size - 1)) > 0) &&
513 !end_early)
515 q = buffer + read_length;
516 at_end = Seek(file, 0, OFFSET_CURRENT) == file_size;
518 if(at_end)
519 *(q++) = '\0';
521 line = buffer;
523 for(p = buffer; (p < q) && !end_early; p++)
525 ch = *p;
527 if((ch == '\n') || (ch == '\0'))
529 *p = '\0';
531 if (args[ARG_CASE])
533 if (args[ARG_PATTERN])
534 line_matches = MatchPattern(pattern, line);
535 else
536 line_matches = MatchString(pattern, line, p, pi, locale);
538 else
540 if(args[ARG_PATTERN])
541 line_matches = MatchPatternNoCase(pattern, line);
542 else
543 line_matches = MatchStringNoCase(pattern, line, p, pi, locale);
545 if(line_matches)
547 if(!found && args[ARG_QUICK])
548 PutStr(new_line);
550 found = TRUE;
552 if(args[ARG_QUIET])
554 end_early = TRUE;
556 else
558 if(!args[ARG_NONUM])
559 VPrintf("%6lu ", (IPTR *)&line_count);
561 /* Replace invisible characters with dots */
563 for(r = line; r < p; r++)
565 if(!IsPrint(locale, *r))
566 *r = '.';
569 VPrintf("%s\n", (IPTR *)&line);
570 if (args[ARG_LINES])
572 lines_to_show =
573 *((ULONG *) args[ARG_LINES]);
577 else
579 if (lines_to_show != 0)
581 Printf("%6lu: ", line_count);
583 /* Replace invisible characters with dots */
585 for (r = line; r < p; r++)
587 if (!IsPrint(locale, *r))
588 *r = '.';
590 PutStr(line);
591 PutStr("\n");
592 lines_to_show--;
595 line = p + 1;
597 if(ch == '\n')
598 line_count++;
600 sigs = SetSignal(0, SIGBREAKF_CTRL_D);
602 if(sigs & (SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_D))
604 end_early = TRUE;
606 if(sigs & SIGBREAKF_CTRL_D)
608 PutStr(abandon_msg);
614 /* Start reading again at start of most recent line */
616 if(!at_end)
617 Seek(file, line - q, OFFSET_CURRENT);
621 if(buffer)
622 FreeMem(buffer, buf_size);
624 Close(file);
627 CurrentDir(old_lock);
629 return found;
633 BOOL MatchStringNoCase(TEXT *string, TEXT *text, TEXT *text_end, UBYTE *pi,
634 struct Locale *locale)
636 TEXT *s, ch;
638 s = string;
640 while(text < text_end)
642 ch = ConvToUpper(locale, *(text++));
644 while((s != string) && (*s != ch))
645 s = string + pi[s - string - 1];
647 if(ch == *s)
648 s++;
650 if(!*s)
651 return TRUE;
654 return FALSE;
657 BOOL MatchString(TEXT *string, TEXT *text, TEXT *text_end, UBYTE *pi,
658 struct Locale *locale)
660 TEXT *s, ch;
662 s = string;
664 while (text < text_end)
666 ch = *(text++);
668 while ((s != string) && (*s != ch))
669 s = string + pi[s - string - 1];
671 if (ch == *s)
672 s++;
674 if (!*s)
675 return TRUE;
678 return FALSE;