/* * 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 #include #include #include #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 := * COMD := * COMDS := COMD | COMD NL COMDS * 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); }