Problems are occurring downloading from downloads.sourceforge.net, so use
[tangerine.git] / workbench / c / Search.c
blob9d7461536aec3c4e381ffbdd22faf6d74b29db79
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, cut_off, pat_length;
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++)
241 // for(from = *(((TEXT ***)args) + ARG_FROM); from && *from && success;
242 // from++)
245 /* Initialise file search */
247 anchor->ap_BreakBits = SIGBREAKF_CTRL_C;
248 anchor->ap_FoundBreak = 0;
249 anchor->ap_Flags = 0;
250 error = MatchFirst(*from, anchor);
252 /* Work out if more than one file is being searched */
254 print_names = ((TEXT **)args[ARG_FROM])[1]
255 || (anchor->ap_Flags & APF_ITSWILD);
256 // print_names = (*(*(((TEXT ***)args) + ARG_FROM) + 1))
257 // || (anchor->ap_Flags & APF_ITSWILD);
259 /* Enter sub-dir if the pattern was an explicitly named dir */
261 if(!(anchor->ap_Flags & APF_ITSWILD)
262 && (anchor->ap_Info.fib_DirEntryType > 0))
263 anchor->ap_Flags |= APF_DODIR;
265 /* Set flag to get name of starting directory */
267 new_dir = TRUE;
269 /* Traverse the directory */
271 while(!error && success)
273 found = FALSE;
275 if(anchor->ap_Info.fib_DirEntryType > 0)
277 /* Enter sub-dir if the ALL switch was supplied and
278 we're not on the way out of it */
280 if(!(anchor->ap_Flags & APF_DIDDIR))
282 if(!(args[ARG_FILE] || args[ARG_QUIET] ||
283 args[ARG_QUICK]))
285 WriteChars(spaces, MARGIN + INDENT * indent +
286 DIR_MARGIN);
287 text = (TEXT *)&(anchor->ap_Info.fib_FileName);
288 VPrintf("%s (dir)\n", (IPTR *)&text);
291 if(args[ARG_ALL] || (anchor->ap_Flags & APF_DODIR))
293 anchor->ap_Flags |= APF_DODIR;
294 indent++;
295 print_names = TRUE;
298 else
300 indent--;
303 new_dir = TRUE;
304 anchor->ap_Flags &= ~APF_DIDDIR;
306 else
308 /* Deal with a file */
310 if(anchor->ap_Flags & APF_DirChanged)
311 new_dir = TRUE;
313 if(new_dir)
315 if(!(cut_off = GetDirName(anchor, path_buffer)))
316 success = FALSE;
318 new_dir = FALSE;
321 if(args[ARG_FILE])
323 found = MatchPatternNoCase(pattern,
324 (TEXT *)&(anchor->ap_Info.fib_FileName));
326 else
328 if(args[ARG_QUICK])
330 PrintFullName(path_buffer, cut_off, anchor);
331 WriteChars((STRPTR)control_codes, 3);
333 else if(!args[ARG_QUIET] && print_names)
335 WriteChars(spaces, MARGIN + INDENT*indent);
336 text = (TEXT *)&(anchor->ap_Info.fib_FileName);
337 VPrintf("%s..\n", (IPTR *)&text);
340 found = FindString(anchor, args, pattern, locale,
341 user_pattern);
344 if(found)
346 if((args[ARG_FILE] || args[ARG_QUIET]) &&
347 !args[ARG_QUICK])
349 PrintFullName(path_buffer, cut_off, anchor);
350 PutStr(new_line);
353 return_code = RETURN_OK;
357 error = MatchNext(anchor);
359 if(error && (error != ERROR_NO_MORE_ENTRIES))
361 success = FALSE;
362 SetIoErr(error);
367 /* Clear line for next shell prompt */
369 if(args[ARG_QUICK] && !args[ARG_FILE])
370 WriteChars((STRPTR)control_codes, 2);
372 MatchEnd(anchor);
374 if(success)
375 SetIoErr(0);
378 FreeArgs(read_args);
380 CloseLocale(locale);
382 else
384 SetIoErr(ERROR_NO_FREE_STORE);
387 /* Free memory */
389 if(anchor)
390 FreeMem(anchor, sizeof(struct AnchorPath));
391 if(spaces)
392 FreeMem(spaces, SPACES_SIZE);
393 if(path_buffer)
394 FreeMem(path_buffer, PATH_BUF_SIZE);
395 if(user_pattern)
396 FreeMem(user_pattern, pat_length + 5);
397 if(pattern)
398 FreeMem(pattern, pat_buf_length);
400 /* Check and reset signals */
402 if(SetSignal(0, -1) & SIGBREAKF_CTRL_C)
403 SetIoErr(ERROR_BREAK);
405 /* Exit */
407 if((error = IoErr()) != 0)
409 PrintFault(error, NULL);
410 return RETURN_FAIL;
413 return return_code;
418 VOID PrintFullName(TEXT *buffer, UWORD cut_off, struct AnchorPath *anchor)
420 buffer[cut_off] = '\0';
422 if(AddPart(buffer, (TEXT *)&(anchor->ap_Info.fib_FileName), PATH_BUF_SIZE))
424 PutStr(buffer);
427 return;
431 UWORD GetDirName(struct AnchorPath *anchor, TEXT *buffer)
433 if(NameFromLock(anchor->ap_Current->an_Lock, buffer, PATH_BUF_SIZE))
434 return strlen(buffer);
436 return 0;
440 BOOL FindString(struct AnchorPath *anchor, IPTR *args, TEXT *pattern,
441 struct Locale *locale, UBYTE *pi)
443 BOOL found = FALSE, end_early = FALSE, line_matches, at_end;
444 BPTR old_lock, file;
445 TEXT *p, *q, *r, *line, *buffer = NULL, ch;
446 ULONG max_line_length = 0, line_length, offset = 0, file_size, buf_size,
447 line_start = 0, line_count = 1, sigs, lines_to_show = 0;
448 LONG read_length = 1;
450 /* Move into the file's directory */
452 old_lock = CurrentDir(anchor->ap_Current->an_Lock);
454 /* Open the file for reading */
456 if((file = Open((TEXT *)&(anchor->ap_Info.fib_FileName), MODE_OLDFILE)) != NULL)
458 /* Get a buffer for the file */
460 file_size = anchor->ap_Info.fib_Size;
461 buf_size = file_size + 1;
463 while(!buffer && buf_size)
465 if(!(buffer = AllocMem(buf_size, MEMF_ANY)))
466 buf_size >>= 1;
469 /* Check size of buffer */
471 if((buf_size <= file_size) && buffer)
473 /* Get length of longest line */
475 while(read_length > 0)
477 read_length = Read(file, buffer, buf_size - 1);
478 q = buffer + read_length;
480 if(!read_length)
481 q++;
483 for(p = buffer; p < q; p++)
485 if((*p=='\n')||!read_length)
487 line_length = offset + (p - buffer) - line_start;
489 if(line_length > max_line_length)
490 max_line_length = line_length;
492 line_start = offset + (p - buffer) + 1;
496 offset += read_length;
499 /* Ensure buffer is big enough for longest line */
501 if(buf_size <= max_line_length)
503 FreeMem(buffer, buf_size);
504 buf_size = max_line_length + 1;
505 buffer = AllocMem(buf_size, MEMF_ANY);
509 /* Test every line against the pattern */
511 if(buffer && pattern)
514 read_length = Seek(file, 0, OFFSET_BEGINNING) + 1;
516 while(((read_length = Read(file, buffer, buf_size - 1)) > 0) &&
517 !end_early)
519 q = buffer + read_length;
520 at_end = Seek(file, 0, OFFSET_CURRENT) == file_size;
522 if(at_end)
523 *(q++) = '\0';
525 line = buffer;
527 for(p = buffer; (p < q) && !end_early; p++)
529 ch = *p;
531 if((ch == '\n') || (ch == '\0'))
533 *p = '\0';
535 if (args[ARG_CASE])
537 if (args[ARG_PATTERN])
538 line_matches = MatchPattern(pattern, line);
539 else
540 line_matches = MatchString(pattern, line, p, pi, locale);
542 else
544 if(args[ARG_PATTERN])
545 line_matches = MatchPatternNoCase(pattern, line);
546 else
547 line_matches = MatchStringNoCase(pattern, line, p, pi, locale);
549 if(line_matches)
551 if(!found && args[ARG_QUICK])
552 PutStr(new_line);
554 found = TRUE;
556 if(args[ARG_QUIET])
558 end_early = TRUE;
560 else
562 if(!args[ARG_NONUM])
563 VPrintf("%6lu ", (IPTR *)&line_count);
565 /* Replace invisible characters with dots */
567 for(r = line; r < p; r++)
569 if(!IsPrint(locale, *r))
570 *r = '.';
573 VPrintf("%s\n", (IPTR *)&line);
574 if (args[ARG_LINES])
576 lines_to_show =
577 *((ULONG *) args[ARG_LINES]);
581 else
583 if (lines_to_show != 0)
585 Printf("%6lu: ", line_count);
587 /* Replace invisible characters with dots */
589 for (r = line; r < p; r++)
591 if (!IsPrint(locale, *r))
592 *r = '.';
594 PutStr(line);
595 PutStr("\n");
596 lines_to_show--;
599 line = p + 1;
601 if(ch == '\n')
602 line_count++;
604 sigs = SetSignal(0, SIGBREAKF_CTRL_D);
606 if(sigs & (SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_D))
608 end_early = TRUE;
610 if(sigs & SIGBREAKF_CTRL_D)
612 PutStr(abandon_msg);
618 /* Start reading again at start of most recent line */
620 if(!at_end)
621 Seek(file, line - q, OFFSET_CURRENT);
625 if(buffer)
626 FreeMem(buffer, buf_size);
628 Close(file);
631 CurrentDir(old_lock);
633 return found;
637 BOOL MatchStringNoCase(TEXT *string, TEXT *text, TEXT *text_end, UBYTE *pi,
638 struct Locale *locale)
640 TEXT *s, ch;
642 s = string;
644 while(text < text_end)
646 ch = ConvToUpper(locale, *(text++));
648 while((s != string) && (*s != ch))
649 s = string + pi[s - string - 1];
651 if(ch == *s)
652 s++;
654 if(!*s)
655 return TRUE;
658 return FALSE;
661 BOOL MatchString(TEXT *string, TEXT *text, TEXT *text_end, UBYTE *pi,
662 struct Locale *locale)
664 TEXT *s, ch;
666 s = string;
668 while (text < text_end)
670 ch = *(text++);
672 while ((s != string) && (*s != ch))
673 s = string + pi[s - string - 1];
675 if (ch == *s)
676 s++;
678 if (!*s)
679 return TRUE;
682 return FALSE;