aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile19
-rwxr-xr-xarr119
-rw-r--r--arr.c311
3 files changed, 119 insertions, 330 deletions
diff --git a/Makefile b/Makefile
deleted file mode 100644
index db9df32..0000000
--- a/Makefile
+++ /dev/null
@@ -1,19 +0,0 @@
-ALL=arr
-
-CFLAGS=-g -O2 -Wall -Wextra -Wwrite-strings
-
-DESTDIR=
-PREFIX=/usr/local
-BINDIR=$(PREFIX)/bin
-MANDIR=$(PREFIX)/share/man
-
-all: $(ALL)
-
-clean: FRC
- rm -f $(ALL)
-
-install: FRC all
- mkdir -p $(DESTDIR)$(BINDIR) $(DESTDIR)$(MANDIR)/man1
- install -m0755 $(ALL) $(DESTDIR)$(BINDIR)
-
-FRC:
diff --git a/arr b/arr
new file mode 100755
index 0000000..8d5ba5a
--- /dev/null
+++ b/arr
@@ -0,0 +1,119 @@
+#!/usr/bin/env ruby
+# arr EXPR [FILES...] - (re)arrange and select fields on each line
+#
+# To the extent possible under law,
+# Christian Neukirchen <chneukirchen@gmail.com>
+# has waived all copyright and related or neighboring rights to this work.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+USAGE = <<'EOF'
+Usage: arr [-0] [-P|-p PADDING] EXPR [FILES...]
+ EXPR ::= FIELDS (("|" CHAR | "*") FIELDS)* # | split on char, * split bytes
+ FIELDS ::= "~"? FIELD ("," FIELD)* # ~ negates
+ FIELD ::= "-"? "\d"+ # negative fields count from back
+ | ("-"? "\d"+)? ":" ("-"? "\d"+)? # range ends default to 1:-1
+EOF
+
+require 'strscan'
+require 'optparse'
+
+def fmt(ss, d)
+ last_split = " "
+
+ loop {
+ fields = []
+ neg = false
+
+ begin
+ i = j = nil
+ neg = true if ss.scan(/~/)
+ if ss.scan(/:|-?\d+/)
+ i = Integer(ss.matched) rescue 1
+ i = i < 0 ? d.size + i : i - 1
+ if ss.matched == ":" || ss.scan(/:/)
+ if ss.scan(/-?\d+/)
+ j = Integer(ss.matched)
+ else
+ j = -1
+ end
+ j = j < 0 ? d.size + j : j - 1
+
+ if j > i
+ fields.concat (i..j).to_a
+ else
+ fields.concat (j..i).to_a.reverse
+ end
+ else
+ fields << i
+ end
+ else
+ abort "parse error at #{ss.inspect}"
+ end
+ end while ss.scan(/,/)
+
+ d = d.values_at(*if neg
+ (0..d.size).to_a - fields
+ else
+ fields
+ end)
+
+ if ss.scan(/\|(.)/)
+ d = d.join(last_split)
+ last_split = ss[1]
+ d = d.split(last_split)
+ elsif ss.scan(/\*/)
+ d = d.join(last_split)
+ last_split = ""
+ d = d.split('')
+ else
+ break
+ end
+ }
+
+ unless ss.scan(/\}/)
+ abort "parse error at #{ss.inspect}"
+ end
+
+ d.compact.join(last_split)
+end
+
+def fmt2(str, arr)
+ ss = StringScanner.new(str)
+
+ r = ""
+
+ while ss.scan(/(.*?)%\{/)
+ r << ss[1]
+ r << fmt(ss, arr)
+ end
+
+ r << ss.rest
+end
+
+begin
+ params = ARGV.getopts('0Pp:')
+ nl = params["0"] ? "\0" : "\n"
+ padding = params["p"] || ""
+ padding = nil if params["P"]
+
+ expr = ARGV.shift or raise OptionParser::MissingArgument, "no EXPR given"
+
+ ARGV << "-" if ARGV.empty?
+
+ files = ARGV.map { |name|
+ if name == "-"
+ STDIN
+ else
+ File.open(name, "rb")
+ end
+ }
+
+ until files.all?(&:eof?)
+ lines = files.map { |f| f.gets(nl).chomp(nl) rescue padding }
+ break if lines.include?(nil)
+ print fmt2(expr, lines), nl
+ end
+rescue OptionParser::ParseError
+ STDERR.puts $!
+ STDERR.puts USAGE
+end
diff --git a/arr.c b/arr.c
deleted file mode 100644
index 552cbf4..0000000
--- a/arr.c
+++ /dev/null
@@ -1,311 +0,0 @@
-// arr - (re)arrange and select fields on each line
-
-/*
-##% gcc -Os -Wall -g -o $STEM $FILE -Wextra -Wwrite-strings
-*/
-
-#include <ctype.h>
-#include <errno.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-
-char lastsplit;
-static char *s;
-
-// "%{" FIELD (CHOP FIELD)* (("|" | "*") FIELD ((, | :) FIELD)*)? "}"
-
-void
-fmt_range(char **args, int bytewise, size_t argsnum)
-{
- long n = 0;
- char *end;
-
- int printed = 0;
-
- while (*s) {
- switch (*s) {
- case '0': case '1': case '2': case '3': case '4':
- case '5': case '6': case '7': case '8': case '9':
- case '-':
- errno = 0;
- n = strtol(s, &end, 10);
- if (errno != 0) {
- fprintf(stderr, "can't parse number at '%s'.\n", s);
- exit(1);
- }
- s = end;
- if (bytewise) {
- if (n < 0)
- n += argsnum + 1;
- if (n < 0)
- n = 0;
- if (n >= 1 && n <= argsnum)
- printf("%c", args[0][n-1]);
- } else {
- if (n < 0)
- n += argsnum;
- if (printed++)
- printf("%c", lastsplit);
- if (n > 0 && n < argsnum)
- printf("%s", args[n]);
- }
- break;
- case ',':
- n = 0;
- s++;
- break;
- case ':': {
- s++;
- long l = n;
- errno = 0;
- n = strtol(s, &end, 10);
- if (errno != 0) {
- fprintf(stderr, "can't parse number at '%s'.\n", s);
- exit(1);
- }
- if (s == end) // default to -1
- n = -1;
- s = end;
- if (bytewise) {
- if (n < 0)
- n += argsnum + 1;
- if (n < 0)
- n = 0;
- if (n > argsnum)
- n = argsnum;
- if (l <= n)
- for (l++; l <= n; l++)
- printf("%c", args[0][l-1]);
- else
- for (l--; l >= n; l--)
- printf("%c", args[0][l-1]);
- } else {
- if (n < 0)
- n += argsnum;
- if (n >= argsnum)
- n = argsnum-1;
- if (l <= n)
- for (l++; l <= n; l++) {
- if (printed++)
- printf("%c", lastsplit);
- printf("%s", args[l]);
- }
- else
- for (l--; l >= n; l--) {
- if (printed++)
- printf("%c", lastsplit);
- printf("%s", args[l]);
- }
- }
- break;
- }
- case '}':
- s++;
- return;
- default:
- fprintf(stderr, "invalid range at '%s'\n", s);
- exit(1);
- }
- }
-}
-
-void
-fmt_inner(char **args, size_t argsnum)
-{
- long field, newfield;
- char *end;
- int i;
-
- field = 0;
-
- while (*s) {
- switch (*s) {
- case '0': case '1': case '2': case '3': case '4':
- case '5': case '6': case '7': case '8': case '9':
- case '-':
- errno = 0;
- newfield = strtol(s, &end, 10);
- if (errno != 0) {
- fprintf(stderr, "can't parse number at '%s'.\n", s);
- exit(1);
- }
- if (s == end && *s == '-')
- goto split;
- s = end;
- if (newfield < 0)
- newfield += argsnum;
- field = newfield;
- break;
- case '|':
- s++;
- fmt_range(args, 0, argsnum);
- return;
- case '*':
- s++;
- if (field >= 0 && field < argsnum)
- fmt_range(args+field, 1, strlen(args[field]));
- else
- fmt_range(args, 1, 0);
- return;
- case '}':
- s++;
- if (field >= 1 && field < argsnum)
- printf("%s", args[field]);
- return;
- case '"':
- s++;
- lastsplit = *s;
- s++;
- if (*s != '"') {
- fprintf(stderr, "invalid syntax '\"%c%s'\n",
- lastsplit, s);
- exit(1);
- }
- s++;
- goto split2;
- default: { /* split at char, recurse */
- split:
- lastsplit = *s;
- s++;
- split2:;
- char *t;
- if (field >= 1 && field < argsnum)
- t = strdup(args[field]);
- else
- t = strdup("");
- char **newargs = calloc(32 /*XXX*/, sizeof (char *));
- if (lastsplit == ' ') { // split on any ws
- while (isspace((unsigned char)*t))
- t++;
- newargs[1] = t;
- for (i = 2; *t; t++) {
- if (isspace((unsigned char)*t)) {
- while (isspace((unsigned char)*(t+1)))
- *t++ = 0;
- newargs[i++] = t + 1;
- }
- }
- } else {
- newargs[1] = t;
- for (i = 2; *t; t++) {
- if (*t == lastsplit) {
- *t = 0;
- newargs[i++] = t + 1;
- }
- }
- }
- args = newargs;
- argsnum = i;
- field = 0;
- }
- }
- }
-}
-
-void
-fmt(const char *pattern, char **args, size_t argsnum)
-{
- s = pattern;
- lastsplit = '\t';
-
- while (*s) {
- switch (*s) {
- case '%':
- s++;
- switch (*s) {
- case '%':
- s++;
- putchar('%');
- break;
- case '{':
- s++;
- fmt_inner(args, argsnum);
- break;
- default:
- putchar('%');
- }
- break;
- default:
- putchar(*s);
- s++;
- }
- }
-}
-
-int
-main(int argc, char *argv[]) {
- // number of implicit stdin arguments
- int stdins = 0;
-
- char c;
- char *padding = "";
- int delim = '\n';
-
- while ((c = getopt(argc, argv, "0Pp:")) != -1)
- switch(c) {
- case '0': delim = '\0'; break;
- case 'p': padding = optarg; break;
- case 'P': padding = 0; break;
- default: goto usage;
- }
-
- if (optind >= argc) {
-usage:
- fprintf(stderr, "Usage: %s FMT FILES...\n", argv[0]);
- exit(1);
- }
-
- int argnum = argc - optind;
- char **arglist = argv + optind; // starts counting at 1
-
- // default to stdin when no file arguments are given
- if (argnum <= 1)
- stdins++;
-
- FILE **files = calloc(argnum+stdins, sizeof (FILE *));
- char **lines = calloc(argnum+stdins, sizeof (char *));
- int i;
- for (i = 1; i < argnum+stdins; i++) {
- if (i >= argnum || strcmp(arglist[i], "-") == 0) {
- files[i] = stdin;
- } else {
- files[i] = fopen(arglist[i], "r");
- if (!files[i]) {
- fprintf(stderr, "%s: %s: %s\n",
- argv[0], arglist[i], strerror(errno));
- exit(1);
- }
- }
- }
-
- int eof;
- size_t len;
- while (1) {
- eof = 0;
- for (i = 1; i < argnum+stdins; i++) {
- int read = getdelim(lines+i, &len, delim, files[i]);
- if (read == -1) {
- if (feof(files[i])) {
- if (!padding)
- exit(0);
- lines[i] = strdup(padding);
- eof++;
- } else {
- exit(1);
- }
- } else {
- // strip delimiter
- if (lines[i][read-1] == delim)
- lines[i][read-1] = 0;
- }
- }
- if (eof >= argnum+stdins-1)
- break;
- fmt(arglist[0], lines, argnum+stdins);
- printf("%c", delim);
- }
-
- return 0;
-}