\def\covernote{Copyright 1987 Norman Ramsey -- Princeton University}

\def\vbar{\.{|}}
@*Directory Trees.
Our object is to print out a directory hierarchy in some pleasant way.
The program takes output from {\tt find * -type d -print \vbar\ sort}
@^system dependencies@>
and produces a nicer-looking listing.
More precisely, our input, which is the output of {\tt find} followed
by {\tt sort}, is a list of fully qualified directory names (parent
and child separated by slashes |'/'|); everything has already been
sorted nicely into lexicographic order.

The {\tt treeprint} routine takes one option, |"-p"|, which tells it
to use the printer's line-drawing set, rather than the terminal's.

@c
@<Global definitions@>@;
@<Global include files@>@;
@<Global declarations@>@;

@#
main(argc, argv)
     int argc;
     char **argv;
{
@<|main| variable declarations@>;
@<Search for options and set special characters on |"-p"|@>;
@<Read output from find and enter into tree@>;
@<Write tree on standard output@>@;
exit(0);
}

@
We make all the siblings of a directory a linked list off of its left child, 
and the offspring a linked list off the right side.
Data are just directory names.
@d sibling left
@d child right

@<Global decl...@>=
typedef struct tnode {
  struct tnode *left, *right;
  char *data;
} TNODE;
@ @<|main| variable...@>=
struct tnode *root=NULL;



@*Input.
Reading the tree is simple---we read one line at a time, and call on the 
recursive |add_tree| procedure.

@c
read_tree (fp, rootptr)
   FILE *fp;
   struct tnode **rootptr;
{
   char buf[255], *p;

   while ((fgets(buf, 255, fp))!=NULL) {
     @<If |buf| contains a newline, make it end there@>;
     add_tree(rootptr, buf);
   }
 }
@ @<Global include...@>=
#include <stdio.h>

@ Depending what system you're on, you may or may not get a newline in |buf|.
@<If |buf| contains a newline...@>=
     p=buf; while (*p!='\0'&&*p!='\n') p++;
@^system dependencies@>
     *p='\0';

@ 
To add a string, we split off the first part of the name and insert it into 
the sibling list. We then do the rest of the string as a child of the new node.

@c
add_tree(rootptr, p)
     struct tnode **rootptr;
     char *p;
{
     char *s;
     int slashed;

     if (*p=='\0') return;

@<Break up the string so |p| is the first word,
     |s| points at null-begun remainder,
    and |slashed| tells whether |*s=='/'| on entry@>;

     if (*rootptr==NULL) {
@<Allocate new node to hold string of size |strlen(p)|@>;
       strcpy((*rootptr)->data,p);
     } 
     if (strcmp((*rootptr)->data,p)==0) {
           if (slashed) ++s;
           add_tree(&((*rootptr)->child),s);
	 }
       else {
           if (slashed) *s='/';
           add_tree(&((*rootptr)->sibling),p);
	 }
   }

@ We perform some nonsense to cut off the string |p| so that |p| just
holds the first word of a multiword name. Variable |s| points at what
was either the end of |p| or a slash delimiting names. In either case
|*s| is made |'\0'|.  Later, depending on whether we want to pass the
whole string or the last piece, we will restore the slash or advance
|s| one character to the right.

@<Break up...@>=
     for (s=p;*s!='\0'&&*s!='/';) s++;
     if (*s=='/') {
       slashed=1;
       *s='\0';
     } else slashed=0;

@ Node allocation is perfectly standard \dots
@<Allocate new node...@>=
       *rootptr=(struct tnode *) malloc (sizeof(struct tnode));
       (*rootptr)->left = (*rootptr)->right = NULL;
       (*rootptr)->data = malloc (strlen(p)+1);

@
@<Global decl...@>= char *malloc();

@ In this simple implementation, we just read from standard input.
@<Read...@>= read_tree(stdin,&root);

@*Output.
We begin by defining some lines, tees, and corners.
The |s| stands for screen and the |p| for printer.
You will have to change this for your line-drawing set.
@^system dependencies@>

@<Global definitions@>=
#define svert '|'
#define shoriz '-'
#define scross '+'
#define scorner '\\' /* lower left corner */

#define pvert '|'
#define phoriz '-'
#define pcross '+'
#define pcorner '\\' /* lower left corner */

@ The default is to use the terminal's line drawing set.
@<Global declarations@>=
char vert=svert;
char horiz=shoriz;
char cross=scross;
char corner=scorner;

@ With option |"-p"| use the printer character set.
@<Search for options...@>=
while (--argc>0) {
  if (**++argv=='-') {
    switch (*++(*argv)) {
      case 'p': 
        vert=pvert;
        horiz=phoriz;
        cross=pcross;
        corner=pcorner;
	break;
      default:
        fprintf(stderr,"treeprint: bad option -%c\n",**argv);
	break;
      }
  }
}

@ We play games with a character stack to figure out when to put in vertical
bars.
A vertical bar connects every sibling with its successor, but the last sibling
in a list is followed by blanks, not by vertical bars. The state of 
bar-ness or space-ness for each preceding sibling is recorded in the 
|indent_string| variable, one character (bar or blank) per sibling.

@<Global decl...@>=
char indent_string[100]="";

@  Children get printed 
before siblings.
We don't bother trying to bring children up to the same line as their parents,
because the \UNIX/ filenames are so long.

We define a predicate telling us when a sibling is the last in a series.
@d is_last(S) (S->sibling==NULL)

@c
print_node(fp, indent_string, node)
     FILE *fp;
     char *indent_string;
     struct tnode *node;
{
  char string[255];
     int i;
     char *p, *is;

  if (node==NULL) {
  }
  else {
    *string='\0';
    for (i=strlen(indent_string); i>0; i--) 
      strcat(string,@,      " |  ");
    strcat(string,@t\ \ @>  " +--");
@<Replace chars in |string| with chars from 
         line-drawing set and from |indent_string|@>;
    fprintf(fp,"%s%s\n",string,node->data);

@#
    /* Add vertical bar or space for this sibling (claim |*is=='\0'|) */
    *is++ = (is_last(node) ? ' ' : vert);
    *is='\0';
   
    print_node(fp, indent_string, node->child); /* extended |indent_string| */
    *--is='\0';
    print_node(fp, indent_string, node->sibling); /* original |indent_string| */
  }

}
@ For simplicity, we originally wrote connecting lines with |'|'|, |'+'|, and
|'-'|.
Now we replace those characters with appropriate characters from the 
line-drawing set. 
We take the early vertical bars and replace them with characters from
|indent_string|, and we replace the other characters appropriately.
We are sure to put a |corner|, not a |cross|, on the last sibling in 
a group.
@<Replace chars...@>=
    is=indent_string;
    for (p=string; *p!='\0'; p++) switch(*p) {
       case '|': *p=*is++; break;
       case '+': *p=(is_last(node) ? corner : cross); break;
       case '-': *p=horiz; break;
       default: break;
	       }


@ For this simple implementation, we just write on standard output.

@<Write...@>= print_node(stdout, indent_string, root);

@*Index.