Subject: Re: bug#11044: bug & RFE

From: Bruce Korb <[email-masked]>
Date: Fri, 30 Mar 2012 14:37:15 -0700

Hi Eric,

I guess nobody is listening.

BSD's implementation allows a goal specification and has a different
default goal width computation anyway.  This change adds a "-g" option
and allows the first two numeric operands to represent the goal width
and actual line width.

* src/fmt.c (main): implement the new option
(check_for_goals): new function to implement the operands
Based on BSD's and Plan-9's fmt programs.
---
  THANKS.in          |    3 ++
  doc/coreutils.texi |   23 +++++++++++--
  src/fmt.c          |   88 +++++++++++++++++++++++++++++++++++++++++++++++++--
  3 files changed, 106 insertions(+), 8 deletions(-)

diff --git a/THANKS.in b/THANKS.in
index d23f7b3..9a525c4 100644
--- a/THANKS.in
+++ b/THANKS.in
@@ -97,6 +99,7 @@ Brian M. Carlson                    [email-masked]
  Brian Silverman                     [email-masked]
  Brian Youmans                       [email-masked]
  Britton Leo Kerin                   [email-masked]
+Bruce Korb                          [email-masked]
  Bruce Robertson                     [email-masked]
  Carl Johnson                        [email-masked]
  Carl Lowenstein                     [email-masked]
diff --git a/doc/coreutils.texi b/doc/coreutils.texi
index 835c245..91ca957 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -2133,7 +2133,7 @@ These commands reformat the contents of files.
  a given number of characters (75 by default).  Synopsis:

  @example
-fmt [@var{option}]@dots{} [@var{file}]@dots{}
+fmt [@var{option}]@dots{} [@var{goal} [@var{width}]] [@var{file}]@dots{}
  @end example

  @command{fmt} reads from the specified @var{file} arguments (or standard
@@ -2144,6 +2144,13 @@ preserved in the output; successive input lines with different
  indentation are not joined; tabs are expanded on input and introduced on
  output.

+@var{goal} and @var{width} are only recognized if neither
+@var{-g}/@var{--goal} nor @var{-w}/@var{--width} have been specified; and if
+the strings represent numbers; and if the numbers do not exceed about 2,500;
+and if these strings do not match existing file names.  If @var{GOAL} is
+provided but @var{width} not, then @var{width} will be set to @var{goal}
+plus 10.
+
  @cindex line-breaking
  @cindex sentences and line-breaking
  @cindex Knuth, Donald E.
@@ -2203,9 +2210,17 @@ between sentences to two spaces.
  @opindex -@var{width}
  @opindex -w
  @opindex --width
-Fill output lines up to @var{width} characters (default 75).  @command{fmt}
-initially tries to make lines about 7% shorter than this, to give it
-room to balance line lengths.
+Fill output lines up to @var{width} characters (default 75 or @var{goal} plus 10,
+if @var{goal} is provided).
+
+@item -@var{goal}
+@itemx -g @var{goal}
+@itemx --goal=@var{goal}
+@opindex -@var{goal}
+@opindex -g
+@opindex --goal
+@command{fmt} initially tries to make lines @var{goal} characters wide.
+By default, this is 7% shorter than @var{width}.

  @item -p @var{prefix}
  @itemx --prefix=@var{prefix}
diff --git a/src/fmt.c b/src/fmt.c
index 89d13a6..95ae149 100644
--- a/src/fmt.c
+++ b/src/fmt.c
@@ -68,7 +68,7 @@ typedef long int COST;
  #define SQR(n)        ((n) * (n))
  #define EQUIV(n)    SQR ((COST) (n))

-/* Cost of a filled line n chars longer or shorter than best_width.  */
+/* Cost of a filled line n chars longer or shorter than goal_width.  */
  #define SHORT_COST(n)    EQUIV ((n) * 10)

  /* Cost of the difference between adjacent filled lines.  */
@@ -167,6 +167,7 @@ static void put_paragraph (WORD *finish);
  static void put_line (WORD *w, int indent);
  static void put_word (WORD *w);
  static void put_space (int space);
+static void check_for_goals (char ** argv);

  /* Option values.  */

@@ -201,7 +202,7 @@ static int prefix_lead_space;
  static int prefix_length;

  /* The preferred width of text lines, set to LEEWAY % less than max_width.  */
-static int best_width;
+static int goal_width;

  /* Dynamic variables.  */

@@ -286,6 +287,7 @@ Mandatory arguments to long options are mandatory for short options too.\n\
    -t, --tagged-paragraph    indentation of first line different from second\n\
    -u, --uniform-spacing     one space between words, two after sentences\n\
    -w, --width=WIDTH         maximum line width (default of 75 columns)\n\
+  -g, --goal=WIDTH          goal width (default of 93% of width)\n\
  "), stdout);
        fputs (HELP_OPTION_DESCRIPTION, stdout);
        fputs (VERSION_OPTION_DESCRIPTION, stdout);
@@ -308,6 +310,7 @@ static struct option const long_options[] =
    {"tagged-paragraph", no_argument, NULL, 't'},
    {"uniform-spacing", no_argument, NULL, 'u'},
    {"width", required_argument, NULL, 'w'},
+  {"goal", required_argument, NULL, 'g'},
    {GETOPT_HELP_OPTION_DECL},
    {GETOPT_VERSION_OPTION_DECL},
    {NULL, 0, NULL, 0},
@@ -319,6 +322,7 @@ main (int argc, char **argv)
    int optchar;
    bool ok = true;
    char const *max_width_option = NULL;
+  char const *goal_width_option = NULL;

    initialize_main (&argc, &argv);
    set_program_name (argv[0]);
@@ -376,6 +380,10 @@ main (int argc, char **argv)
          max_width_option = optarg;
          break;

+      case 'g':
+        goal_width_option = optarg;
+        break;
+
        case 'p':
          set_prefix (optarg);
          break;
@@ -398,7 +406,25 @@ main (int argc, char **argv)
        max_width = tmp;
      }

-  best_width = max_width * (2 * (100 - LEEWAY) + 1) / 200;
+  if (goal_width_option)
+    {
+      /* Limit goal_width to max_width.  */
+      unsigned long int tmp;
+      if (! (xstrtoul (goal_width_option, NULL, 10, &tmp, "") == LONGINT_OK
+             && tmp <= max_width))
+        error (EXIT_FAILURE, 0, _("invalid width: %s"),
+               quote (goal_width_option));
+      goal_width = tmp;
+      if (max_width_option == NULL)
+        max_width = goal_width + 10;
+    }
+  else
+    {
+      goal_width = max_width * (2 * (100 - LEEWAY) + 1) / 200;
+    }
+
+  if ((max_width_option == NULL) && (goal_width_option == NULL))
+    check_for_goals (argv);

    if (optind == argc)
      fmt (stdin);
@@ -435,6 +461,53 @@ main (int argc, char **argv)
    exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);
  }

+/* Check the first two operands for being numbers without a file by that name.
+   If there are no such files and the numbers are not too big, then accept
+   them as -g and -w options, respectively.  */
+
+static void
+check_for_goals (char ** argv)
+{
+  unsigned long v;
+
+  /* see if the first operand is a number.  That means there is no file
+     by that name and the operand fully translates to a number.  */
+  char * num = argv[optind];
+  if ((num == NULL) || access (num, R_OK))
+    return;
+  errno = 0;
+  v = strtoul (num, &num, 0);
+  if ((errno == 0) && (*num == '\0') && (v > 0) && (v < MAXCHARS/2))
+    goal_width = v;
+  else
+    return;
+  optind++;
+
+  /* see if the second operand is a number.  That means there is no file
+     by that name and the operand fully translates to a number.  */
+  num = argv[optind];
+  if ((num == NULL) || access (num, R_OK))
+    {
+      max_width = goal_width + 10;
+      return;
+    }
+  errno = 0;
+  v = strtoul (num, &num, 0);
+  if ((errno == 0) && (*num == '\0') && (v > 0) && (v < MAXCHARS/2))
+    {
+      max_width = v;
+      if (goal_width > max_width)
+        error (EXIT_FAILURE, 0, _("goal exceeds width:  %u > %u"),
+               goal_width, max_width);
+    }
+  else
+    {
+      max_width = goal_width + 10;
+      return;
+    }
+  optind++;
+}
+
  /* Trim space from the front and back of the string P, yielding the prefix,
     and record the lengths of the prefix and the space trimmed.  */

@@ -924,7 +997,7 @@ line_cost (WORD *next, int len)

    if (next == word_limit)
      return 0;
-  n = best_width - len;
+  n = goal_width - len;
    cost = SHORT_COST (n);
    if (next->next_break != word_limit)
      {
@@ -1010,3 +1083,10 @@ put_space (int space)
        out_column++;
      }
  }
+/*
+ * Local Variables:
+ * mode: C
+ * c-file-style: "gnu"
+ * indent-tabs-mode: nil
+ * End:
+ * end of fmt.c */
-- 
1.7.7