revert between 56095 -> 55830 in arch
[AROS.git] / workbench / c / Search.c
blobe3359ecca093fb4722ad0ef7af1d5ae41a4a6d00
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 Printf("%s (dir)\n", &anchor->ap_Info.fib_FileName[0]);
286 if(args[ARG_ALL] || (anchor->ap_Flags & APF_DODIR))
288 anchor->ap_Flags |= APF_DODIR;
289 indent++;
290 print_names = TRUE;
293 else
295 indent--;
298 new_dir = TRUE;
299 anchor->ap_Flags &= ~APF_DIDDIR;
301 else
303 /* Deal with a file */
305 if(anchor->ap_Flags & APF_DirChanged)
306 new_dir = TRUE;
308 if(new_dir)
310 if(!(cut_off = GetDirName(anchor, path_buffer)))
311 success = FALSE;
313 new_dir = FALSE;
316 if(args[ARG_FILE])
318 found = MatchPatternNoCase(pattern,
319 (TEXT *)&(anchor->ap_Info.fib_FileName));
321 else
323 if(args[ARG_QUICK])
325 PrintFullName(path_buffer, cut_off, anchor);
326 WriteChars((STRPTR)control_codes, 3);
328 else if(!args[ARG_QUIET] && print_names)
330 WriteChars(spaces, MARGIN + INDENT*indent);
331 Printf("%s..\n", &anchor->ap_Info.fib_FileName[0]);
334 found = FindString(anchor, args, pattern, locale,
335 user_pattern);
338 if(found)
340 if((args[ARG_FILE] || args[ARG_QUIET]) &&
341 !args[ARG_QUICK])
343 PrintFullName(path_buffer, cut_off, anchor);
344 PutStr(new_line);
347 return_code = RETURN_OK;
351 error = MatchNext(anchor);
353 if(error && (error != ERROR_NO_MORE_ENTRIES))
355 success = FALSE;
356 SetIoErr(error);
361 /* Clear line for next shell prompt */
363 if(args[ARG_QUICK] && !args[ARG_FILE])
364 WriteChars((STRPTR)control_codes, 2);
366 MatchEnd(anchor);
368 if(success)
369 SetIoErr(0);
372 FreeArgs(read_args);
374 CloseLocale(locale);
376 else
378 SetIoErr(ERROR_NO_FREE_STORE);
381 /* Free memory */
383 if(anchor)
384 FreeMem(anchor, sizeof(struct AnchorPath));
385 if(spaces)
386 FreeMem(spaces, SPACES_SIZE);
387 if(path_buffer)
388 FreeMem(path_buffer, PATH_BUF_SIZE);
389 if(user_pattern)
390 FreeMem(user_pattern, pat_length + 5);
391 if(pattern)
392 FreeMem(pattern, pat_buf_length);
394 /* Check and reset signals */
396 if(SetSignal(0, -1) & SIGBREAKF_CTRL_C)
397 SetIoErr(ERROR_BREAK);
399 /* Exit */
401 if((error = IoErr()) != 0)
403 PrintFault(error, NULL);
404 return RETURN_FAIL;
407 return return_code;
412 VOID PrintFullName(TEXT *buffer, UWORD cut_off, struct AnchorPath *anchor)
414 buffer[cut_off] = '\0';
416 if(AddPart(buffer, (TEXT *)&(anchor->ap_Info.fib_FileName), PATH_BUF_SIZE))
418 PutStr(buffer);
421 return;
425 UWORD GetDirName(struct AnchorPath *anchor, TEXT *buffer)
427 if(NameFromLock(anchor->ap_Current->an_Lock, buffer, PATH_BUF_SIZE))
428 return strlen(buffer);
430 return 0;
434 BOOL FindString(struct AnchorPath *anchor, IPTR *args, TEXT *pattern,
435 struct Locale *locale, UBYTE *pi)
437 BOOL found = FALSE, end_early = FALSE, line_matches, at_end;
438 BPTR old_lock, file;
439 TEXT *p, *q, *r, *line, *buffer = NULL, ch;
440 ULONG max_line_length = 0, line_length, offset = 0, file_size, buf_size,
441 line_start = 0, line_count = 1, sigs, lines_to_show = 0;
442 LONG read_length = 1;
444 /* Move into the file's directory */
446 old_lock = CurrentDir(anchor->ap_Current->an_Lock);
448 /* Open the file for reading */
450 if((file = Open(anchor->ap_Info.fib_FileName, MODE_OLDFILE)) != BNULL)
452 /* Get a buffer for the file */
454 file_size = anchor->ap_Info.fib_Size;
455 buf_size = file_size + 1;
457 while(!buffer && buf_size)
459 if(!(buffer = AllocMem(buf_size, MEMF_ANY)))
460 buf_size >>= 1;
463 /* Check size of buffer */
465 if((buf_size <= file_size) && buffer)
467 /* Get length of longest line */
469 while(read_length > 0)
471 read_length = Read(file, buffer, buf_size - 1);
472 q = buffer + read_length;
474 if(!read_length)
475 q++;
477 for(p = buffer; p < q; p++)
479 if((*p=='\n')||!read_length)
481 line_length = offset + (p - buffer) - line_start;
483 if(line_length > max_line_length)
484 max_line_length = line_length;
486 line_start = offset + (p - buffer) + 1;
490 offset += read_length;
493 /* Ensure buffer is big enough for longest line */
495 if(buf_size <= max_line_length)
497 FreeMem(buffer, buf_size);
498 buf_size = max_line_length + 1;
499 buffer = AllocMem(buf_size, MEMF_ANY);
503 /* Test every line against the pattern */
505 if(buffer && pattern)
508 read_length = Seek(file, 0, OFFSET_BEGINNING) + 1;
510 while(((read_length = Read(file, buffer, buf_size - 1)) > 0) &&
511 !end_early)
513 q = buffer + read_length;
514 at_end = Seek(file, 0, OFFSET_CURRENT) == file_size;
516 if(at_end)
517 *(q++) = '\0';
519 line = buffer;
521 for(p = buffer; (p < q) && !end_early; p++)
523 ch = *p;
525 if((ch == '\n') || (ch == '\0'))
527 *p = '\0';
529 if (args[ARG_CASE])
531 if (args[ARG_PATTERN])
532 line_matches = MatchPattern(pattern, line);
533 else
534 line_matches = MatchString(pattern, line, p, pi, locale);
536 else
538 if(args[ARG_PATTERN])
539 line_matches = MatchPatternNoCase(pattern, line);
540 else
541 line_matches = MatchStringNoCase(pattern, line, p, pi, locale);
543 if(line_matches)
545 if(!found && args[ARG_QUICK])
546 PutStr(new_line);
548 found = TRUE;
550 if(args[ARG_QUIET])
552 end_early = TRUE;
554 else
556 if(!args[ARG_NONUM])
557 Printf("%6lu ", line_count);
559 /* Replace invisible characters with dots */
561 for(r = line; r < p; r++)
563 if(!IsPrint(locale, *r))
564 *r = '.';
567 Printf("%s\n", line);
568 if (args[ARG_LINES])
570 lines_to_show =
571 *((ULONG *) args[ARG_LINES]);
575 else
577 if (lines_to_show != 0)
579 Printf("%6lu: ", line_count);
581 /* Replace invisible characters with dots */
583 for (r = line; r < p; r++)
585 if (!IsPrint(locale, *r))
586 *r = '.';
588 PutStr(line);
589 PutStr("\n");
590 lines_to_show--;
593 line = p + 1;
595 if(ch == '\n')
596 line_count++;
598 sigs = SetSignal(0, SIGBREAKF_CTRL_D);
600 if(sigs & (SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_D))
602 end_early = TRUE;
604 if(sigs & SIGBREAKF_CTRL_D)
606 PutStr(abandon_msg);
612 /* Start reading again at start of most recent line */
614 if(!at_end)
615 Seek(file, line - q, OFFSET_CURRENT);
619 if(buffer)
620 FreeMem(buffer, buf_size);
622 Close(file);
625 CurrentDir(old_lock);
627 return found;
631 BOOL MatchStringNoCase(TEXT *string, TEXT *text, TEXT *text_end, UBYTE *pi,
632 struct Locale *locale)
634 TEXT *s, ch;
636 s = string;
638 while(text < text_end)
640 ch = ConvToUpper(locale, *(text++));
642 while((s != string) && (*s != ch))
643 s = string + pi[s - string - 1];
645 if(ch == *s)
646 s++;
648 if(!*s)
649 return TRUE;
652 return FALSE;
655 BOOL MatchString(TEXT *string, TEXT *text, TEXT *text_end, UBYTE *pi,
656 struct Locale *locale)
658 TEXT *s, ch;
660 s = string;
662 while (text < text_end)
664 ch = *(text++);
666 while ((s != string) && (*s != ch))
667 s = string + pi[s - string - 1];
669 if (ch == *s)
670 s++;
672 if (!*s)
673 return TRUE;
676 return FALSE;