This is the mail archive of the frysk@sourceware.org mailing list for the frysk project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Patch: word-wrapping for 'help' output


This patch implements word-wrapping for frysk 'help' output.

The idea is simple: when writing help output, write through a new
PrintWriter which handles word-wrapping.  This patch goes a bit beyond
that, though, and provides some nice formatting in some particular
cases.  For instance, when printing an option, if the option's
description wraps, then the wrapped line will be indented.  This makes
it nicer to read.

I did not try to deal with letting the user configure the number of
columns.  However the wrapping code is general and ought to handle
future changes here gracefully.  Likewise, currently the code only
handles the default locale, but again is written to upgrade easily.

Tom

frysk-core/frysk/hpd/ChangeLog:
2008-02-29  Tom Tromey  <tromey@redhat.com>

	* ParameterizedCommand.java (help): Use getWordWrapWriter.  Set
	indent for wrapping.
	* NoOptsCommand.java (help): Use getWordWrapWriter.
	* MultiLevelCommand.java (help): use getWordWrapWriter.  Set
	indent for wrapping.
	* Command.java (help): Use getWordWrapWriter.
	* CLI.java (getWordWrapWriter): New method.

frysk-core/frysk/util/ChangeLog:
2008-02-29  Tom Tromey  <tromey@redhat.com>

	* WordWrapWriter.java: New file.

diff --git a/frysk-core/frysk/hpd/CLI.java b/frysk-core/frysk/hpd/CLI.java
index 35bc9e5..67b3788 100644
--- a/frysk-core/frysk/hpd/CLI.java
+++ b/frysk-core/frysk/hpd/CLI.java
@@ -61,6 +61,7 @@ import frysk.rt.ProcTaskIDManager;
 import frysk.stepping.SteppingEngine;
 import frysk.stepping.TaskStepEngine;
 import frysk.util.CountDownLatch;
+import frysk.util.WordWrapWriter;
 import frysk.expr.Expression;
 import frysk.expr.ScratchSymTab;
 import frysk.expr.ExprSymTab;
@@ -181,6 +182,10 @@ public class CLI {
             idManager.manageProc(proc, this.taskID);
     }
 
+    WordWrapWriter getWordWrapWriter() {
+	return new WordWrapWriter(outWriter);
+    }
+
     final PrintWriter outWriter;
     private Preprocessor prepro;
     private String prompt; // string to represent prompt, will be moved
diff --git a/frysk-core/frysk/hpd/Command.java b/frysk-core/frysk/hpd/Command.java
index ecbb374..3060b9a 100644
--- a/frysk-core/frysk/hpd/Command.java
+++ b/frysk-core/frysk/hpd/Command.java
@@ -1,6 +1,6 @@
 // This file is part of the program FRYSK.
 //
-// Copyright 2007, Red Hat Inc.
+// Copyright 2007, 2008, Red Hat Inc.
 //
 // FRYSK is free software; you can redistribute it and/or modify it
 // under the terms of the GNU General Public License as published by
@@ -40,6 +40,7 @@
 package frysk.hpd;
 
 import java.util.List;
+import java.io.PrintWriter;
 
 /**
  * A handler class for the CLI that supplies its own help messages.
@@ -72,11 +73,12 @@ public abstract class Command {
      * command.
      */
     void help(CLI cli, Input buffer) {
-	cli.outWriter.print("Usage: ");
-	cli.outWriter.print(syntax);
-	cli.outWriter.println();
-	cli.outWriter.print(full);
-	cli.outWriter.println();
+	PrintWriter out = cli.getWordWrapWriter();
+	out.print("Usage: ");
+	out.print(syntax);
+	out.println();
+	out.print(full);
+	out.println();
     }
 
     /**
diff --git a/frysk-core/frysk/hpd/MultiLevelCommand.java b/frysk-core/frysk/hpd/MultiLevelCommand.java
index 266d5ef..ac51455 100644
--- a/frysk-core/frysk/hpd/MultiLevelCommand.java
+++ b/frysk-core/frysk/hpd/MultiLevelCommand.java
@@ -1,6 +1,6 @@
 // This file is part of the program FRYSK.
 //
-// Copyright 2007, Red Hat Inc.
+// Copyright 2007, 2008, Red Hat Inc.
 //
 // FRYSK is free software; you can redistribute it and/or modify it
 // under the terms of the GNU General Public License as published by
@@ -45,6 +45,7 @@ import java.util.HashMap;
 import java.util.TreeMap;
 import java.util.Iterator;
 import java.util.List;
+import frysk.util.WordWrapWriter;
 
 /**
  * A handler class for the CLI that supplies its own help messages.
@@ -115,14 +116,17 @@ public abstract class MultiLevelCommand extends Command {
      */
     void help(CLI cli, Input input) {
 	if (input.size() == 0) {
+	    WordWrapWriter out = cli.getWordWrapWriter();
 	    for (Iterator i = subCommands.entrySet().iterator();
 		 i.hasNext(); ) {
 		Map.Entry entry = (Map.Entry)(i.next());
 		String name = (String)entry.getKey();
 		Command command = (Command)entry.getValue();
-		cli.outWriter.print(name);
-		cli.outWriter.print(" - ");
-		cli.outWriter.println(command.description());
+		out.print(name);
+		out.print(" - ");
+		out.setWrapIndentFromColumn();
+		out.println(command.description());
+		out.setWrapIndent(0);
 	    }
 	} else {
 	    lookup(input.parameter(0)).help(cli, input.accept());
diff --git a/frysk-core/frysk/hpd/NoOptsCommand.java b/frysk-core/frysk/hpd/NoOptsCommand.java
index 7edc566..b37ce14 100644
--- a/frysk-core/frysk/hpd/NoOptsCommand.java
+++ b/frysk-core/frysk/hpd/NoOptsCommand.java
@@ -1,6 +1,6 @@
 // This file is part of the program FRYSK.
 //
-// Copyright 2007, Red Hat Inc.
+// Copyright 2007, 2008, Red Hat Inc.
 //
 // FRYSK is free software; you can redistribute it and/or modify it
 // under the terms of the GNU General Public License as published by
@@ -39,6 +39,8 @@
 
 package frysk.hpd;
 
+import java.io.PrintWriter;
+
 /**
  * Handle commands that should not interpret options.
  */
@@ -72,8 +74,9 @@ public abstract class NoOptsCommand extends Command {
     abstract void interpretCommand(CLI cli, Input input);
     
     void help(CLI cli, Input input) {
-	cli.outWriter.print(syntax);
-	cli.outWriter.println();
-	cli.outWriter.println(full);
+	PrintWriter out = cli.getWordWrapWriter();
+	out.print(syntax);
+	out.println();
+	out.println(full);
     }
 }
\ No newline at end of file
diff --git a/frysk-core/frysk/hpd/ParameterizedCommand.java b/frysk-core/frysk/hpd/ParameterizedCommand.java
index 35ec6a6..99bfe7c 100644
--- a/frysk-core/frysk/hpd/ParameterizedCommand.java
+++ b/frysk-core/frysk/hpd/ParameterizedCommand.java
@@ -1,6 +1,6 @@
 // This file is part of the program FRYSK.
 //
-// Copyright 2007, Red Hat Inc.
+// Copyright 2007, 2008, Red Hat Inc.
 //
 // FRYSK is free software; you can redistribute it and/or modify it
 // under the terms of the GNU General Public License as published by
@@ -43,6 +43,7 @@ import java.util.TreeMap;
 import java.util.Iterator;
 import java.util.SortedMap;
 import java.util.List;
+import frysk.util.WordWrapWriter;
 
 abstract class ParameterizedCommand extends Command {
     private final SortedMap longOptions = new TreeMap();
@@ -147,22 +148,25 @@ abstract class ParameterizedCommand extends Command {
     }
 
     void help(CLI cli, Input input) {
-	cli.outWriter.print(syntax);
+	WordWrapWriter out = cli.getWordWrapWriter();
+	out.print(syntax);
 	if (longOptions.size() > 0) {
-	    cli.outWriter.println(" -option ...; where options are:");
+	    out.println(" -option ...; where options are:");
 	    for (Iterator i = longOptions.values().iterator();
 		 i.hasNext(); ) {
 		CommandOption option = (CommandOption)i.next();
-		cli.outWriter.print("  -");
-		cli.outWriter.print(option.longName);
-		cli.outWriter.print("\t");
-		cli.outWriter.print(option.description);
-		cli.outWriter.println();
+		out.print("  -");
+		out.print(option.longName);
+		out.print("\t");
+		out.setWrapIndentFromColumn();
+		out.print(option.description);
+		out.setWrapIndent(0);
+		out.println();
 	    }
 	} else {
-	    cli.outWriter.println();
+	    out.println();
 	}
-	cli.outWriter.println(full);
+	out.println(full);
     }
 
     /**
diff --git a/frysk-core/frysk/util/WordWrapWriter.java b/frysk-core/frysk/util/WordWrapWriter.java
new file mode 100644
index 0000000..5aa8527
--- /dev/null
+++ b/frysk-core/frysk/util/WordWrapWriter.java
@@ -0,0 +1,166 @@
+// This file is part of the program FRYSK.
+//
+// Copyright 2008 Red Hat Inc.
+//
+// FRYSK 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; version 2 of the License.
+//
+// FRYSK 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 FRYSK; if not, write to the Free Software Foundation,
+// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+// 
+// In addition, as a special exception, Red Hat, Inc. gives You the
+// additional right to link the code of FRYSK with code not covered
+// under the GNU General Public License ("Non-GPL Code") and to
+// distribute linked combinations including the two, subject to the
+// limitations in this paragraph. Non-GPL Code permitted under this
+// exception must only link to the code of FRYSK through those well
+// defined interfaces identified in the file named EXCEPTION found in
+// the source code files (the "Approved Interfaces"). The files of
+// Non-GPL Code may instantiate templates or use macros or inline
+// functions from the Approved Interfaces without causing the
+// resulting work to be covered by the GNU General Public
+// License. Only Red Hat, Inc. may make changes or additions to the
+// list of Approved Interfaces. You must obey the GNU General Public
+// License in all respects for all of the FRYSK code and other code
+// used in conjunction with FRYSK except the Non-GPL Code covered by
+// this exception. If you modify this file, you may extend this
+// exception to your version of the file, but you are not obligated to
+// do so. If you do not wish to provide this exception without
+// modification, you must delete this exception statement from your
+// version and license this file solely under the GPL without
+// exception.
+
+package frysk.util;
+
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.util.Locale;
+import java.text.BreakIterator;
+import java.text.StringCharacterIterator;
+
+/**
+ * This class is a PrintWriter which word-wraps the output.  It
+ * provides settings which control over the number of columns and
+ * indentation of wrapped lines.
+ */
+public class WordWrapWriter extends PrintWriter {
+    // Number of columns available.
+    private int columns;
+
+    // Amount of indentation to use on wrapped line.
+    private int wrapIndent;
+
+    // The break iterator, used to find the next line-break
+    // opportunity.
+    private BreakIterator iter;
+
+    // The current column.
+    private int column;
+
+    public WordWrapWriter(Writer outStream, int columns, int wrapIndent, Locale locale) {
+	// Always enable auto-flush.
+	super(outStream, true);
+	this.columns = columns;
+	this.wrapIndent = wrapIndent;
+	this.iter = BreakIterator.getWordInstance(locale);
+    }
+
+    public WordWrapWriter(Writer outStream, int columns) {
+	this(outStream, columns, 0, Locale.getDefault());
+    }
+
+    public WordWrapWriter(Writer outStream) {
+	this(outStream, 72, 0, Locale.getDefault());
+    }
+
+    public void setColumns(int columns) {
+	this.columns = columns;
+    }
+
+    public void setWrapIndent(int wrapIndent) {
+	this.wrapIndent = wrapIndent;
+    }
+
+    public void setWrapIndentFromColumn() {
+	this.wrapIndent = this.column;
+    }
+
+    public void write(char[] buf, int offset, int len) {
+	// A bit inefficient but we don't care much.
+	write(new String(buf, offset, len));
+    }
+
+    public void write(int b) {
+	write(String.valueOf((char) b));
+    }
+
+    // Update 'column' assuming STR is printed.  We use a helper
+    // function here to properly handle tabs.
+    private void updateColumn(String str) {
+	int len = str.length();
+	for (int i = 0; i < len; ++i) {
+	    if (str.charAt(i) == '\t') {
+		column = 8 * ((column + 8) / 8);
+	    } else {
+		++column;
+	    }
+	}
+    }
+
+    public void write(String str, int offset, int len) {
+	int term = offset + len;
+	while (offset < term) {
+	    // Find the next newline, if there is one.
+	    int nlIndex = str.indexOf('\n', offset);
+	    if (nlIndex >= term)
+		nlIndex = -1;
+	    // Only operate on the current line.  If we don't see a
+	    // \n, operate on all the remaining text.
+	    int subLen = (nlIndex < 0) ? len : (nlIndex - offset + 1);
+	    iter.setText(new StringCharacterIterator(str, offset,
+						     offset + subLen, offset));
+	    int start = iter.first();
+	    int end = iter.next();
+	    boolean first = column == 0;
+	    while (end != BreakIterator.DONE) {
+		String word = str.substring(start, end);
+		updateColumn(word);
+		if (!first && column >= columns) {
+		    super.write('\n');
+		    for (int i = 0; i < wrapIndent; ++i)
+			super.write(' ');
+		    column = wrapIndent;
+		    // The first word on a line should not start with
+		    // a space.  We use '<=' to work like String.trim,
+		    // and to eliminate tabs as well.
+		    int j = 0;
+		    while (j < word.length() && word.charAt(j) <= ' ')
+			++j;
+		    if (j > 0)
+			word = word.substring(j);
+		    updateColumn(word);
+		}
+		first = false;
+		// Avoid recursion here...
+		super.write(word, 0, word.length());
+		start = end;
+		end = iter.next();
+	    }
+
+	    if (nlIndex >= 0) {
+		// We already wrote the \n.
+		column = 0;
+	    }
+
+	    offset += subLen;
+	    len -= subLen;
+	}
+    }
+}


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]