/*
* `gnu.iou'
* Copyright (C) 2006 John Pritchard.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package gnu.iou ;
import java.io.DataInput;
import java.io.DataInputStream;
import java.util.StringTokenizer;
/**
* Tool parses string with '%format%' replaceable tokens, inserts
* arguments. Syntax- compatible with cog template (as a subfeature
* of "cog/build"), but does not interface to cog template features
* from here.
*
* <p> A "format" directive can optionally index the "argv" with a
* "%format[idx]%" syntax, where "idx" is an index into "argv". In
* this case the running "argc" index into "argv" is not incremented.
*
* <p> A "format" directive can optionally apply processing commands
* to the target string with a "%format(cmd args)%" syntax, where
* "cmd" is interpreted by the "cmd" function, see "pad" and "col",
* below.
*
* <p> By default, each instance of a "%format%" directive uses and
* increments an internal index into the "argv". Each call to the
* "format" function gets a fresh internal "argv" index, "argc".
*
* @see #cmd
* @see #pad
* @see #col
* @see #format(java.lang.String,java.lang.String[])
* @see #format(java.lang.String,java.lang.String)
* @see #format(java.lang.String[][],java.lang.String[])
* @see #format(java.lang.String[][],java.lang.String[],java.lang.String)
*
* @author John Pritchard (john@syntelos.org)
*/
public abstract class format {
/**
* General user interface parses src for "%format%" elements.
*
* @param templ_src Source to be parsed by 'utl/templ' for "%...%"
* directives.
*
* @param argv Arguments to "%format%" directives (dynamic
* replacement text).
*
* @exception IllegalArgumentException For "%format%" syntax
* errors, or "argv" array indexing (bounds) errors. */
public static String format ( String templ_src, String[] argv) throws IllegalArgumentException {
return format( templ.parse(templ_src), argv);
}
/**
* Format with one argument, creates stringary for you.
*/
public static String format ( String templ_src, String arg) throws IllegalArgumentException {
String[] argv = new String[1];
argv[0] = arg;
return format( templ.parse(templ_src), argv);
}
/**
* "format"
*/
private final static String default_format_key = "format";
/**
* Replace instances of "%format%" with elements of "argv".
*
* @param srcary Source parsed by 'utl/templ' for "%...%"
* directives.
*
* @param argv Arguments to "%format%" directives (dynamic
* replacement text).
*
* @exception IllegalArgumentException For "%format%" syntax
* errors, or "argv" array indexing (bounds) errors. */
public static String format ( String[][] templ_srcary, String[] argv) throws IllegalArgumentException {
return format( templ_srcary, argv, default_format_key);
}
/**
* User format key replaces the default "format" string.
*
* @param srcary Source parsed by 'utl/templ' for "%...%"
* directives.
*
* @param argv Arguments to format directives (dynamic
* replacement text).
*
* @param format_key Optional string to identify format operators. Default
* "format".
*
* @exception IllegalArgumentException For format syntax
* errors, or "argv" array indexing (bounds) errors. */
public static String format ( String[][] srcary, String[] argv, String format_key) throws IllegalArgumentException {
if ( null == srcary)
throw new IllegalArgumentException("Null source array argument.");
else if ( null == format_key)
format_key = default_format_key;
String format_key_prefix = "%"+format_key;
String s, ss, sub[];
int ii, idx, argc = 0, srclen = srcary.length, sublen;
linebuf lb = new linebuf(((srclen>1)?(srclen):(2)));
for ( int c = 0, lidx = 0, cc; c < srclen; c++, lidx++){
sub = srcary[c];
if ( null != sub){
sublen = sub.length;
for ( cc = 0; cc < sublen; cc++){
s = sub[cc];
if ( null != s){
if ( s.startsWith(format_key_prefix)){ // "%format"
idx = s.lastIndexOf("[");
if ( 0 < idx){
ss = s.substring(idx+1);
idx = ss.lastIndexOf("]");
ss = ss.substring(0,idx).trim();
try {
ii = Integer.parseInt(ss);
}
catch ( NumberFormatException nfx){
throw new IllegalArgumentException("Expected a number for `idx' in \""+format_key_prefix+"[idx]%\", in your \""+sub[cc]+"\", in \""+new linebuf(sub,"").toString()+"\".");
}
if ( 0 > ii || null == argv || ii >= argv.length)
throw new IllegalArgumentException("Format index ("+ii+") in your \""+sub[cc]+"\", in \""+new linebuf(sub,"").toString()+"\", is out of bounds for input argv {"+(new linebuf("`",argv,"',").toString())+"}.");
idx = s.lastIndexOf("(");
if ( 0 < idx){
ss = s.substring(idx+1);
idx = ss.lastIndexOf(")");
ss = ss.substring(0,idx).trim();
cmd ( lb, lidx, ss, argv[ii], srcary, c, cc);
lidx = lb.index();
}
else {
lb.index_append(lidx,argv[ii]);
}
}
else {
idx = s.lastIndexOf("(");
if ( null == argv || argc >= argv.length)
throw new IllegalArgumentException("Format index ("+argc+") implied in your \""+sub[cc]+"\", in \""+new linebuf(sub,"").toString()+"\", is out of bounds for input argv {"+(new linebuf("`",argv,"',").toString())+"}.");
if ( 0 < idx){
ss = s.substring(idx+1);
idx = ss.lastIndexOf(")");
ss = ss.substring(0,idx).trim();
cmd ( lb, lidx, ss, argv[argc++], srcary, c, cc);
lidx = lb.index();
}
else {
lb.index_append( lidx, argv[argc++]);
}
}
}
else
lb.index_append( lidx, sub[cc]);
}
}
}
else
lb.println();
}
return lb.toString();
}
/**
* Interprets format "(cmd args)" functions.
*
* <ul>
*
* <li> <b>(pad int)</b> -- Use "pad" on target to left- justify in
* column of width "int".
*
* <li> <b>(pad int false)</b> -- Use the "pad" function on target
* to right justify in column of width "int".
*
* <li> <b>(col int1 int2)</b> -- Use the "col" function to put
* target into column "int1", with width "int2".
*
* </ul>
*
* @param editbuf Buffer to write into
*
* @param lidx Buffer line index for this operation
*
* @param cmd The "(cmd args)" string
*
* @param target The argv element applied to this command
*
* @param srcary The full text used in error reporting
*
* @param srci The full text primary index used in error reporting
*
* @param srcii The full text secondary index used in error reporting
*
* @see #pad
* @see #col */
public final static void cmd ( linebuf editbuf, int lidx, String cmd, String target, String[][] srcary, int srci, int srcii) {
String[] cmdary = linebuf.toStringArray(cmd,"( ,)");
String cc = cmdary[0];
if ( "pad".equals(cc)){
if ( 1 < cmdary.length && null != cmdary[1]){
boolean ljust = true;
int len = 0;
cc = cmdary[1];
try {
len = Integer.parseInt(cc);
}
catch ( NumberFormatException nfx){
throw new IllegalArgumentException("Expected a number for `pad(int)', found \""+cc+"\" in your \""+srcary[srci][srcii]+"\", in \""+new linebuf(srcary[srci],"").toString()+"\".");
}
if ( 0 > len)
throw new IllegalArgumentException("PAD length argument ("+len+") in your \""+srcary[srci][srcii]+"\", in \""+new linebuf(srcary[srci],"").toString()+"\", is invalid (negative).");
if ( 2 < cmdary.length){
cc = cmdary[2];
if ( null != cc)
ljust = cc.toLowerCase().equals("true"); //Boolean.toBoolean
}
pad (editbuf,lidx,target,len,ljust);
// lidx = editbuf.index();
}
else
throw new IllegalArgumentException("PAD format command, `"+cmd+"', in your \""+srcary[srci][srcii]+"\", in \""+new linebuf(srcary[srci],"").toString()+"\", requires at least a `len' argument.");
}
else if ( "col".equals(cc)){
if ( 3 == cmdary.length){
int col0 = -1, cwide = -1;
cc = cmdary[1];
try {
col0 = Integer.parseInt(cc);
}
catch ( NumberFormatException nfx){
throw new IllegalArgumentException("Expected an integer for `col0' in `col(col0,colwid)', found \""+cc+"\" in your \""+srcary[srci][srcii]+"\", in \""+new linebuf(srcary[srci],"").toString()+"\".");
}
cc = cmdary[2];
try {
cwide = Integer.parseInt(cc);
}
catch ( NumberFormatException nfx){
throw new IllegalArgumentException("Expected an integer for `colwid' in `col(col0,colwid)', found \""+cc+"\" in your \""+srcary[srci][srcii]+"\", in \""+new linebuf(srcary[srci],"").toString()+"\".");
}
col (editbuf,lidx,target,col0,cwide);
// lidx = editbuf.index();
}
else
throw new IllegalArgumentException("COL format command, `"+cmd+"', in your \""+srcary[srci][srcii]+"\", in \""+new linebuf(srcary[srci],"").toString()+"\", requires two arguments.");
}
else
throw new IllegalArgumentException("Format command `"+cmd+"' in your \""+srcary[srci][srcii]+"\", in \""+new linebuf(srcary[srci],"").toString()+"\", not recognized.");
}
/**
* Pad with space, or truncate to length.
*
* @param editbuf Buffer to write into
*
* @param lidx Buffer line index for this operation
*
* @param s String
*
* @param len Length of output
*
* @param leftj Left or right justified.
*
* @see #cmd
*/
public final static void pad ( linebuf editbuf, int lidx, String s, int len, boolean leftj){
if ( null == s){
char[] ret = new char[len--];
while ( len >= 0){
ret[len--] = ' ';
}
editbuf.index_append( lidx, ret);
}
else {
char[] cary = linebuf.detab(s);
int slen = (null != cary)?(cary.length):(0);
if ( slen > len)
editbuf.index_append( lidx, chbuf.substring(cary,0,len));
else if ( slen == len)
editbuf.index_append( lidx, cary);
else {
char[] ret = new char[len];
if (leftj){
if ( 0 < slen) System.arraycopy(cary,0,ret,0,slen);
while ( slen < len){
ret[slen++] = ' ';
}
}
else {
int ri = len-slen;
if ( 0 < slen) System.arraycopy(cary,0,ret,ri--,slen);
while ( ri >= 0){
ret[ri--] = ' ';
}
}
editbuf.index_append( lidx, ret);
}
}
}
/**
* Wrap string into column using return elements for lines and the
* ASCII SPACE character for offsetting.
*
* @param editbuf Buffer to write column into
*
* @param lidx Buffer first- line index.
*
* @param s String source to fit into column
*
* @param col0 Column specification for first column index
*
* @param cwide Column specification for width
*
* @see #cmd
*/
public final static void col ( linebuf editbuf, int lidx, String s, int col0, int cwide){
int slen = s.length();
if ( slen <= cwide){
editbuf.index_column_overwrite(lidx,s,col0,cwide);
return ;
}
else {
StringTokenizer strtok = new StringTokenizer(s," \t");
chbuf sbuf = new chbuf(slen);
String tok;
char[] sub;
int sbuflen;
while (strtok.hasMoreTokens()){
tok = chbuf.cat(strtok.nextToken()," ");
sbuf.append(tok);
sbuflen = sbuf.length();
if ( sbuflen < cwide){
continue;
}
else if ( sbuflen == cwide){
sub = sbuf.toCary();
editbuf.index_column_overwrite(lidx++,sub,col0,cwide);
sbuf.reset();
}
else {
sbuf.popBuf(tok.length());
sub = sbuf.toCary();
editbuf.index_column_overwrite(lidx++,sub,col0,cwide);
sbuf.reset();
sbuf.append(tok);
}
}
if ( 0 < sbuf.length()){
sub = sbuf.toCary();
editbuf.index_column_overwrite(lidx++,sub,col0,cwide);
}
return ;
}
}
private final static String main_usage = " Usage: cat format-text | format [args]+";
/**
* Command line filter tool applies its arguments to the
* `format'ed input stream. */
public static void main ( String[] argv){
try {
if ( null == argv || 0 >= argv.length || (1 == argv.length && "?".equals(argv[0])))
throw new IllegalArgumentException(main_usage);
DataInput din = new DataInputStream(System.in);
String line;
linebuf lb = new linebuf(1024);
while (null != (line = din.readLine()))
lb.append(line);
String fmtxt = lb.toString();
if ( null != fmtxt)
System.out.println(format(fmtxt,argv));
else
System.out.println();
}
catch ( IllegalArgumentException ilx){
System.err.println(ilx.getMessage());
}
catch ( Exception exc){
exc.printStackTrace();
}
}
}