/*
* 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);
}
|