Imported Robodoc.
[robodoc.git] / Source / directory.c
1 /*
2 Copyright (C) 1994-2007  Frans Slothouber, Jacco van Weert, Petteri Kettunen,
3 Bernd Koesling, Thomas Aglassinger, Anthon Pang, Stefan Kost, David Druffner,
4 Sasha Vasko, Kai Hofmann, Thierry Pierron, Friedrich Haase, and Gergely Budai.
5
6 This file is part of ROBODoc
7
8 ROBODoc is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 3 of the License, or
11 (at your option) any later version.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program.  If not, see <http://www.gnu.org/licenses/>.
20
21 */
22
23 /****h* ROBODoc/Directory
24  * NAME
25  *   This module contains function to scan a directory tree and to
26  *   create a RB_Directory structure.  Most of the OS dependend parts
27  *   of ROBODoc can be found in this module..
28  *
29  *   This module currently works under:
30  *   o Cygwin   http://cygwin.com 
31  *   o Redhad 7.3 Linux
32  *   o VC++ under Windows NT 
33  *   o MINGW    http://www.mingw.org/ 
34  *   o OS/X
35  *
36  * AUTHOR 
37  *   Frans Slothouber
38  *****
39  * $Id: directory.c,v 1.41 2007/07/10 19:13:51 gumpu Exp $
40  */
41
42 #include <assert.h>
43 #include <stdlib.h>
44 #include "robodoc.h"
45 #include "directory.h"
46 #include "util.h"
47 #include "globals.h"
48 #include "roboconfig.h"
49
50 #if defined (RB_MSVC) || defined(__MINGW32__)
51
52 #  include <io.h>
53 #  include <sys/types.h>
54
55 #else
56
57 #  if defined _DIRENT_HAVE_D_TYPE
58      /* Empty */
59 #  else
60 #    include <sys/stat.h>
61 #  endif
62
63   /* no dirent in strict ansi !!! */
64 #  include <dirent.h>
65
66 #endif
67
68 #include <stdio.h>
69 #include <string.h>
70
71 #ifdef DMALLOC
72 #  include <dmalloc.h>
73 #endif
74
75
76 /****v* Directory/content_buffer
77  * FUNCTION
78  *   Temporary buffer file file content and filenames.
79  * SOURCE
80  */
81
82 #define RB_CBUFFERSIZE 8191
83 char                content_buffer[RB_CBUFFERSIZE + 1];
84
85 /*****/
86
87
88 /* Local functions */
89 static int          RB_Is_PathCharacter(
90     int c );
91
92
93 #if defined (RB_MSVC) || defined(__MINGW32__)
94 /* empty */
95 #else
96
97 /****f* Directory/RB_FileType
98  * FUNCTION
99  *   Determine the type of the file.  This function is used for all
100  *   compilers except VC++.
101  *
102  *   If _DIRENT_HAVE_D_TYPE is defined we can find the filetype
103  *   directly in the a_direntry.  If not we have to stat the file to
104  *   find out.
105  * SYNOPSIS
106  */
107 T_RB_FileType RB_FileType(
108     char *arg_pathname,
109     struct dirent *a_direntry )
110 /*
111  * INPUTS
112  *   o arg_pathname -- the path leading up to this file
113  *   o a_direntry -- a directory entry read by readdir()
114  * RESULT
115  *   The type of the file.
116  ******
117  */
118 {
119     T_RB_FileType       file_type = RB_FT_UNKNOWN;
120
121 #if defined _DIRENT_HAVE_D_TYPE
122     if ( a_direntry->d_type == DT_REG )
123     {
124         file_type = RB_FT_FILE;
125     }
126     else if ( a_direntry->d_type == DT_DIR )
127     {
128         file_type = RB_FT_DIRECTORY;
129     }
130     else
131     {
132         file_type = RB_FT_UNKNOWN;
133     }
134 #endif
135     if ( file_type == RB_FT_UNKNOWN )
136     {
137         char               *file_name = a_direntry->d_name;
138         struct stat         filestat;
139         int                 result;
140
141         /* Either we do not have d_type, or it gives
142          * no information, so we try it a different
143          * way. (BUG 715778)
144          */
145         content_buffer[0] = '\0';
146         strcat( content_buffer, arg_pathname );
147         if ( content_buffer[strlen( content_buffer ) - 1] != '/' )
148         {
149             strcat( content_buffer, "/" );
150         }
151         strcat( content_buffer, file_name );
152         result = stat( content_buffer, &filestat );
153         if ( result == 0 )
154         {
155             if ( S_ISREG( filestat.st_mode ) )
156             {
157                 file_type = RB_FT_FILE;
158             }
159             else if ( S_ISDIR( filestat.st_mode ) )
160             {
161                 file_type = RB_FT_DIRECTORY;
162             }
163             else
164             {
165                 file_type = RB_FT_UNKNOWN;
166             }
167         }
168         else
169         {
170             /* Some error occurred while statting the file */
171         }
172     }
173     return file_type;
174 }
175 #endif
176
177 /****f* Directory/RB_Directory_Insert_RB_Path
178  * SYNOPSIS
179  */
180
181 void RB_Directory_Insert_RB_Path(
182     struct RB_Directory *arg_rb_directory,
183     struct RB_Path *arg_rb_path )
184 /*
185  * FUNCTION
186  *  Insert a RB_Path into a RB_Directory.
187  *  The RB_Path is added at the beginning of the list.
188  * SOURCE
189  */
190 {
191     arg_rb_path->next = arg_rb_directory->first_path;
192     arg_rb_directory->first_path = arg_rb_path;
193 }
194
195 /*****/
196
197 /****f* Directory/RB_Directory_Insert_RB_File
198  * SYNOPSIS
199  */
200 void RB_Directory_Insert_RB_Filename(
201     struct RB_Directory *arg_rb_directory,
202     struct RB_Filename *arg_rb_filename )
203 /*
204  * FUNCTION
205  *   Insert an RB_File structure into a RB_Directory structure.
206  *   The RB_File is added at the end of the list.
207  * SOURCE
208  */
209 {
210     if ( arg_rb_directory->last == 0 )
211     {
212         arg_rb_directory->first = arg_rb_filename;
213         arg_rb_filename->next = 0;
214         arg_rb_directory->last = arg_rb_filename;
215     }
216     else
217     {
218         arg_rb_directory->last->next = arg_rb_filename;
219         arg_rb_filename->next = 0;
220         arg_rb_directory->last = arg_rb_filename;
221     }
222 }
223
224 /******/
225
226 /****f* Directory/RB_Free_RB_Directory
227  * FUNCTION
228  *   Free all the memory use by the RB_Directory structure.
229  * SYNOPSIS
230  */
231 void RB_Free_RB_Directory(
232     struct RB_Directory *arg_directory )
233 /*
234  * INPUTS
235  *   o arg_directory -- the thing to be freed.
236  * SOURCE
237  */
238 {
239     struct RB_Filename *rb_filename;
240     struct RB_Filename *rb_filename2;
241     struct RB_Path     *rb_path;
242     struct RB_Path     *rb_path2;
243
244     /* TODO  Not complete... check for leaks. */
245     rb_filename = arg_directory->first;
246
247     while ( rb_filename )
248     {
249         rb_filename2 = rb_filename;
250         rb_filename = rb_filename->next;
251         RB_Free_RB_Filename( rb_filename2 );
252     }
253
254     rb_path = arg_directory->first_path;
255
256     while ( rb_path )
257     {
258         rb_path2 = rb_path;
259         rb_path = rb_path->next;
260         RB_Free_RB_Path( rb_path2 );
261     }
262
263     free( arg_directory );
264 }
265
266 /******/
267
268
269 /****f* Directory/RB_Get_RB_Directory
270  * NAME
271  *   RB_Get_RB_Directory -- get a RB_Directory structure
272  * FUNCTION
273  *   Returns a RB_Directory structure to the give directory,
274  *   specified by the path.
275  *   This structure can then be uses to walk through all the
276  *   files in the directory and it's subdirectories.
277  * SYNOPSIS
278  */
279 struct RB_Directory *RB_Get_RB_Directory(
280     char *arg_rootpath_name,
281     char *arg_docroot_name )
282 /*
283  * INPUTS
284  *   arg_rootpath -- the name a the directory to get,
285  *                   a nul terminated string.
286  *   arg_docroot_name -- the name of the directory the documentation
287  *                       file are stored in.  This directory is
288  *                       skipped while scanning for sourcefiles.
289  *                       It can be NULL.
290  * RESULT
291  *   A freshly allocated RB_Directory filled with source files.
292  * SOURCE
293  */
294 {
295     struct RB_Directory *rb_directory;
296     struct RB_Path     *doc_path = NULL;
297
298     rb_directory =
299         ( struct RB_Directory * ) malloc( sizeof( struct RB_Directory ) );
300     rb_directory->first = 0;
301     rb_directory->last = 0;
302     rb_directory->first_path = RB_Get_RB_Path( arg_rootpath_name );
303
304     if ( arg_docroot_name )
305     {
306         doc_path = RB_Get_RB_Path( arg_docroot_name );
307     }
308
309     RB_Fill_Directory( rb_directory, rb_directory->first_path, doc_path );
310     if ( ( RB_Number_Of_Filenames( rb_directory ) > 0 ) &&
311          ( RB_Number_Of_Paths( rb_directory ) > 0 ) )
312     {
313         RB_SortDirectory( rb_directory );
314     }
315     else
316     {
317
318         RB_Panic( "No files found! (Or all were filtered out)\n" );
319     }
320     return rb_directory;
321 }
322
323 /********/
324
325
326
327 /****f* Directory/RB_Get_RB_SingleFileDirectory
328  * NAME
329  *   RB_Get_RB_SingleFileDirectory -- get a RB_Directory structure
330  * SYNOPSIS
331  */
332 struct RB_Directory *RB_Get_RB_SingleFileDirectory(
333     char *arg_fullpath )
334 /*
335  * FUNCTION
336  *   Returns a RB_Directory structure to the give directory,
337  *   specified by the path that contains only a single file.
338  *   This is used for the --singlefile option.
339  * INPUT
340  *   o filename -- a filename.  This may include the path.
341  * RESULT
342  *   a freshly allocated RB_Directory that contains only
343  *   a single file.
344  * SOURCE
345  */
346 {
347     struct RB_Directory *rb_directory;
348     struct RB_Path     *path;
349     char               *pathname = NULL;
350     char               *filename = NULL;
351
352     assert( arg_fullpath );
353
354     pathname = RB_Get_PathName( arg_fullpath );
355     filename = RB_Get_FileName( arg_fullpath );
356
357     if ( pathname )
358     {
359         path = RB_Get_RB_Path( pathname );
360     }
361     else
362     {
363         /* no directory was specified so we use
364          * the current directory
365          */
366         path = RB_Get_RB_Path( "./" );
367     }
368
369     rb_directory =
370         ( struct RB_Directory * ) malloc( sizeof( struct RB_Directory ) );
371     rb_directory->first = 0;
372     rb_directory->last = 0;
373
374     rb_directory->first_path = path;
375
376     RB_Directory_Insert_RB_Filename( rb_directory,
377                                      RB_Get_RB_Filename( filename, path ) );
378
379     return rb_directory;
380 }
381
382 /*******/
383
384
385 /****f* Directory/RB_Fill_Directory
386  * NAME
387  *   RB_Fill_Directory -- fill a RB_Directory structure
388  * SYNOPSIS
389  */
390 void RB_Fill_Directory(
391     struct RB_Directory *arg_rb_directory,
392     struct RB_Path *arg_path,
393     struct RB_Path *arg_doc_path )
394 /*
395  * FUNCTION
396  *   Walks through all the files in the directory pointed to
397  *   by arg_path and adds all the files to arg_rb_directory.
398  *   This is a recursive function.
399  * INPUTS
400  *   o arg_rb_directory  -- the result.
401  *   o arg_path          -- the current path that is scanned.
402  *   o arg_doc_path      -- the path to the documentation files.
403  * RESULT
404  *   a RB_Directory structure filled with all sourcefiles and 
405  *   subdirectories in arg_path.
406  * NOTE
407  *   This a is a recursive function.
408  * SOURCE
409  */
410 {
411
412 #if defined (RB_MSVC) || defined(__MINGW32__)
413     struct _finddata_t  c_file;
414     long                hFile;
415     char               *wildcard = NULL;
416     int                 len;
417
418     RB_Say( "Scanning %s\n", SAY_INFO, arg_path->name );
419     len = strlen( arg_path->name ) + strlen( "*.*" ) + 1;
420     wildcard = ( char * ) malloc( len );
421     assert( wildcard );
422     wildcard[0] = '\0';
423     strcat( wildcard, arg_path->name );
424     strcat( wildcard, "*.*" );
425     if ( ( hFile = _findfirst( wildcard, &c_file ) ) == -1L )
426     {
427         RB_Panic( "No files found!\n" );
428     }
429     else
430     {
431         int                 found = TRUE;
432
433         while ( found )
434         {
435             if ( ( c_file.attrib & _A_SUBDIR ) )
436             {
437                 /* It's a directory */
438                 if ( ( strcmp( ".", c_file.name ) == 0 )
439                      || ( strcmp( "..", c_file.name ) == 0 ) )
440                 {
441                     /* Don't recurse into . or ..
442                        because that will result in an infinite 
443                        loop. */
444                 }
445                 else
446                 {
447                     if ( RB_To_Be_Skipped( c_file.name ) )
448                     {
449                         /* User asked this directory to be skipped */
450                     }
451                     else
452                     {
453                                                 if ( course_of_action.do_nodesc )
454                         {
455                             /* Don't descent into the subdirectories */
456                         }
457                         else
458                         {
459                             struct RB_Path     *rb_path =
460                                 RB_Get_RB_Path2( arg_path->name,
461                                                  c_file.name );
462
463                             if ( ( arg_doc_path
464                                    && strcmp( rb_path->name,
465                                               arg_doc_path->name ) )
466                                  || !arg_doc_path )
467                             {
468                                 rb_path->parent = arg_path;
469                                 RB_Directory_Insert_RB_Path( arg_rb_directory,
470                                                              rb_path );
471                                 RB_Fill_Directory( arg_rb_directory, rb_path,
472                                                    arg_doc_path );
473                             }
474                             else
475                             {
476                                 RB_Say( "skipping %s\n", SAY_INFO,
477                                         rb_path->name );
478                             }
479                         }
480                     }
481                 }
482             }
483             else
484             {
485                 if ( RB_Is_Source_File( arg_path, c_file.name ) )
486                 {
487                     RB_Directory_Insert_RB_Filename( arg_rb_directory,
488                                                      RB_Get_RB_Filename
489                                                      ( c_file.name,
490                                                        arg_path ) );
491                 }
492                 else
493                 {
494                     /* It's not a sourcefile so we skip it. */
495                 }
496             }
497             /* Find the rest of the *.* files */
498             found = ( _findnext( hFile, &c_file ) == 0 );
499         }
500         _findclose( hFile );
501     }
502     free( wildcard );
503 #else
504     struct dirent      *a_direntry;
505     DIR                *a_dirstream;
506
507     RB_Say( "Scanning %s\n", SAY_INFO, arg_path->name );
508     a_dirstream = opendir( arg_path->name );
509
510     if ( a_dirstream )
511     {
512         T_RB_FileType       file_type;
513
514         for ( a_direntry = readdir( a_dirstream );
515               a_direntry; a_direntry = readdir( a_dirstream ) )
516         {
517             file_type = RB_FileType( arg_path->name, a_direntry );
518             if ( file_type == RB_FT_FILE )
519             {
520                 /* It is a regular file. See if it is a sourcefile. */
521                 if ( RB_Is_Source_File( arg_path, a_direntry->d_name ) )
522                 {
523                     /* It is, so we add it to the directory tree */
524                     RB_Directory_Insert_RB_Filename( arg_rb_directory,
525                                                      RB_Get_RB_Filename
526                                                      ( a_direntry->d_name,
527                                                        arg_path ) );
528                 }
529                 else
530                 {
531                     /* It's not a sourcefile so we skip it. */
532                 }
533             }
534             else if ( file_type == RB_FT_DIRECTORY )
535             {
536                 if ( ( strcmp( ".", a_direntry->d_name ) == 0 ) ||
537                      ( strcmp( "..", a_direntry->d_name ) == 0 ) )
538                 {
539                     /* Don't recurse into . or ..
540                        because that will result in an infinite */
541                 }
542                 else
543                 {
544                     if ( RB_To_Be_Skipped( a_direntry->d_name ) )
545                     {
546                         /* User asked this directory to be skipped */
547                     }
548                     else
549                     {
550                         if ( course_of_action.do_nodesc )
551                         {
552                             /* Don't descent into the subdirectories */
553                         }
554                         else
555                         {
556                             struct RB_Path     *rb_path =
557                                 RB_Get_RB_Path2( arg_path->name,
558                                                  a_direntry->d_name );
559
560                             rb_path->parent = arg_path;
561                             if ( ( arg_doc_path
562                                    && strcmp( rb_path->name,
563                                               arg_doc_path->name ) )
564                                  || !arg_doc_path )
565                             {
566                                 RB_Directory_Insert_RB_Path( arg_rb_directory,
567                                                              rb_path );
568                                 RB_Fill_Directory( arg_rb_directory, rb_path,
569                                                    arg_doc_path );
570                             }
571                             else
572                             {
573                                 RB_Say( "skipping %s\n", SAY_INFO,
574                                         rb_path->name );
575                             }
576                         }
577                     }
578                 }
579             }
580             else
581             {
582                 /* Not a file and also not a directory */
583             }
584         }
585     }
586     closedir( a_dirstream );
587 #endif
588 }
589
590 /*****/
591
592 /****f* Directory/RB_Is_Source_File
593  * NAME
594  *   RB_Is_Source_File -- Is a file a sourcefile?
595  * SYNOPSIS
596  */
597 int RB_Is_Source_File(
598     struct RB_Path *path,
599     char *filename )
600 /*
601  * FUNCTION
602  *   This functions examines the content of a file to
603  *   see whether or not it is a sourcefile.
604  *
605  *   Currently it checks if there are no nul characters
606  *   in the first 8191 characters of the file.
607  * SOURCE
608  */
609 {
610     int                 is_source = 1;
611
612     if ( RB_Not_Accepted( filename ) )
613     {
614         /* File needs to be skipped based on it's name */
615         is_source = 0;
616     }
617     else
618     {
619         unsigned int        size = 0;
620
621         /* Compute the length of the filename including
622            the path. */
623         size += strlen( path->name );
624         size += 1;
625         size += strlen( filename );
626         if ( size < RB_CBUFFERSIZE )
627         {
628             FILE               *file;
629
630             /* We use the content_buffer buffer temporarily to
631                store the filename. */
632             content_buffer[0] = '\0';
633             strcat( content_buffer, path->name );
634             strcat( content_buffer, filename );
635             if ( ( file = fopen( content_buffer, "rb" ) ) )
636             {
637                 int                 no_read;
638                 no_read =
639                     fread( content_buffer, sizeof( char ), RB_CBUFFERSIZE,
640                            file );
641                 if ( no_read > 10 )
642                 {
643                     char               *c;
644
645                     for ( c = content_buffer; no_read; --no_read, c++ )
646                     {
647                         if ( *c == 0 )
648                         {
649                             is_source = 0;
650                             break;
651                         }
652                     }
653                 }
654                 else
655                 {
656                     /* A file with only 9 characters can not
657                        contain any source plus documentation. */
658                     is_source = 0;
659                 }
660                 fclose( file );
661             }
662             else
663             {
664                 /* The file could not be opened.   */
665                 is_source = 0;
666             }
667         }
668         else
669         {
670             /* The filename is longer than 8191 characters,
671                that's way too long... so we skip it. */
672             is_source = 0;
673         }
674     }
675     return is_source;
676 }
677
678 /*****/
679
680
681 /****f* Directory/RB_To_Be_Skipped
682  * FUNCTION
683  *   Test if a file should not be included in the list of source files
684  *   that are scanned for documentation. 
685  *
686  *   This test is done based on the wildcard expressions specified
687  *   in configuration.ignore_files.
688  * SYNOPSIS
689  */
690 int RB_To_Be_Skipped(
691     char *filename )
692 /*
693  * INPUTS
694  *   o filename -- the name of the file
695  * SOURCE
696  */
697 {
698     unsigned int        i;
699     int                 skip = FALSE;
700
701     for ( i = 0; i < configuration.ignore_files.number; ++i )
702     {
703         if ( RB_Match( filename, configuration.ignore_files.names[i] ) )
704         {
705             skip = TRUE;
706             break;
707         }
708     }
709     return skip;
710 }
711
712 /******/
713
714
715 /****f* Directory/RB_Not_Accepted
716  * FUNCTION
717  *   Test if a file should be skipped, 
718  *   because it does not match a pattern in "accept files:"
719  *
720  *   This test is done based on the wildcard expressions specified
721  *   in configuration.accept_files.
722  * SYNOPSIS
723  */
724 int RB_Not_Accepted(
725     char *filename )
726 /*
727  * INPUTS
728  *   o filename -- the name of the file
729  * SOURCE
730  */
731 {
732     unsigned int        i;
733     int                 skip = FALSE;
734
735     skip = RB_To_Be_Skipped( filename );
736
737     if ( !skip && configuration.accept_files.number > 0 )
738     {
739         skip = TRUE;
740         for ( i = 0; i < configuration.accept_files.number; ++i )
741         {
742             if ( RB_Match( filename, configuration.accept_files.names[i] ) )
743             {
744                 RB_Say( "accept >%s< with >%s<\n", SAY_INFO, filename,
745                         configuration.accept_files.names[i] );
746                 skip = FALSE;
747                 break;
748             }
749         }
750     }
751     return skip;
752 }
753
754 /******/
755
756
757 /****f* Directory/RB_Get_FileName
758  * NAME
759  *   RB_Get_PathName -- extract the file name 
760  * SYNOPSIS
761  */
762 char               *RB_Get_FileName(
763     char *arg_fullpath )
764 /*
765  * FUNCTION
766  *   Given a full path to a file, that is a filename, or a filename
767  *   prefixed with a pathname, return the filename.
768  *   So
769  *      "./filename"           returns "filename"
770  *      "/home/et/filename"    returns "filename"
771  *      "filename"             return  "filename"
772  * INPUTS
773  *   arg_fullpath -- a full path to a file, with or without a path.
774  *
775  * RESULT
776  *   0  -- The full path did not contain a filename
777  *   pointer to the filename -- otherwise.
778  * NOTES
779  *   You are responsible for deallocating it.
780  * SOURCE
781  */
782 {
783     int                 n;
784     int                 i;
785     int                 found;
786     char               *result = 0;
787
788     assert( arg_fullpath );
789
790     n = strlen( arg_fullpath );
791
792     /* Try and find a path character ( ':' or '/' ) */
793     for ( found = FALSE, i = 0; i < n; ++i )
794     {
795         if ( RB_Is_PathCharacter( arg_fullpath[i] ) )
796         {
797             found = TRUE;
798             break;
799         }
800     }
801
802     if ( !found )
803     {
804         /* The full path does not contain a pathname,
805          * so we can return the arg_fullpath as
806          * the filename.
807          */
808         result = RB_StrDup( arg_fullpath );
809     }
810     else
811     {
812         /* The full path does contain a pathname,
813          * strip this and return the remainder
814          */
815
816         for ( i = n - 1; i > 0; --i )
817         {
818             if ( RB_Is_PathCharacter( arg_fullpath[i] ) )
819             {
820                 assert( i < ( n - 1 ) );
821                 result = RB_StrDup( &( arg_fullpath[i + 1] ) );
822                 break;
823             }
824         }
825     }
826
827     return result;
828 }
829
830 /********/
831
832
833
834 /****f* Directory/RB_Get_PathName
835  * NAME
836  *   RB_Get_PathName -- extract the path name 
837  * SYNOPSIS
838  */
839 char               *RB_Get_PathName(
840     char *arg_fullpath )
841 /*
842  * FUNCTION
843  *   Given a full path to a file, that is a filename, or a filename
844  *   prefixed with a pathname, return the pathname.
845  *   So
846  *      "./filename"           returns "./"
847  *      "/home/et/filename"    returns "/home/et/"
848  *      "filename"             return  ""
849  * INPUTS
850  *   arg_fullpath -- a full path to a file, with or without a path.
851  *
852  * RESULT
853  *   0  -- The full path did not contain a path
854  *   pointer to the pathname -- otherwise.
855  * NOTES
856  *   You are responsible for deallocating it.
857  * SOURCE
858  */
859 {
860     int                 n;
861     int                 i;
862     int                 found;
863     char               *result = 0;
864
865     assert( arg_fullpath );
866
867     n = strlen( arg_fullpath );
868
869     /* Try and find a path character ( ':' or '/' ) */
870     for ( found = FALSE, i = 0; i < n; ++i )
871     {
872         if ( RB_Is_PathCharacter( arg_fullpath[i] ) )
873         {
874             found = TRUE;
875             break;
876         }
877     }
878
879     if ( found )
880     {
881         /* Copy the whole file name and then
882            replace the character after the 
883            first path character found
884            counting from the back with a '\0'
885          */
886         result = RB_StrDup( arg_fullpath );
887
888         for ( i = n - 1; i > 0; --i )
889         {
890             if ( RB_Is_PathCharacter( result[i] ) )
891             {
892                 assert( i < ( n - 1 ) );
893                 result[i + 1] = '\0';
894                 break;
895             }
896         }
897     }
898
899     return result;
900 }
901
902 /*******/
903
904
905 /****f* Directory/RB_Is_PathCharacter
906  * FUNCTION
907  *   Test if a character is part of the group of
908  *   characters that you would normally find in 
909  *   a path.
910  * SYNOPSIS
911  */
912 static int RB_Is_PathCharacter(
913     int c )
914 /*
915  * INPUTS
916  *   c -- the character to be tested.
917  * RESULT
918  *   TRUE  -- it is a path character.
919  *   FALSE -- it is not.
920  * SOURCE
921  */
922 {
923     return ( ( c == ':' ) || ( c == '/' ) );
924 }
925
926 /******/
927
928
929 /* Sort the files and paths */
930
931 /* TODO FS Document */
932 unsigned int RB_Number_Of_Filenames(
933     struct RB_Directory *arg_rb_directory )
934 {
935     unsigned int        number_of_filenames = 0;
936     struct RB_Filename *rb_filename = NULL;
937
938     for ( rb_filename = arg_rb_directory->first;
939           rb_filename;
940           ++number_of_filenames, rb_filename = rb_filename->next )
941     {
942         /* Empty */
943     }
944     return number_of_filenames;
945 }
946
947
948 /* TODO FS Document */
949 unsigned int RB_Number_Of_Paths(
950     struct RB_Directory *arg_rb_directory )
951 {
952     unsigned int        number_of_paths = 0;
953     struct RB_Path     *rb_path = NULL;
954
955     for ( rb_path = arg_rb_directory->first_path;
956           rb_path; ++number_of_paths, rb_path = rb_path->next )
957     {
958         /* Empty */
959     }
960     return number_of_paths;
961 }
962
963 /* TODO FS Document */
964 int RB_Path_Compare(
965     void *p1,
966     void *p2 )
967 {
968     struct RB_Path     *path_1 = p1;
969     struct RB_Path     *path_2 = p2;
970
971     return RB_Str_Case_Cmp( path_1->name, path_2->name );
972 }
973
974 /* TODO FS Document */
975 int RB_Filename_Compare(
976     void *p1,
977     void *p2 )
978 {
979     struct RB_Filename *filename_1 = p1;
980     struct RB_Filename *filename_2 = p2;
981
982     return RB_Str_Case_Cmp( filename_1->name, filename_2->name );
983 }
984
985
986 /* TODO FS Document */
987 void RB_SortDirectory(
988     struct RB_Directory *arg_rb_directory )
989 {
990     unsigned int        number_of_filenames =
991         RB_Number_Of_Filenames( arg_rb_directory );
992     unsigned int        number_of_paths =
993         RB_Number_Of_Paths( arg_rb_directory );
994     unsigned int        i;
995     struct RB_Path     *rb_path = NULL;
996     struct RB_Filename *rb_filename = NULL;
997     struct RB_Path    **paths = NULL;
998     struct RB_Filename **filenames = NULL;
999
1000     assert( number_of_filenames > 0 );
1001     assert( number_of_paths > 0 );
1002     paths = calloc( number_of_paths, sizeof( struct RB_Path * ) );
1003     filenames = calloc( number_of_filenames, sizeof( struct RB_Filename * ) );
1004
1005
1006     RB_Say( "Sorting Directory\n", SAY_INFO );
1007     for ( i = 0, rb_path = arg_rb_directory->first_path;
1008           rb_path; rb_path = rb_path->next )
1009     {
1010         assert( i < number_of_paths );
1011         paths[i] = rb_path;
1012         ++i;
1013     }
1014
1015     for ( i = 0, rb_filename = arg_rb_directory->first;
1016           rb_filename; rb_filename = rb_filename->next )
1017     {
1018         assert( i < number_of_filenames );
1019         filenames[i] = rb_filename;
1020         ++i;
1021     }
1022
1023     RB_QuickSort( ( void ** ) paths, 0, number_of_paths - 1,
1024                   RB_Path_Compare );
1025     RB_QuickSort( ( void ** ) filenames, 0, number_of_filenames - 1,
1026                   RB_Filename_Compare );
1027
1028     for ( i = 0; i < number_of_paths - 1; ++i )
1029     {
1030         paths[i]->next = paths[i + 1];
1031     }
1032     paths[number_of_paths - 1]->next = NULL;
1033     arg_rb_directory->first_path = paths[0];
1034
1035     for ( i = 0; i < number_of_filenames - 1; ++i )
1036     {
1037         filenames[i]->next = filenames[i + 1];
1038     }
1039     filenames[number_of_filenames - 1]->next = NULL;
1040     arg_rb_directory->first = filenames[0];
1041     arg_rb_directory->last = filenames[number_of_filenames - 1];
1042
1043     free( paths );
1044     free( filenames );
1045 }