bug-coreutils
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[RFC]: --format=FORMAT option for ls


From: Steven Schubiger
Subject: [RFC]: --format=FORMAT option for ls
Date: Tue, 12 Feb 2008 14:13:03 +0100
User-agent: Mutt/1.5.13 (2006-08-11)

I hacked up a prototype of ls with the possibility of specifying
a format string that currently solely defines the order of items.

I had to change quite a bit of the underlying code to make it work,
as in, moving most of the routines called by print_long_format() to
functions, that are both used by the user-defined and default format
code. Right now only a column is buffered at once (the majority is '\0'
terminated) and then written by DIRED_FPUTS(). It probably would be
better to buffer the entire row and have it then printed to the output
stream (as it mostly was before).

How it works:

 /your/custom/ls --printf="%M %O %G %S %T %f" -l

should be roughly equivalent to a bare

 /bin/ls -l

I've chosen --printf, because it was easier to add and I didn't want
to possibly "pollute" the --format code (at least not, for now).

The patch is not complete at all, but merely an approximation.

Ideas, opinions and critique are welcome.

Steven Schubiger

diff --git a/src/ls.c b/src/ls.c
index 46713f2..b15042b 100644
--- a/src/ls.c
+++ b/src/ls.c
@@ -584,6 +584,12 @@ static bool immediate_dirs;
 
 static bool directories_first;
 
+static bool print_formatted_line;
+
+#define FORMAT_TYPES_NUM 10
+
+static char format_specifier[FORMAT_TYPES_NUM];
+
 /* Which files to ignore.  */
 
 static enum
@@ -765,6 +771,7 @@ static struct option const long_options[] =
   {"indicator-style", required_argument, NULL, INDICATOR_STYLE_OPTION},
   {"dereference", no_argument, NULL, 'L'},
   {"literal", no_argument, NULL, 'N'},
+  {"printf", required_argument, NULL, 'P'},
   {"quote-name", no_argument, NULL, 'Q'},
   {"quoting-style", required_argument, NULL, QUOTING_STYLE_OPTION},
   {"recursive", no_argument, NULL, 'R'},
@@ -1368,6 +1375,79 @@ main (int argc, char **argv)
   exit (exit_status);
 }
 
+static void 
+extract_formatstring_specifiers (const char *fmt)
+{
+  const char *fmt_start;
+  int cnt, i = 0;
+
+  if (!strlen (fmt))
+    error (EXIT_FAILURE, 0, _("format string must not be empty"));
+
+  fmt_start = fmt;
+
+  while (*fmt) {
+    cnt = 0;
+    while (cnt < 2) {
+      switch (cnt) 
+        {
+         case 0:
+            if (*fmt != '%')
+             error (EXIT_FAILURE, 0, _("format specifier must begin with %%"));
+           break;
+         case 1:
+            if (isalpha(*fmt))
+              {
+                char ch;
+                ch = *fmt;
+                if (i >= FORMAT_TYPES_NUM)
+                  goto end; 
+                if (ch == 'f' && (fmt - fmt_start != strlen (fmt_start) - 1))
+                  error (EXIT_FAILURE, 0, _("file format specifier %%f must be 
at end"));
+                if (islower(ch) && (ch != 'f' && ch != 's')) 
+                  format_specifier[i] = toupper(ch);
+                else
+                  format_specifier[i] = ch;
+                i++;
+              }
+            else
+              error (EXIT_FAILURE, 0, _("format character must be a valid 
character"));
+            break;
+         /* never reached */
+         default:
+           abort ();
+        }
+      cnt++;
+      fmt++;
+    }
+    while (*fmt == ' ')
+      fmt++;
+  }
+
+  end:  
+
+  for (i = 0; isalpha(format_specifier[i]); i++)
+    {
+      switch (format_specifier[i])
+        {
+         case 'f':
+         case 's':
+         case 'A':
+         case 'B':
+         case 'G':
+         case 'I':
+         case 'M':
+         case 'O':
+         case 'S':
+         case 'T':
+            break;
+         default:
+            error (EXIT_FAILURE, 0, _("format specifier %%%c is unsupported"), 
format_specifier[i]);
+            break;
+        }
+    }
+}
+
 /* Set all the option flags according to the switches specified.
    Return the index of the first non-option argument.  */
 
@@ -1508,7 +1588,7 @@ decode_switches (int argc, char **argv)
     {
       int oi = -1;
       int c = getopt_long (argc, argv,
-                          "abcdfghiklmnopqrstuvw:xABCDFGHI:LNQRST:UXZ1",
+                          "abcdfghiklmnopqrstuvw:xABCDFGHI:LNPQRST:UXZ1",
                           long_options, &oi);
       if (c == -1)
        break;
@@ -1675,6 +1755,11 @@ decode_switches (int argc, char **argv)
          set_quoting_style (NULL, literal_quoting_style);
          break;
 
+        case 'P':
+          print_formatted_line = true;
+          extract_formatstring_specifiers (optarg);
+          break;
+
        case 'Q':
          set_quoting_style (NULL, c_quoting_style);
          break;
@@ -3345,20 +3430,25 @@ get_current_time (void)
 /* Print the user or group name NAME, with numeric id ID, using a
    print width of WIDTH columns.  */
 
-static void
-format_user_or_group (char const *name, unsigned long int id, int width)
+static char *
+format_user_or_group (char *p, char *name, unsigned long int id, int width)
 {
   size_t len;
+  char *iter;
 
   if (name)
     {
       int width_gap = width - mbswidth (name, 0);
       int pad = MAX (0, width_gap);
-      fputs (name, stdout);
+
+      iter = name;
+      while (*iter)
+        *p++ = *iter++;
+      
       len = strlen (name) + pad;
 
       do
-       putchar (' ');
+        *p++ = ' ';
       while (pad--);
     }
   else
@@ -3367,26 +3457,30 @@ format_user_or_group (char const *name, unsigned long 
int id, int width)
       len = width;
     }
 
+  *p = '\0';
+
   dired_pos += len + 1;
+
+  return p;
 }
 
 /* Print the name or id of the user with id U, using a print width of
    WIDTH.  */
 
-static void
-format_user (uid_t u, int width, bool stat_ok)
+static char *
+format_user (char *p, uid_t u, int width, bool stat_ok)
 {
-  format_user_or_group (! stat_ok ? "?" :
-                       (numeric_ids ? NULL : getuser (u)), u, width);
+  return format_user_or_group (p, ! stat_ok ? "?" :
+                              (numeric_ids ? NULL : getuser (u)), u, width);
 }
 
 /* Likewise, for groups.  */
 
-static void
-format_group (gid_t g, int width, bool stat_ok)
+static char *
+format_group (char *p, gid_t g, int width, bool stat_ok)
 {
-  format_user_or_group (! stat_ok ? "?" :
-                       (numeric_ids ? NULL : getgroup (g)), g, width);
+  return format_user_or_group (p, ! stat_ok ? "?" :
+                              (numeric_ids ? NULL : getgroup (g)), g, width);
 }
 
 /* Return the number of columns that format_user_or_group will print.  */
@@ -3423,44 +3517,33 @@ format_group_width (gid_t g)
   return format_user_or_group_width (numeric_ids ? NULL : getgroup (g), g);
 }
 
-
-/* Print information about F in long format.  */
-
 static void
-print_long_format (const struct fileinfo *f)
+print_link_name (const struct fileinfo *f)
+{
+  if (f->filetype == symbolic_link)
+    {
+      if (f->linkname)
+       {
+         DIRED_FPUTS_LITERAL (" -> ", stdout);
+         print_name_with_quoting (f->linkname, f->linkmode, f->linkok - 1,
+                                  f->stat_ok, f->filetype, NULL);
+         if (indicator_style != none)
+           print_type_indicator (true, f->linkmode, unknown);
+       }
+      else if (indicator_style != none)
+       print_type_indicator (f->stat_ok, f->stat.st_mode, f->filetype);
+    }
+}
+
+static char *
+print_time_to_ptr (char *p, const struct fileinfo *f)
 {
-  char modebuf[12];
-  char buf
-    [LONGEST_HUMAN_READABLE + 1                /* inode */
-     + LONGEST_HUMAN_READABLE + 1      /* size in blocks */
-     + sizeof (modebuf) - 1 + 1                /* mode string */
-     + INT_BUFSIZE_BOUND (uintmax_t)   /* st_nlink */
-     + LONGEST_HUMAN_READABLE + 2      /* major device number */
-     + LONGEST_HUMAN_READABLE + 1      /* minor device number */
-     + TIME_STAMP_LEN_MAXIMUM + 1      /* max length of time/date */
-     ];
   size_t s;
-  char *p;
   time_t when;
   int when_ns;
   struct timespec when_timespec;
   struct tm *when_local;
 
-  /* Compute the mode string, except remove the trailing space if no
-     file in this directory has an ACL or SELinux security context.  */
-  if (f->stat_ok)
-    filemodestring (&f->stat, modebuf);
-  else
-    {
-      modebuf[0] = filetype_letter[f->filetype];
-      memset (modebuf + 1, '?', 10);
-      modebuf[11] = '\0';
-    }
-  if (! any_has_acl)
-    modebuf[10] = '\0';
-  else if (f->have_acl)
-    modebuf[10] = '+';
-
   switch (time_type)
     {
     case time_ctime:
@@ -3476,107 +3559,13 @@ print_long_format (const struct fileinfo *f)
       abort ();
     }
 
-  when = when_timespec.tv_sec;
-  when_ns = when_timespec.tv_nsec;
-
-  p = buf;
-
-  if (print_inode)
-    {
-      char hbuf[INT_BUFSIZE_BOUND (uintmax_t)];
-      sprintf (p, "%*s ", inode_number_width,
-              (f->stat.st_ino == NOT_AN_INODE_NUMBER
-               ? "?"
-               : umaxtostr (f->stat.st_ino, hbuf)));
-      /* Increment by strlen (p) here, rather than by inode_number_width + 1.
-        The latter is wrong when inode_number_width is zero.  */
-      p += strlen (p);
-    }
-
-  if (print_block_size)
-    {
-      char hbuf[LONGEST_HUMAN_READABLE + 1];
-      char const *blocks =
-       (! f->stat_ok
-        ? "?"
-        : human_readable (ST_NBLOCKS (f->stat), hbuf, human_output_opts,
-                          ST_NBLOCKSIZE, output_block_size));
-      int pad;
-      for (pad = block_size_width - mbswidth (blocks, 0); 0 < pad; pad--)
-       *p++ = ' ';
-      while ((*p++ = *blocks++))
-       continue;
-      p[-1] = ' ';
-    }
-
-  /* The last byte of the mode string is the POSIX
-     "optional alternate access method flag".  */
-  {
-    char hbuf[INT_BUFSIZE_BOUND (uintmax_t)];
-    sprintf (p, "%s %*s ", modebuf, nlink_width,
-            ! f->stat_ok ? "?" : umaxtostr (f->stat.st_nlink, hbuf));
-  }
-  /* Increment by strlen (p) here, rather than by, e.g.,
-     sizeof modebuf - 2 + any_has_acl + 1 + nlink_width + 1.
-     The latter is wrong when nlink_width is zero.  */
-  p += strlen (p);
-
-  DIRED_INDENT ();
-
-  if (print_owner | print_group | print_author | print_scontext)
-    {
-      DIRED_FPUTS (buf, stdout, p - buf);
-
-      if (print_owner)
-       format_user (f->stat.st_uid, owner_width, f->stat_ok);
-
-      if (print_group)
-       format_group (f->stat.st_gid, group_width, f->stat_ok);
-
-      if (print_author)
-       format_user (f->stat.st_author, author_width, f->stat_ok);
-
-      if (print_scontext)
-       format_user_or_group (f->scontext, 0, scontext_width);
-
-      p = buf;
-    }
-
-  if (f->stat_ok
-      && (S_ISCHR (f->stat.st_mode) || S_ISBLK (f->stat.st_mode)))
-    {
-      char majorbuf[INT_BUFSIZE_BOUND (uintmax_t)];
-      char minorbuf[INT_BUFSIZE_BOUND (uintmax_t)];
-      int blanks_width = (file_size_width
-                         - (major_device_number_width + 2
-                            + minor_device_number_width));
-      sprintf (p, "%*s, %*s ",
-              major_device_number_width + MAX (0, blanks_width),
-              umaxtostr (major (f->stat.st_rdev), majorbuf),
-              minor_device_number_width,
-              umaxtostr (minor (f->stat.st_rdev), minorbuf));
-      p += file_size_width + 1;
-    }
-  else
-    {
-      char hbuf[LONGEST_HUMAN_READABLE + 1];
-      char const *size =
-       (! f->stat_ok
-        ? "?"
-        : human_readable (unsigned_file_size (f->stat.st_size),
-                          hbuf, human_output_opts, 1, file_output_block_size));
-      int pad;
-      for (pad = file_size_width - mbswidth (size, 0); 0 < pad; pad--)
-       *p++ = ' ';
-      while ((*p++ = *size++))
-       continue;
-      p[-1] = ' ';
-    }
-
   when_local = localtime (&when_timespec.tv_sec);
   s = 0;
   *p = '\1';
 
+  when = when_timespec.tv_sec;
+  when_ns = when_timespec.tv_nsec;
+
   if (f->stat_ok && when_local)
     {
       time_t six_months_ago;
@@ -3631,24 +3620,237 @@ print_long_format (const struct fileinfo *f)
                   : umaxtostr (when, hbuf))));
       p += strlen (p);
     }
+  return p;
+}
 
-  DIRED_FPUTS (buf, stdout, p - buf);
-  print_name_with_quoting (f->name, FILE_OR_LINK_MODE (f), f->linkok,
-                          f->stat_ok, f->filetype, &dired_obstack);
+static char *
+print_file_size_to_ptr (char *p, const struct fileinfo *f)
+{
+  if (f->stat_ok
+     && (S_ISCHR (f->stat.st_mode) || S_ISBLK (f->stat.st_mode)))
+    {
+      char majorbuf[INT_BUFSIZE_BOUND (uintmax_t)];
+      char minorbuf[INT_BUFSIZE_BOUND (uintmax_t)];
+      int blanks_width = (file_size_width
+                         - (major_device_number_width + 2
+                            + minor_device_number_width));
+      snprintf (p, 
+               major_device_number_width + MAX (0, blanks_width) +
+              minor_device_number_width + 4,
+               "%*s, %*s ",
+              major_device_number_width + MAX (0, blanks_width),
+              umaxtostr (major (f->stat.st_rdev), majorbuf),
+              minor_device_number_width,
+              umaxtostr (minor (f->stat.st_rdev), minorbuf));
+      p += strlen (p);
+    }
+  else
+    {
+      char hbuf[LONGEST_HUMAN_READABLE + 1];
+      char const *size =
+       (! f->stat_ok
+        ? "?"
+        : human_readable (unsigned_file_size (f->stat.st_size),
+                          hbuf, human_output_opts, 1, file_output_block_size));
+      int pad;
+      for (pad = file_size_width - mbswidth (size, 0); 0 < pad; pad--)
+       *p++ = ' ';
+      while ((*p++ = *size++))
+       continue;
+      *(--p) = ' ';
+      *(++p) = '\0';
+    }
+  return p;
+}
 
-  if (f->filetype == symbolic_link)
+static char *
+print_mode_string_to_ptr (char *p, const struct fileinfo *f, char *modebuf)
+{
+    /* Compute the mode string, except remove the trailing space if no
+     file in this directory has an ACL or SELinux security context.  */
+  if (f->stat_ok)
+    filemodestring (&f->stat, modebuf);
+  else
     {
-      if (f->linkname)
+      modebuf[0] = filetype_letter[f->filetype];
+      memset (modebuf + 1, '?', 10);
+      modebuf[11] = '\0';
+    }
+  if (! any_has_acl)
+    modebuf[10] = '\0';
+  else if (f->have_acl)
+    modebuf[10] = '+';
+
+  {
+    char hbuf[INT_BUFSIZE_BOUND (uintmax_t)];
+    snprintf (p, strlen (modebuf) + nlink_width + 3, "%s %*s ", modebuf, 
nlink_width,
+             ! f->stat_ok ? "?" : umaxtostr (f->stat.st_nlink, hbuf));
+  }
+  /* Increment by strlen (p) here, rather than by, e.g.,
+     sizeof modebuf - 2 + any_has_acl + 1 + nlink_width + 1.
+     The latter is wrong when nlink_width is zero.  */
+  p += strlen (p);
+  /* The last byte of the mode string is the POSIX
+     "optional alternate access method flag".  */
+  return p;
+}
+
+static char *
+print_block_size_to_ptr (char *p, const struct fileinfo *f)
+{
+  char hbuf[LONGEST_HUMAN_READABLE + 1];
+  char const *blocks =
+    (! f->stat_ok
+     ? "?"
+     : human_readable (ST_NBLOCKS (f->stat), hbuf, human_output_opts,
+                          ST_NBLOCKSIZE, output_block_size));
+  int pad;
+  for (pad = block_size_width - mbswidth (blocks, 0); 0 < pad; pad--)
+    *p++ = ' ';
+  while ((*p++ = *blocks++))
+    continue;
+  *(--p) = ' ';
+  *(++p) = '\0';
+  return p;
+}
+
+static char *
+print_inode_to_ptr (char *p, const struct fileinfo *f)
+{
+  char hbuf[INT_BUFSIZE_BOUND (uintmax_t)];
+  snprintf (p, inode_number_width + 2, "%*s ", inode_number_width,
+    (f->stat.st_ino == NOT_AN_INODE_NUMBER
+     ? "?"
+     : umaxtostr (f->stat.st_ino, hbuf)));
+  /* Increment by strlen (p) here, rather than by inode_number_width + 1.
+     The latter is wrong when inode_number_width is zero.  */
+  p += strlen (p);
+  return p;
+}
+
+/* Print information about F in long format.  */
+
+static void
+print_long_format (const struct fileinfo *f)
+{
+  char buf[LONGEST_HUMAN_READABLE + 1];
+  char modebuf[12];
+  char *p;
+
+  p = buf;
+
+  if (print_formatted_line) 
+    {
+      int i; 
+     
+      for (i = 0; isalpha(format_specifier[i]); i++) 
+        {         
+          bool print_buf_and_assign_ptr = true;
+          switch (format_specifier[i]) 
+            {
+             case 'f':
+                  print_name_with_quoting (f->name, FILE_OR_LINK_MODE (f), 
f->linkok,
+                                           f->stat_ok, f->filetype, 
&dired_obstack);
+                  print_link_name (f);
+                  print_buf_and_assign_ptr = false;
+                  break;
+             case 's':
+                if (print_scontext)
+                 p = format_user_or_group (p, f->scontext, 0, scontext_width);
+                break;
+             case 'A':
+                if (print_author) 
+                 p = format_user (p, f->stat.st_author, author_width, 
f->stat_ok);
+                break;
+             case 'B':
+                if (print_block_size)
+                  p = print_block_size_to_ptr (p, f);
+                print_buf_and_assign_ptr = false;
+                break;
+             case 'G':
+                if (print_group)
+                 p = format_group (p, f->stat.st_gid, group_width, f->stat_ok);
+                break;
+             case 'I':
+                if (print_inode)
+                  p = print_inode_to_ptr (p, f);
+                break;
+             case 'M':
+               p = print_mode_string_to_ptr (p, f, modebuf);
+                break;
+             case 'O':
+                if (print_owner)
+                  p = format_user (p, f->stat.st_uid, owner_width, f->stat_ok);
+                break;
+             case 'S':
+                p = print_file_size_to_ptr (p, f);
+                break;
+             case 'T':
+                p = print_time_to_ptr (p, f);
+                break;
+             /* never reached */
+             default:
+                abort ();
+            }
+           if (print_buf_and_assign_ptr)
+             { 
+                DIRED_FPUTS (buf, stdout, p - buf);
+                p = buf;
+              }
+        }
+    }
+  else
+    {
+      if (print_inode)
+        p = print_inode_to_ptr (p, f);
+  
+      if (print_block_size)
+        p = print_block_size_to_ptr (p, f);
+
+      p = print_mode_string_to_ptr (p, f, modebuf);
+
+      DIRED_INDENT ();
+
+      if (print_owner)
        {
-         DIRED_FPUTS_LITERAL (" -> ", stdout);
-         print_name_with_quoting (f->linkname, f->linkmode, f->linkok - 1,
-                                  f->stat_ok, f->filetype, NULL);
-         if (indicator_style != none)
-           print_type_indicator (true, f->linkmode, unknown);
+         p = format_user (p, f->stat.st_uid, owner_width, f->stat_ok);
+         DIRED_FPUTS (buf, stdout, p - buf);
+         p = buf;
+       }
+ 
+      if (print_group)
+       {
+         p = format_group (p, f->stat.st_gid, group_width, f->stat_ok);
+         DIRED_FPUTS (buf, stdout, p - buf);
+         p = buf;
+        }
+
+      if (print_author)
+       {
+         p = format_user (p, f->stat.st_author, author_width, f->stat_ok);
+         DIRED_FPUTS (buf, stdout, p - buf);
+         p = buf;
        }
+
+      if (print_scontext)
+       {
+         p = format_user_or_group (p, f->scontext, 0, scontext_width);
+         DIRED_FPUTS (buf, stdout, p - buf);
+         p = buf;
+        }
+
+      p = print_file_size_to_ptr (p, f);
+      DIRED_FPUTS (buf, stdout, p - buf);
+      p = buf;  
+
+      p = print_time_to_ptr (p, f);
+      DIRED_FPUTS (buf, stdout, p - buf);
+      p = buf;
+
+      print_name_with_quoting (f->name, FILE_OR_LINK_MODE (f), f->linkok,
+                              f->stat_ok, f->filetype, &dired_obstack);
+      print_link_name (f);
     }
-  else if (indicator_style != none)
-    print_type_indicator (f->stat_ok, f->stat.st_mode, f->filetype);
 }
 
 /* Output to OUT a quoted representation of the file name NAME,
@@ -4162,6 +4364,7 @@ print_with_commas (void)
        }
 
       print_file_name_and_frills (f);
+
       pos += len;
     }
   putchar ('\n');




reply via email to

[Prev in Thread] Current Thread [Next in Thread]