home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Fresh Fish 2
/
FFMCD02.bin
/
new
/
dev
/
misc
/
cweb
/
examples
/
treeprint.w
< prev
next >
Wrap
Text File
|
1993-12-21
|
7KB
|
248 lines
\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;
@*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.