[Top][All Lists]
[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');
- [RFC]: --format=FORMAT option for ls,
Steven Schubiger <=
Re: [RFC]: --format=FORMAT option for ls, James Youngman, 2008/02/12