Plan 9 from Bell Labs’s /usr/web/sources/contrib/arisawa/rit/rit.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


/*
*	Rit version 1.5
*
*	Rit is a PHP like language that can process
*	rc scripts embedded in text.
*	The name came from "rc in text".
*
*	The rules:
*		${ ... }
*		${ ... }$
*		$var
*		$NL
*	where NL is new line
*	and var is a shell variable of a sequence of alpha, numeric and '_'
*
*	Operation rank:
*		1. }$
*		2. $$$...
*		3. ${ , $var, $NL
*
*	Rit source is in http://plan9.aichi-u.ac.jp/netlib/cmd/rit/
*	Look http://plan9.aichi-u.ac.jp/rit/rit-1.5.html for the document
*
*	date: 2007/10/08
*	-Kenar-
*/


#include <u.h>
#include <libc.h>
#include <ctype.h>
#include <bio.h>
#define Brdln(b)	Brdstr(b,'\n',1)
#define DBG if(debug)

/*	Note on Japanese code
*	Don't use Bprint for string output
*	Bprint does not handle Japanese EUC document
*
*	Note on output buffering
*	In web application, it is better to use output buffer
*	for efficient networking.
*	Pegasus do that automatically.
*/

#define idchars(c)	(isalnum(c)||c=='_')

static char *skip(char *s, char *p);
static char *skipto(char *s, char *p);
static char *skipvar(char *s);
static void	translate(char *line);

static char *usage="usage: rit [-Dbes] [-r begin,end] [file [arg ...]]";
static Biobuf bout, *binp;
static int bflag = 0;
static int eflag = 0;
static int	skipaline = 0;
static rcfd = 0;
static synfd = 0;
static char *synstr=nil;
static int debug = 0;
static int instr = 0;
static int inbrace = 0;
static char *path = nil;
static char *pbegin = nil;
static char *pend = nil;


char*
strtrim(char *s)
{
	char *t;
	while(isspace(*s))
		s++;
	t = strchr(s, 0);
	t--;
	while(isspace(*t ))
		t--;
	t++;
	*t = 0;
	return s;
}


static void
command(char *cmd)
{
	int n, m;
	int cflag=0;

	DBG
		fprint(2, "#command: instr=%d inbrace=%d <%s>\n", 
		instr, inbrace, cmd);

	m = strlen(cmd);
	/* o byte write makes a problem */
	if(m > 0){
		if(cmd[m-1] == '\\'){
			m--;
			cflag = 1;
		}
		n = write(rcfd, cmd, m);
		if(n != m)
			sysfatal("# command: write: %r");
	}
	if(cflag)
		return;
	n = write(rcfd,"\n", 1);
	if(n != 1)
		sysfatal("# command: write: %r");
	DBG fprint(2, "#command done\n");
}

void
sync(void)
{
	char *p, buf[4096];
	int n,m;
	DBG fprint(2, "#sync ...\n");	
	command("echo -n $synstr >[1=2]");
	/* we should make room for last null */
	m = strlen(synstr);
	while((n = read(synfd, buf, sizeof buf - 1)) > 0){
		buf[n] = 0;
		DBG  fprint(2, "#sync: <%s>\n", buf);
		p = strtrim(buf);
		n = strlen(p);
		/*	we must discard synstr
			assume:
			synstr = "pro:" then m=4
			buf = "blablapro:" then n=10
			we must compare: strncmp(buf + 6, synstr, 4)
		*/
		if(strcmp(p + n - m, synstr) == 0){
			if(n > m)
				p[n-m] = 0;
			break;
		}
		if(strcmp(p,"exit") == 0)
			exits(nil);
		if(strncmp(p,"exit ", 5) == 0)
			exits(p + 5);
		if(eflag)
			sysfatal("# Rc error: %s: %r", p);
		else
			fprint(2, "# Rc error: %s: %r\n", p);
	}
	if(n < 0)
		sysfatal("# sync: %r");
	DBG fprint(2, "#sync done\n");
}


static char *
skip(char *s, char *p)
{
	while(*s && strchr(p, *s))
		s++;
	return s;
}

static char *
skipto(char *s, char *p)
{
	char *s0;
	s0 = s;
	while(*s){
		if(!instr){
			if(inbrace == 0 && strchr(p, *s))
				break;
			switch(*s){
			case '#':
				if(s != s0 && *(s-1) == '$')
					break;
				while(*s) s++;
				return s;
			case '{':
				inbrace++;
				break;
			case '}':
				if(inbrace == 0)
					return s;
				inbrace--;
				break;
			default:
				break;
			}
		}
		if(*s == '\'')
			instr = !instr;
		s++;
	}
	return s;
}

static char *
skipvar(char *s)
{
	while(*s && idchars(*s))
		s++;
	return s;
}

void
newrc(char *argv[])
{
	int n, fd[2];
	char *args[16];
	rfork(RFNAMEG);
	if(bind("#|", "/tmp", MBEFORE)<0)
		sysfatal("bind: %r");
	if(pipe(fd)<0)
		sysfatal("pipe: %r");
	switch(rfork(RFFDG|RFENVG|RFREND|RFPROC)){
	case -1:
		sysfatal("# fork:%r");
	case 0: /* child */
		close(2);
		dup(fd[1], 2);
		close(fd[1]);
		putenv("synstr", synstr);
		args[0] = "rc";
		args[1] = "/tmp/data1";
		if(*argv)
			argv++;
		for(n = 2; n < 16 && *argv; n++, argv++)
			args[n] = *argv;
		args[n] = nil;
		exec("/bin/rc", args);
		sysfatal("# execl: %r");
	default: /* parent */
		if(*argv && strcmp(*argv, ".") != 0)
			close(0);
		close(fd[1]);
		break;
	}
	synfd = fd[0];
	rcfd =  open("/tmp/data", OWRITE);
	if(rcfd < 0)
		sysfatal("newrc: %r");
}

/*	translate a document line
*
*	translation syntax
*	here | [ ] <> and := are meta symbol
*
*	SP := ' '	# space
*	NL := '\n'	# new line
*	LINE := ELEM | ELEM LINE
*	DOLS := '$' | '$' DOLS
*	VAR := <rc environment variable>
*	COMD := <rc command line>
*	COMDS := COMD | COMD NL COMDS
*	TEXT := <text>
*	ELEM := TEXT | DOLS | DOLS VAR | DELS '{' CMDS '}' | DOLS NL
*/

/*	translate: translate a line
*	NOTE: trailing '\n' is already stripped	*/
static void
translate(char *line)
{
	char buf[256], *p, *p0, ch;
	int esc=0;
	p0 = line;
	DBG fprint(2,"#translate <%s>\n", line);

	while(*p0){
		DBG fprint(2,"#processing <%s>\n", p0);
		p = p0;
		if(*p == '$'){
			p++;
			switch(*p){
			case 0:
				/* new line escape */
				esc = 1;
				break;
			case '{':
				/*	command	field	*/
				p++;
				p0=p;
				instr = inbrace = 0;
				p = skipto(p0, "}");
				ch = *p;
				*p = 0;
				while(ch != '}'){
					command(p0);
					line = Brdln(binp);
					if(line == nil){
						sync();
						sysfatal("# Premature EOF");
					}
					/* continue next line	*/
					p0 = line;
					p = skipto(line,"}");
					ch = *p;
					*p = 0;
				}
				p++;
				if(*p == '$'){ /*	}$ ...  */
					/* 	NOTE: don't write like this:
					*	snprint(buf, sizeof buf, "%s | %s -c", p0, path);
					*	this makes an endless recursion if rit is used in script
					*	of #!/bin/rit type.
					*	It is inconvenient to preinstall rit to /bin
					*	but I don't have a solution */
					char *q;
					int n;
					q = strtrim(p0);
					n = strlen(q);
					if(n > 0 && q[n-1] != ';'){
						snprint(buf, sizeof buf, "%s | /bin/rit -c", p0);
						p0 = buf;
						DBG fprint(2, "}$: %s\n", p0);
					}
					p++;
				}
				command(p0);
				sync();
				break;
			case '$':
				/*	we handle: $$...	*/
				p0 = p;
				p = skip(p,"$");
				ch = *p;
				*p = 0;
				Bwrite(&bout, p0, strlen(p0));
				*p = ch;
				break;
			default:
				/*	possibly a variable	*/
				p0 = p;
				if(idchars(*p)){
					/*	variable
					*	for $bla then p is "bla"	*/
					p = skipvar(p);
					ch = *p;
					*p = 0;
					/*	we can't replace something like cat */
					snprint(buf, sizeof buf, "echo -n $%s", p0);
					instr = inbrace = 0;
					command(buf);
					sync();
					*p = ch;
				}
				else /* not a variable */
					Bwrite(&bout, "$", 1);				
				break;
			}
			p0 = p;
			if(esc)
				break;
		}

		/*	text field	*/
		p = strchr(p0, '$');
		if(p == nil)
			p = p0 + strlen(p0);
		ch = *p;
		*p = 0;
		Bwrite(&bout, p0, strlen(p0));
		Bflush(&bout);
		*p = ch;
		p0 = p;
	}
	if(!esc)
		Bwrite(&bout, "\n", 1);
	Bflush(&bout);
	DBG fprint(2,"#translate done\n");
}


static void
doit(char *file)
{
	Biobuf bin;
	char *line, buf[256], *p;
	int fd=0;

	if(file && strcmp(file,".") == 0)
		file = nil;
	if(file)
		fd = open(file, OREAD);
	if(fd < 0)
		sysfatal("# open: %r");
	if(Binit(&bin, fd, OREAD) < 0)
		sysfatal("# Binit: %r");
	binp = &bin;

	/*	If we have pbegin, we skip until the pattern	*/
	if(pbegin){
		while((line = Brdln(binp)) != nil){
			if(strcmp(line,pbegin) == 0)
				break;
		}
	}

	/*	basename of file is more useful in many web application */
	if(file && bflag){
		p = strrchr(file,'/');
		if(p)
			file = ++p;
	}

	/*	we cheat Rc */
	if(file)
		snprint(buf, sizeof buf, "0=%s", file);
	else
		snprint(buf, sizeof buf, "0='#d/0'");
	command(buf);

	/*	I could not get rc command exit status.
	*	therefore we get it in sync()	*/
	command("fn quit {echo exit $1 >[1=2]; exit}");
	sync();

	/*	Not that "/bin/echo -n $*" causes closing pipe
	*	if $* is empty. Therefore we replace "echo"
	*	by new one */
	command("fn echo {if(~ $1 '-n'){shift;if(~ $\"* ?*)/bin/echo -n $*};if not /bin/echo $*}");
	sync();

	if(skipaline)
		Brdln(binp);
	while((line = Brdln(binp)) != nil){
		if(pend && strcmp(line,pend) == 0)
			break;
		translate(line);
	}
	Bterm(binp);
}

void
cutnl(void)
{
	char buf[4096];
	int n,m;
	m = sizeof buf;
	while((n = read(0, buf, m)) == m)
		write(1, buf, m);
	if(n > 0 && buf[n-1] == '\n')
		n--;
	m = write(1, buf, n);
	if(n != m)
		sysfatal("cutnl: write: %r");
}

void
main(int argc, char *argv[])
{
	char *ep,buf[32];
	int i;
    char *r;
	path = *argv;
	ARGBEGIN{
	case 'b':
		/* gives basename for file */
		bflag = 1;
		break;
	case 'c':
		/* this option is used only for internally */
		cutnl();
		exits(nil);
	case 'e':
		eflag = 1;
		break;
	case 'r':
		r = ARGF();
		if(!r) sysfatal(usage);
		pbegin = r;
		r = strchr(r,',');
		if(!r) break;
		*r = 0;
		if(*pbegin == 0)
			pbegin = nil;
		pend = ++r;
		if(*pend == 0)
			pend = nil;
		break;
	case 's':
		skipaline = 1;
		break;
	case 'D':
		debug = 1;
		break;
	default:
		sysfatal(usage);
	}ARGEND

	if(access("/bin/rit", 0))
		sysfatal("Please install Rit");

	Binit(&bout, 1, OWRITE);

	/* making a random string */
	srand(time(nil));
	ep = buf + sizeof buf;
	buf[0] = '#';
	for(i = 0; i < 10; i++) // bufsize must be > 2*10+2
		seprint(buf+2*i+1,ep, "%2.2ux", rand());
	strcat(buf,"#");
	synstr = buf;
	DBG fprint(2,"synstr: %s\n", synstr);

	newrc(argv);
	doit(*argv);

	Bterm(&bout);
	exits(nil);
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.