summary refs log tree commit diff stats
path: root/compiler
diff options
context:
space:
mode:
authorTimothee Cour <timothee.cour2@gmail.com>2019-01-09 00:46:44 -0800
committerAndreas Rumpf <rumpf_a@web.de>2019-01-09 09:46:44 +0100
commit23c1ee982e2d3795001879a4527581f33875cd33 (patch)
tree799acb5b12ca6f24796213a54d4ed9baf6d70beb /compiler
parent258952832f312a25d0ab9771237a547297b336b8 (diff)
downloadNim-23c1ee982e2d3795001879a4527581f33875cd33.tar.gz
add `alignTable`, `parseTableCells` to align/format a tab(etc) delimited table (#10182)
* add compiler/unittest_light.nim for easy diffing: assertEquals and mismatch
* fixup
* add alignTable, parseTableCells
Diffstat (limited to 'compiler')
-rw-r--r--compiler/asciitables.nim83
-rw-r--r--compiler/unittest_light.nim37
2 files changed, 120 insertions, 0 deletions
diff --git a/compiler/asciitables.nim b/compiler/asciitables.nim
new file mode 100644
index 000000000..c25d54bde
--- /dev/null
+++ b/compiler/asciitables.nim
@@ -0,0 +1,83 @@
+#[
+move to std/asciitables.nim once stable, or to a nimble paackage
+once compiler can depend on nimble
+]#
+
+type Cell* = object
+  text*: string
+  width*, row*, col*, ncols*, nrows*: int
+
+iterator parseTableCells*(s: string, delim = '\t'): Cell =
+  ## iterates over all cells in a `delim`-delimited `s`, after a 1st
+  ## pass that computes number of rows, columns, and width of each column.
+  var widths: seq[int]
+  var cell: Cell
+  template update() =
+    if widths.len<=cell.col:
+      widths.setLen cell.col+1
+      widths[cell.col] = cell.width
+    else:
+      widths[cell.col] = max(widths[cell.col], cell.width)
+    cell.width = 0
+
+  for a in s:
+    case a
+    of '\n':
+      update()
+      cell.col = 0
+      cell.row.inc
+    elif a == delim:
+      update()
+      cell.col.inc
+    else:
+      # todo: consider multi-width chars when porting to non-ascii implementation
+      cell.width.inc
+  if s.len > 0 and s[^1] != '\n':
+    update()
+
+  cell.ncols = widths.len
+  cell.nrows = cell.row + 1
+  cell.row = 0
+  cell.col = 0
+  cell.width = 0
+
+  template update2() =
+    cell.width = widths[cell.col]
+    yield cell
+    cell.text = ""
+    cell.width = 0
+    cell.col.inc
+
+  template finishRow() =
+    for col in cell.col..<cell.ncols:
+      cell.col = col
+      update2()
+    cell.col = 0
+
+  for a in s:
+    case a
+    of '\n':
+      finishRow()
+      cell.row.inc
+    elif a == delim:
+      update2()
+    else:
+      cell.width.inc
+      cell.text.add a
+
+  if s.len > 0 and s[^1] != '\n':
+    finishRow()
+
+proc alignTable*(s: string, delim = '\t', fill = ' ', sep = " "): string =
+  ## formats a `delim`-delimited `s` representing a table; each cell is aligned
+  ## to a width that's computed for each column; consecutive columns are
+  ## delimted by `sep`, and alignment space is filled using `fill`.
+  ## More customized formatting can be done by calling `parseTableCells` directly.
+  for cell in parseTableCells(s, delim):
+    result.add cell.text
+    for i in cell.text.len..<cell.width:
+      result.add fill
+    if cell.col < cell.ncols-1:
+      result.add sep
+    if cell.col == cell.ncols-1 and cell.row < cell.nrows - 1:
+      result.add '\n'
diff --git a/compiler/unittest_light.nim b/compiler/unittest_light.nim
new file mode 100644
index 000000000..bcba6f7c7
--- /dev/null
+++ b/compiler/unittest_light.nim
@@ -0,0 +1,37 @@
+# note: consider merging tests/assert/testhelper.nim here.
+
+proc mismatch*[T](lhs: T, rhs: T): string =
+  ## Simplified version of `unittest.require` that satisfies a common use case,
+  ## while avoiding pulling too many dependencies. On failure, diagnostic
+  ## information is provided that in particular makes it easy to spot
+  ## whitespace mismatches and where the mismatch is.
+  proc replaceInvisible(s: string): string =
+    for a in s:
+      case a
+      of '\n': result.add "\\n\n"
+      else: result.add a
+
+  proc quoted(s: string): string = result.addQuoted s
+
+  result.add "\n"
+  result.add "lhs:{\n" & replaceInvisible(
+      $lhs) & "}\nrhs:{\n" & replaceInvisible($rhs) & "}\n"
+  when compiles(lhs.len):
+    if lhs.len != rhs.len:
+      result.add "lhs.len: " & $lhs.len & " rhs.len: " & $rhs.len & "\n"
+    when compiles(lhs[0]):
+      var i = 0
+      while i < lhs.len and i < rhs.len:
+        if lhs[i] != rhs[i]: break
+        i.inc
+      result.add "first mismatch index: " & $i & "\n"
+      if i < lhs.len and i < rhs.len:
+        result.add "lhs[i]: {" & quoted($lhs[i]) & "} rhs[i]: {" & quoted(
+            $rhs[i]) & "}"
+      result.add "lhs[0..<i]:{\n" & replaceInvisible($lhs[
+          0..<i]) & "}\nrhs[0..<i]:{\n" & replaceInvisible($rhs[0..<i]) & "}"
+
+proc assertEquals*[T](lhs: T, rhs: T) =
+  when false: # can be useful for debugging to see all that's fed to this.
+    echo "----" & $lhs
+  doAssert lhs==rhs, mismatch(lhs, rhs)
0 } /* Literal.String */ .highlight .na { color: #336699 } /* Name.Attribute */ .highlight .nb { color: #003388 } /* Name.Builtin */ .highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */ .highlight .no { color: #003366; font-weight: bold } /* Name.Constant */ .highlight .nd { color: #555555 } /* Name.Decorator */ .highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */ .highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */ .highlight .nl { color: #336699; font-style: italic } /* Name.Label */ .highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */ .highlight .py { color: #336699; font-weight: bold } /* Name.Property */ .highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */ .highlight .nv { color: #336699 } /* Name.Variable */ .highlight .ow { color: #008800 } /* Operator.Word */ .highlight .w { color: #bbbbbb } /* Text.Whitespace */ .highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */ .highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */ .highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */ .highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */ .highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ .highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */ .highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */ .highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */ .highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */ .highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ .highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */ .highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */ .highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ .highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ .highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ .highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #336699 } /* Name.Variable.Class */ .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
#! /usr/bin/perl -w
# Some scripts for handling mailto URLs within lynx via an interactive form
# 
# Warning: this is a quick demo, to show what kinds of things are possible
# by hooking some external commands into lynx.  Use at your own risk.
# 
# Requirements:
# 
# - Perl and CGI.pm.
# - A "sendmail" command for actually sending mail (if you need some
#   other interface, change the code below in sub sendit appropriately).
# - Lynx compiled with support for lynxcgi, that means EXEC_CGI must have
#   been defined at compilation, usually done with
#     ./configure --enable-cgi-links
# - Lynx must have support for CERN-style rules as of 2.8.3, which must
#   not have been disabled at compilation (it is enabled by default).
# 
# Instructions:
# (This is for people without lynxcgi experience; if you are already
# use lynxcgi, you don't have to follow everything literally, use
# common sense for picking appropriate file locations in your situation.)
# 
# - Make a subdirectory 'lynxcgi' under you home directory, i.e.
#      mkdir ~/lynxcgi
# - Put this three script file mailto-form.pl there and make it
#   executable.  For example,
#      cp mailto-form.pl ~/lynxcgi
#      chmod a+x ~/lynxcgi/mailto-form.pl
# - Edit mailto-form.pl (THIS FILE), there are some strings that
#   that need to be changed, see ### Configurable variables ###
#   below.
# - Allow lynx to execute lynxcgi files in that directory, for example,
#   put in your lynx.cfg file:
#      TRUSTED_LYNXCGI:<tab>/home/myhomedir/lynxcgi/mailto-form.pl
#   where <tab> is a real TAB character and you have to put the real
#   location of your directory in place of "myhomedir", of course.
#   The '~' abbreviation cannot be used.
#   You could also just enable execution of all lynxcgi scripts, by
#   not having any TRUSTED_LYNXCGI options in lynx.cfg at all, but
#   that can't be recommended.
# - Tell lynx to actually use the lynxcgi scripts for mailto URLs.
#   There are two variants:
#   a) Redirect "mailto"
#   Requires patched lynx, currently not yet in the developent code.
#   Use the following two lines in the file that is configured as
#   RULESFILE in lynxcfg:
#      PermitRedirection mailto:*
#      Redirect mailto:* lynxcgi:/home/myhomedir/lynxcgi/mailto-form.pl?from=myname@myhost&to=*
#   You can also put them directly in lynx.cfg, prefixing each with
#   "RULE:".  Replace ""myhomedir", "myname", and "myhost" with your
#   correct values, of course.
#   b) Redirect "xmailto"
#   Requires defining a fake proxy before starting lynx, like
#      export xmailto_proxy=dummy  # or for csh: setenv xmailto_proxy dummy
#   Requires that you change "mailto" to "xmailto" each time you want
#   to activate a mailto link.  This can be done conveniently with
#   a few keys: 'E', ^A, 'x', Enter.
#   Use the following two lines in the file that is configured as
#   RULESFILE in lynxcfg:
#      PermitRedirection xmailto:*
#      Redirect xmailto:* lynxcgi:/home/myhomedir/lynxcgi/mailto-form.pl?from=myname@myhost&to=*
#   You can also put them directly in lynx.cfg, prefixing each with
#   "RULE:".  Replace ""myhomedir", "myname", and "myhost" with your
#   correct values, of course.
# 
# Limitations:
# 
# - Only applies to mailto URLs that appear as links or are entered at
#   a 'g'oto prompt.  Does not apply to other ways of sending mail, like
#   the 'c' (COMMENT) key, mailto as a FORM action, or mailing a file
#   from the 'P'rinting Options screen.
# - Nothing is done for charset labelling, content-transfer-encoding
#   of non-ASCII characters, and other MIME niceties.
#
# Klaus Weide 20000712

########################################################################
########## Configurable variables ######################################

$SENDMAIL = '/usr/sbin/sendmail';
#                                   The location of your sendmail binary
$SELFURL = 'lynxcgi:/home/lynxdev/lynxcgi/mailto-form.pl';
#                                   Where this script lives in URL space
$SEND_TOKEN = '/vJhOp6eQ';
#                           When found in the PATH_INFO part of the URL,
#                           this causes the script to actually send mail
#                           by calling $SENDMAIL instead of just throwing
#                           up a form.  CHANGE IT!  And don't tell anyone!
#                           Treat it like a password.
#                           Must start with '/', probably should have only
#                           alphanumeric ASCII characters.

## Also, make sure the first line of this script points
## to your PERL binary

########## Nothing else to change - I hope #############################
########################################################################

use CGI;

$|=1;

### Upcase first character
##sub ucfirst {
##    s/^./\U$1/;
##}

# If there are multiple occurrences of the same thing, how to join them
# into one string
%joiner = (from => ', ',
	   to => ', ',
	   cc => ', ',
	   subject => '; ',
	   body => "\n\n"
	   );
sub joiner {
    my ($key) = @_;
    if ($joiner{$key}) {
	$joiner{$key};
    } else {
	" ";
    }
}

# Here we check whether this script is called for actual sending, rather
# than form generation.  If so, all the rest is handled by sub sendit, below.
$pathinfo = $ENV{'PATH_INFO'}; 
if (defined($pathinfo) && $pathinfo eq $SEND_TOKEN) {
    $q = new CGI;
    print $q->header('text/plain');
    sendit();
    exit;
}

$method = $ENV{'REQUEST_METHOD'};
$querystring = $ENV{'QUERY_STRING'};
if ($querystring) {
    if ($method && $method eq "POST" && $ENV{'CONTENT_LENGTH'}) {
	$querystring =~ s/((^|\&)to=[^?&]*)\?/$1&/;
	$q0 = new CGI;
	$q = new CGI($querystring);
	@fields = $q0->param();
	foreach $key (@fields) {
	    @vals = $q0->param($key);
#	    print "Content-type: text/html\n\n";
#	    print "Appending $key to \$q...\n";
	    $q->append($key, @vals);
#	    print "<H2>Current Values in \$q0</H2>\n";
#	    print $q0->dump;
#	    print "<H2>Current Values in \$q</H2>\n";
#	    print $q->dump;

	}

    } else {
	$querystring =~ s/((^|\&)to=[^?&]*)\?/$1&/;
	$q = new CGI($querystring);
    }
} else {
    $q = new CGI;
}

print $q->header;

$long_title = $ENV{'QUERY_STRING'};
$long_title =~ s/^from=([^&]*)\&to=//;
$long_title = "someone" unless $long_title;
$long_title = "Compose mail for $long_title";
if (length($long_title) > 72) {
    $title = substr($long_title,0,72) . "...";
} else {
    $title = $long_title;
}
$long_title =~ s/&/&amp;/g;
$long_title =~ s/</&lt;/g;
print
    $q->start_html($title), "\n",
    $q->h1($long_title), "\n",
    $q->start_form(-method=>'POST', -action => $SELFURL . $SEND_TOKEN), "\n";

print "<TABLE>\n";
@fields = $q->param();
foreach $key (@fields) {
    @vals = $q->param($key);
    if (scalar(@vals) != 1) {
	print "multiple values " . scalar(@vals) ." for $key!\n";
	$q->param($key, join (joiner($key), @vals));
    }
}
foreach $key (@fields) {
    $_ = lc($key);
    if ($_ ne $key) {
	print "noncanonical case for $key!\n";
	$val=$q->param($key);
	$q->delete($key);
	if (!$q->param($_)) {
	    $q->param($_, $val);
	} else {
	    $q->param($_, $q->param($_) . joiner($_) . "$val");
	}
    }
}
foreach $key ('from', 'to', 'cc', 'subject') {
    print $q->Tr,
    $q->td(ucfirst($key) . ":"),
    $q->td($q->textfield(-name=>$key,
			 -size=>60,
			 -default=>$q->param($key))), "\n";
    $q->delete($key);
}

# Also pass on any unrecognized header fields that were specified.
# This may not be a good idea for general use!
# At least some dangerous header fields may have to be suppressed.
@keys = $q->param();
if (scalar(@keys) > (($q->param('body')) ? 1 : 0)) {
    print "<TR><TD colspan=2><EM>Additional headers:</EM>\n";
    foreach $key ($q->param()) {
	if ($key ne 'body') {
	    print $q->Tr,
	    $q->td(ucfirst($key) . ":"),
	    $q->td($q->textfield(-name=>$key,
				 -size=>60,
				 -default=>$q->param($key))), "\n";
	}
    }
}
print "</TABLE>\n";
print $q->textarea(-name=>'body',
		   -default=>$q->param('body')), "\n";
print "<PRE>\n\n</PRE>", "\n",
    $q->submit(-value=>"Send the message"), "\n",
    $q->endform, "\n";

print "\n";
exit;

# This is for header field values.
sub sanitize_field_value {
    my($val) = @_;
    $val =~ s/\0/./g;
    $val =~ s/\r\n/\n/g;
    $val =~ s/\r/\n/g;
    $val =~ s/\n*$//g;
    $val =~ s/\n+/\n/g;
    $val =~ s/\n(\S)/\n\t$1/g;
    $val;
}

sub sendit {
    open (MAIL, "| $SENDMAIL -t -oi -v") || die ("$0: Can't run sendmail: $!\n");
    @fields = $q->param();
    foreach $key (@fields) {
	@vals = $q->param($key);
	if (scalar(@vals) != 1) {
	    print "multiple values " . scalar(@vals) ." for $key!\n";
	    $q->param($key, join (joiner($key), @vals));
	}
    }
    foreach $key (@fields) {
	if ($key ne 'body') {
	    if ($key =~ /[^A-Za-z0-9_-]/) {
		print "$0: Ignoring malformed header field named '$key'!\n";
		next;
	    }
	    print MAIL ucfirst($key) . ": " .
		sanitize_field_value($q->param($key)) . "\n"
		or die ("$0: Feeding header to sendmail failed: $!\n");
	}
    }
    print MAIL "\n"
	or die ("$0: Ending header for sendmail failed: $!\n");
    print MAIL $q->param('body'), "\n"
	or die ("$0: Feeding body to sendmail failed: $!\n");
    close(MAIL)
	or warn $! ? "Error closing pipe to sendmail: $!"
	    : ($? & 127) ? ("Sendmail killed by signal " . ($? & 127) .
			    ($? & 127) ? ", core dumped" : "")
		: "Return value " . ($? >> 8) . " from sendmail";
}