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'); _______________________________________________ Bug-coreutils mailing list Bug-coreutils@gnu.org http://lists.gnu.org/mailman/listinfo/bug-coreutils