Hweb Documentation
Directory Trees. Our object is to print out a directory hierarchy in some pleasant way. The program takes output from find * -type d -print \.| sort system dependencies and produces a nicer-looking listing. More precisely, our input, which is the output of find followed by 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 treeprint routine takes one option, |"-p, which tells it to use the printer's line-drawing set, rather than the terminal's.
<Global definitions>
<Global include files>
<Global declarations>;
main
# 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...>=
<|main| variable...>
Input. Reading the tree is simple-we read one line at a time, and call on the recursive |add_tree| procedure.
<If |buf| contains a newline, make it end there>;
add_tree(rootptr, buf);
<Global include...>=
Depending what system you're on, you may or may not get a newline
in |buf|.
<If |buf| contains a newline...>= p=buf;
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.
<Break up the string so |p| is the first word, |s| points at null-begun remainder, and |slashed| tells whether |*s=='/'| on entry>;
<Allocate new node to hold string of size |strlen(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.
Node allocation is perfectly standard ... @<Allocate new node...@>=
*rootptr=(struct tnode *) malloc (sizeof(struct tnode)); (*rootptr)->left
= (*rootptr)->right = NULL; (*rootptr)->data = malloc (strlen(p)+1);
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@>
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 - 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.
Children get printed before siblings. We don't bother trying to bring children up to the same line as their parents, because the / 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)
print_node See Code for print_node(fp,
indent_string, node)
/* Add vertical bar or space for this sibling ;
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);