% Copyright 1987 Norman Ramsey -- Rutgers University % Adapted to CWEB version 3.0 by Marc van Leeuwen -- CWI Amsterdam @*Directory Trees. The object is to print out a directory hierarchy in some pleasant way. The program takes output from \.{find * -type d -print {\v} sort} @^system dependencies@> and produces a nicer-looking listing. For those of you who may not have \.{find} or \.{sort}, the output is a list of fully qualified directory names (parent and child separated by slashes |'/'|), and everything is already nicely sorted in lexicographic order. |treeprint| takes one option, |"-p"|, which tells it to use the printer's line-drawing set, rather than the terminal's. @h @c @< Global declarations @>@; main(int argc, char** argv) { @< Variable declaration for |main| @> @; @< 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; @ @< Variable declaration for |main| @>= struct tnode *root; @*Input. Reading the tree is simple---we read one line at a time, and call on the recursive |add_tree| procedure. @c read_tree (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); } } @ 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(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. |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 wether 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@> @d svert '|' @d shoriz '-' @d scross '+' @d scorner '\\' /* lower left corner */ @d pvert '|' @d phoriz '-' @d pcross '+' @d 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 \caps{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(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.