diff options
| author | Charles.Forsyth <devnull@localhost> | 2006-12-22 17:07:39 +0000 |
|---|---|---|
| committer | Charles.Forsyth <devnull@localhost> | 2006-12-22 17:07:39 +0000 |
| commit | 37da2899f40661e3e9631e497da8dc59b971cbd0 (patch) | |
| tree | cbc6d4680e347d906f5fa7fca73214418741df72 /appl/cmd/sh/csv.b | |
| parent | 54bc8ff236ac10b3eaa928fd6bcfc0cdb2ba46ae (diff) | |
20060303a
Diffstat (limited to 'appl/cmd/sh/csv.b')
| -rw-r--r-- | appl/cmd/sh/csv.b | 244 |
1 files changed, 244 insertions, 0 deletions
diff --git a/appl/cmd/sh/csv.b b/appl/cmd/sh/csv.b new file mode 100644 index 00000000..601032d6 --- /dev/null +++ b/appl/cmd/sh/csv.b @@ -0,0 +1,244 @@ +implement Shellbuiltin; + +# parse/generate comma-separated values. + +include "sys.m"; + sys: Sys; +include "draw.m"; +include "sh.m"; + sh: Sh; + Listnode, Context: import sh; + myself: Shellbuiltin; +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +initbuiltin(ctxt: ref Context, shmod: Sh): string +{ + sys = load Sys Sys->PATH; + sh = shmod; + myself = load Shellbuiltin "$self"; + if (myself == nil) + ctxt.fail("bad module", sys->sprint("csv: cannot load self: %r")); + bufio = load Bufio Bufio->PATH; + if (bufio == nil) + ctxt.fail("bad module", + sys->sprint("csv: cannot load: %s: %r", Bufio->PATH)); + ctxt.addbuiltin("getcsv", myself); + ctxt.addsbuiltin("csv", myself); + return nil; +} + +whatis(nil: ref Sh->Context, nil: Sh, nil: string, nil: int): string +{ + return nil; +} + +getself(): Shellbuiltin +{ + return myself; +} + +runbuiltin(c: ref Sh->Context, nil: Sh, + cmd: list of ref Sh->Listnode, last: int): string +{ + return builtin_getcsv(c, cmd, last); +} + +runsbuiltin(c: ref Sh->Context, nil: Sh, + cmd: list of ref Sh->Listnode): list of ref Listnode +{ + return sbuiltin_csv(c, cmd); +} + +builtin_getcsv(ctxt: ref Context, argv: list of ref Listnode, nil: int) : string +{ + n := len argv; + if (n != 2 || !iscmd(hd tl argv)) + builtinusage(ctxt, "getcsv {cmd}"); + cmd := hd tl argv :: ctxt.get("*"); + stdin := bufio->fopen(sys->fildes(0), Sys->OREAD); + if (stdin == nil) + ctxt.fail("bad input", sys->sprint("getcsv: cannot open stdin: %r")); + status := ""; + ctxt.push(); + for(;;){ + { + for (;;) { + line: list of ref Listnode = nil; + sl := readcsvline(stdin); + if (sl == nil) + break; + for (; sl != nil; sl = tl sl) + line = ref Listnode(nil, hd sl) :: line; + ctxt.setlocal("line", line); + status = setstatus(ctxt, ctxt.run(cmd, 0)); + } + ctxt.pop(); + return status; + } + exception e{ + "fail:*" => + ctxt.pop(); + if (loopexcept(e) == BREAK) + return status; + ctxt.push(); + } + } +} + +CONTINUE, BREAK: con iota; +loopexcept(ename: string): int +{ + case ename[5:] { + "break" => + return BREAK; + "continue" => + return CONTINUE; + * => + raise ename; + } + return 0; +} + +iscmd(n: ref Listnode): int +{ + return n.cmd != nil || (n.word != nil && n.word[0] == '{'); +} + +builtinusage(ctxt: ref Context, s: string) +{ + ctxt.fail("usage", "usage: " + s); +} + +setstatus(ctxt: ref Context, val: string): string +{ + ctxt.setlocal("status", ref Listnode(nil, val) :: nil); + return val; +} + +# in csv format, is it possible to distinguish between a line containing +# one empty field and a line containing no fields at all? +# what does each one look like? +readcsvline(iob: ref Iobuf): list of string +{ + sl: list of string; + + for(;;) { + (s, eof) := readcsvword(iob); + if (sl == nil && s == nil && eof) + return nil; + + c := Bufio->EOF; + if (!eof) + c = iob.getc(); + sl = s :: sl; + if (c == '\n' || c == Bufio->EOF) + return sl; + } +} + +sbuiltin_csv(nil: ref Context, val: list of ref Listnode): list of ref Listnode +{ + val = tl val; + if (val == nil) + return nil; + s := s2qv(word(hd val)); + for (val = tl val; val != nil; val = tl val) + s += "," + s2qv(word(hd val)); + return ref Listnode(nil, s) :: nil; +} + +s2qv(s: string): string +{ + needquote := 0; + needscan := 0; + for (i := 0; i < len s; i++) { + c := s[i]; + if (c == '\n' || c == ',') + needquote = 1; + else if (c == '"') { + needquote = 1; + needscan = 1; + } + } + if (!needquote) + return s; + if (!needscan) + return "\"" + s + "\""; + r := "\""; + for (i = 0; i < len s; i++) { + c := s[i]; + if (c == '"') + r[len r] = c; + r[len r] = c; + } + r[len r] = '"'; + return r; +} + +readcsvword(iob: ref Iobuf): (string, int) +{ + s := ""; + case c := iob.getc() { + '"' => + for (;;) { + case c = iob.getc() { + Bufio->EOF => + return (s, 1); + '"' => + case c = iob.getc() { + '"' => + s[len s] = '"'; + '\n' or + ',' => + iob.ungetc(); + return (s, 0); + Bufio->EOF => + return (s, 1); + * => + # illegal + iob.ungetc(); + (t, eof) := readcsvword(iob); + return (s + t, eof); + } + * => + s[len s] = c; + } + } + ',' or + '\n' => + iob.ungetc(); + return (s, 0); + Bufio->EOF => + return (nil, 1); + * => + s[len s] = c; + for (;;) { + case c = iob.getc() { + ',' or + '\n' => + iob.ungetc(); + return (s, 0); + '"' => + # illegal + iob.ungetc(); + (t, eof) := readcsvword(iob); + return (s + t, eof); + Bufio->EOF => + return (s, 1); + * => + s[len s] = c; + } + } + } +} + +word(n: ref Listnode): string +{ + if (n.word != nil) + return n.word; + if (n.cmd != nil) + n.word = sh->cmd2string(n.cmd); + return n.word; +} |
