home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / cweb28.zip / examples / treeprint.w < prev    next >
Text File  |  1992-07-08  |  7KB  |  248 lines

  1. \def\covernote{Copyright 1987 Norman Ramsey -- Princeton University}
  2.  
  3. \def\vbar{\.{|}}
  4. @*Directory Trees.
  5. Our object is to print out a directory hierarchy in some pleasant way.
  6. The program takes output from {\tt find * -type d -print \vbar\ sort}
  7. @^system dependencies@>
  8. and produces a nicer-looking listing.
  9. More precisely, our input, which is the output of {\tt find} followed
  10. by {\tt sort}, is a list of fully qualified directory names (parent
  11. and child separated by slashes |'/'|); everything has already been
  12. sorted nicely into lexicographic order.
  13.  
  14. The {\tt treeprint} routine takes one option, |"-p"|, which tells it
  15. to use the printer's line-drawing set, rather than the terminal's.
  16.  
  17. @c
  18. @<Global definitions@>@;
  19. @<Global include files@>@;
  20. @<Global declarations@>@;
  21.  
  22. @#
  23. main(argc, argv)
  24.      int argc;
  25.      char **argv;
  26. {
  27. @<|main| variable declarations@>;
  28. @<Search for options and set special characters on |"-p"|@>;
  29. @<Read output from find and enter into tree@>;
  30. @<Write tree on standard output@>@;
  31. exit(0);
  32. }
  33.  
  34. @
  35. We make all the siblings of a directory a linked list off of its left child, 
  36. and the offspring a linked list off the right side.
  37. Data are just directory names.
  38. @d sibling left
  39. @d child right
  40.  
  41. @<Global decl...@>=
  42. typedef struct tnode {
  43.   struct tnode *left, *right;
  44.   char *data;
  45. } TNODE;
  46. @ @<|main| variable...@>=
  47. struct tnode *root;
  48.  
  49.  
  50.  
  51. @*Input.
  52. Reading the tree is simple---we read one line at a time, and call on the 
  53. recursive |add_tree| procedure.
  54.  
  55. @c
  56. read_tree (fp, rootptr)
  57.    FILE *fp;
  58.    struct tnode **rootptr;
  59. {
  60.    char buf[255], *p;
  61.  
  62.    while ((fgets(buf, 255, fp))!=NULL) {
  63.      @<If |buf| contains a newline, make it end there@>;
  64.      add_tree(rootptr, buf);
  65.    }
  66.  }
  67. @ @<Global include...@>=#include <stdio.h>
  68.  
  69. @ Depending what system you're on, you may or may not get a newline in |buf|.
  70. @<If |buf| contains a newline...@>=
  71.      p=buf; while (*p!='\0'&&*p!='\n') p++;
  72. @^system dependencies@>
  73.      *p='\0';
  74.  
  75. To add a string, we split off the first part of the name and insert it into 
  76. the sibling list. We then do the rest of the string as a child of the new node.
  77.  
  78. @c
  79. add_tree(rootptr, p)
  80.      struct tnode **rootptr;
  81.      char *p;
  82. {
  83.      char *s;
  84.      int slashed;
  85.  
  86.      if (*p=='\0') return;
  87.  
  88. @<Break up the string so |p| is the first word,
  89.      |s| points at null-begun remainder,
  90.     and |slashed| tells whether |*s=='/'| on entry@>;
  91.  
  92.      if (*rootptr==NULL) {
  93. @<Allocate new node to hold string of size |strlen(p)|@>;
  94.        strcpy((*rootptr)->data,p);
  95.      } 
  96.      if (strcmp((*rootptr)->data,p)==0) {
  97.            if (slashed) ++s;
  98.            add_tree(&((*rootptr)->child),s);
  99.      }
  100.        else {
  101.            if (slashed) *s='/';
  102.            add_tree(&((*rootptr)->sibling),p);
  103.      }
  104.    }
  105.  
  106. @ We perform some nonsense to cut off the string |p| so that |p| just
  107. holds the first word of a multiword name. Variable |s| points at what
  108. was either the end of |p| or a slash delimiting names. In either case
  109. |*s| is made |'\0'|.  Later, depending on whether we want to pass the
  110. whole string or the last piece, we will restore the slash or advance
  111. |s| one character to the right.
  112.  
  113. @<Break up...@>=
  114.      for (s=p;*s!='\0'&&*s!='/';) s++;
  115.      if (*s=='/') {
  116.        slashed=1;
  117.        *s='\0';
  118.      } else slashed=0;
  119.  
  120. @ Node allocation is perfectly standard \dots
  121. @<Allocate new node...@>=
  122.        *rootptr=(struct tnode *) malloc (sizeof(struct tnode));
  123.        (*rootptr)->left = (*rootptr)->right = NULL;
  124.        (*rootptr)->data = malloc (strlen(p)+1);
  125.  
  126. @
  127. @<Global decl...@>= char *malloc();
  128.  
  129. @ In this simple implementation, we just read from standard input.
  130. @<Read...@>= read_tree(stdin,&root);
  131.  
  132. @*Output.
  133. We begin by defining some lines, tees, and corners.
  134. The |s| stands for screen and the |p| for printer.
  135. You will have to change this for your line-drawing set.
  136. @^system dependencies@>
  137.  
  138. @<Global definitions@>=
  139. #define svert '|'
  140. #define shoriz '-'
  141. #define scross '+'
  142. #define scorner '\\' /* lower left corner */
  143.  
  144. #define pvert '|'
  145. #define phoriz '-'
  146. #define pcross '+'
  147. #define pcorner '\\' /* lower left corner */
  148.  
  149. @ The default is to use the terminal's line drawing set.
  150. @<Global declarations@>=
  151. char vert=svert;
  152. char horiz=shoriz;
  153. char cross=scross;
  154. char corner=scorner;
  155.  
  156. @ With option |"-p"| use the printer character set.
  157. @<Search for options...@>=
  158. while (--argc>0) {
  159.   if (**++argv=='-') {
  160.     switch (*++(*argv)) {
  161.       case 'p': 
  162.         vert=pvert;
  163.         horiz=phoriz;
  164.         cross=pcross;
  165.         corner=pcorner;
  166.     break;
  167.       default:
  168.         fprintf(stderr,"treeprint: bad option -%c\n",**argv);
  169.     break;
  170.       }
  171.   }
  172. }
  173.  
  174. @ We play games with a character stack to figure out when to put in vertical
  175. bars.
  176. A vertical bar connects every sibling with its successor, but the last sibling
  177. in a list is followed by blanks, not by vertical bars. The state of 
  178. bar-ness or space-ness for each preceding sibling is recorded in the 
  179. |indent_string| variable, one character (bar or blank) per sibling.
  180.  
  181. @<Global decl...@>=
  182. char indent_string[100]="";
  183.  
  184. @  Children get printed 
  185. before siblings.
  186. We don't bother trying to bring children up to the same line as their parents,
  187. because the \UNIX\ filenames are so long.
  188.  
  189. We define a predicate telling us when a sibling is the last in a series.
  190. @d is_last(S) (S->sibling==NULL)
  191.  
  192. @c
  193. print_node(fp, indent_string, node)
  194.      FILE *fp;
  195.      char *indent_string;
  196.      struct tnode *node;
  197. {
  198.   char string[255];
  199.      int i;
  200.      char *p, *is;
  201.  
  202.   if (node==NULL) {
  203.   }
  204.   else {
  205.     *string='\0';
  206.     for (i=strlen(indent_string); i>0; i--) 
  207.       strcat(string,@,      " |  ");
  208.     strcat(string,@t\ \ @>  " +--");
  209. @<Replace chars in |string| with chars from 
  210.          line-drawing set and from |indent_string|@>;
  211.     fprintf(fp,"%s%s\n",string,node->data);
  212.  
  213. @#
  214.     /* Add vertical bar or space for this sibling (claim |*is=='\0'|) */
  215.     *is++ = (is_last(node) ? ' ' : vert);
  216.     *is=='\0';
  217.    
  218.     print_node(fp, indent_string, node->child); /* extended |indent_string| */
  219.     *--is='\0';
  220.     print_node(fp, indent_string, node->sibling); /* original |indent_string| */
  221.   }
  222.  
  223. }
  224. @ For simplicity, we originally wrote connecting lines with |'|'|, |'+'|, and
  225. |'-'|.
  226. Now we replace those characters with appropriate characters from the 
  227. line-drawing set. 
  228. We take the early vertical bars and replace them with characters from
  229. |indent_string|, and we replace the other characters appropriately.
  230. We are sure to put a |corner|, not a |cross|, on the last sibling in 
  231. a group.
  232. @<Replace chars...@>=
  233.     is=indent_string;
  234.     for (p=string; *p!='\0'; p++) switch(*p) {
  235.        case '|': *p=*is++; break;
  236.        case '+': *p=(is_last(node) ? corner : cross); break;
  237.        case '-': *p=horiz; break;
  238.        default: break;
  239.            }
  240.  
  241.  
  242. @ For this simple implementation, we just write on standard output.
  243.  
  244. @<Write...@>= print_node(stdout, indent_string, root);
  245.  
  246. @*Index.
  247.