home *** CD-ROM | disk | FTP | other *** search
/ ARM Club 3 / TheARMClub_PDCD3.iso / hensa / programming / cweb / cwebsrc / CWEB / src / Examples / w / treeprint < prev    next >
Encoding:
Text File  |  1996-10-30  |  6.7 KB  |  249 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=NULL;
  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...@>=
  68. #include <stdio.h>
  69.  
  70. @ Depending what system you're on, you may or may not get a newline in |buf|.
  71. @<If |buf| contains a newline...@>=
  72.      p=buf; while (*p!='\0'&&*p!='\n') p++;
  73. @^system dependencies@>
  74.      *p='\0';
  75.  
  76. To add a string, we split off the first part of the name and insert it into 
  77. the sibling list. We then do the rest of the string as a child of the new node.
  78.  
  79. @c
  80. add_tree(rootptr, p)
  81.      struct tnode **rootptr;
  82.      char *p;
  83. {
  84.      char *s;
  85.      int slashed;
  86.  
  87.      if (*p=='\0') return;
  88.  
  89. @<Break up the string so |p| is the first word,
  90.      |s| points at null-begun remainder,
  91.     and |slashed| tells whether |*s=='/'| on entry@>;
  92.  
  93.      if (*rootptr==NULL) {
  94. @<Allocate new node to hold string of size |strlen(p)|@>;
  95.        strcpy((*rootptr)->data,p);
  96.      } 
  97.      if (strcmp((*rootptr)->data,p)==0) {
  98.            if (slashed) ++s;
  99.            add_tree(&((*rootptr)->child),s);
  100.      }
  101.        else {
  102.            if (slashed) *s='/';
  103.            add_tree(&((*rootptr)->sibling),p);
  104.      }
  105.    }
  106.  
  107. @ We perform some nonsense to cut off the string |p| so that |p| just
  108. holds the first word of a multiword name. Variable |s| points at what
  109. was either the end of |p| or a slash delimiting names. In either case
  110. |*s| is made |'\0'|.  Later, depending on whether we want to pass the
  111. whole string or the last piece, we will restore the slash or advance
  112. |s| one character to the right.
  113.  
  114. @<Break up...@>=
  115.      for (s=p;*s!='\0'&&*s!='/';) s++;
  116.      if (*s=='/') {
  117.        slashed=1;
  118.        *s='\0';
  119.      } else slashed=0;
  120.  
  121. @ Node allocation is perfectly standard \dots
  122. @<Allocate new node...@>=
  123.        *rootptr=(struct tnode *) malloc (sizeof(struct tnode));
  124.        (*rootptr)->left = (*rootptr)->right = NULL;
  125.        (*rootptr)->data = malloc (strlen(p)+1);
  126.  
  127. @
  128. @<Global decl...@>= char *malloc();
  129.  
  130. @ In this simple implementation, we just read from standard input.
  131. @<Read...@>= read_tree(stdin,&root);
  132.  
  133. @*Output.
  134. We begin by defining some lines, tees, and corners.
  135. The |s| stands for screen and the |p| for printer.
  136. You will have to change this for your line-drawing set.
  137. @^system dependencies@>
  138.  
  139. @<Global definitions@>=
  140. #define svert '|'
  141. #define shoriz '-'
  142. #define scross '+'
  143. #define scorner '\\' /* lower left corner */
  144.  
  145. #define pvert '|'
  146. #define phoriz '-'
  147. #define pcross '+'
  148. #define pcorner '\\' /* lower left corner */
  149.  
  150. @ The default is to use the terminal's line drawing set.
  151. @<Global declarations@>=
  152. char vert=svert;
  153. char horiz=shoriz;
  154. char cross=scross;
  155. char corner=scorner;
  156.  
  157. @ With option |"-p"| use the printer character set.
  158. @<Search for options...@>=
  159. while (--argc>0) {
  160.   if (**++argv=='-') {
  161.     switch (*++(*argv)) {
  162.       case 'p': 
  163.         vert=pvert;
  164.         horiz=phoriz;
  165.         cross=pcross;
  166.         corner=pcorner;
  167.     break;
  168.       default:
  169.         fprintf(stderr,"treeprint: bad option -%c\n",**argv);
  170.     break;
  171.       }
  172.   }
  173. }
  174.  
  175. @ We play games with a character stack to figure out when to put in vertical
  176. bars.
  177. A vertical bar connects every sibling with its successor, but the last sibling
  178. in a list is followed by blanks, not by vertical bars. The state of 
  179. bar-ness or space-ness for each preceding sibling is recorded in the 
  180. |indent_string| variable, one character (bar or blank) per sibling.
  181.  
  182. @<Global decl...@>=
  183. char indent_string[100]="";
  184.  
  185. @  Children get printed 
  186. before siblings.
  187. We don't bother trying to bring children up to the same line as their parents,
  188. because the \UNIX/ filenames are so long.
  189.  
  190. We define a predicate telling us when a sibling is the last in a series.
  191. @d is_last(S) (S->sibling==NULL)
  192.  
  193. @c
  194. print_node(fp, indent_string, node)
  195.      FILE *fp;
  196.      char *indent_string;
  197.      struct tnode *node;
  198. {
  199.   char string[255];
  200.      int i;
  201.      char *p, *is;
  202.  
  203.   if (node==NULL) {
  204.   }
  205.   else {
  206.     *string='\0';
  207.     for (i=strlen(indent_string); i>0; i--) 
  208.       strcat(string,@,      " |  ");
  209.     strcat(string,@t\ \ @>  " +--");
  210. @<Replace chars in |string| with chars from 
  211.          line-drawing set and from |indent_string|@>;
  212.     fprintf(fp,"%s%s\n",string,node->data);
  213.  
  214. @#
  215.     /* Add vertical bar or space for this sibling (claim |*is=='\0'|) */
  216.     *is++ = (is_last(node) ? ' ' : vert);
  217.     *is='\0';
  218.    
  219.     print_node(fp, indent_string, node->child); /* extended |indent_string| */
  220.     *--is='\0';
  221.     print_node(fp, indent_string, node->sibling); /* original |indent_string| */
  222.   }
  223.  
  224. }
  225. @ For simplicity, we originally wrote connecting lines with |'|'|, |'+'|, and
  226. |'-'|.
  227. Now we replace those characters with appropriate characters from the 
  228. line-drawing set. 
  229. We take the early vertical bars and replace them with characters from
  230. |indent_string|, and we replace the other characters appropriately.
  231. We are sure to put a |corner|, not a |cross|, on the last sibling in 
  232. a group.
  233. @<Replace chars...@>=
  234.     is=indent_string;
  235.     for (p=string; *p!='\0'; p++) switch(*p) {
  236.        case '|': *p=*is++; break;
  237.        case '+': *p=(is_last(node) ? corner : cross); break;
  238.        case '-': *p=horiz; break;
  239.        default: break;
  240.            }
  241.  
  242.  
  243. @ For this simple implementation, we just write on standard output.
  244.  
  245. @<Write...@>= print_node(stdout, indent_string, root);
  246.  
  247. @*Index.
  248.