git: 8b92977857f8 - main - ls: implement --group-directories-first for compatibility with GNU ls

From: Piotr Pawel Stefaniak <pstef_at_FreeBSD.org>
Date: Thu, 16 Jan 2025 16:03:53 UTC
The branch main has been updated by pstef:

URL: https://cgit.FreeBSD.org/src/commit/?id=8b92977857f8acaa539c21800b024edea9eee8b4

commit 8b92977857f8acaa539c21800b024edea9eee8b4
Author:     Piotr Paweł Stefaniak <pstef@FreeBSD.org>
AuthorDate: 2025-01-06 21:26:14 +0000
Commit:     Piotr Paweł Stefaniak <pstef@FreeBSD.org>
CommitDate: 2025-01-16 16:00:17 +0000

    ls: implement --group-directories-first for compatibility with GNU ls
    
    Also implement --group-directories which takes a parameter.
    "first" is equivalent to --group-directories-first, "last" gives
    reversed sorting.
    
    Changes in sorting between elements of the same type (files,
    directories) are not intended.
    
    Differential Revision:  https://reviews.freebsd.org/D48347
---
 bin/ls/ls.1   | 19 ++++++++++++++++++-
 bin/ls/ls.c   | 35 +++++++++++++++++++++++++++++++----
 bin/ls/util.c |  4 ++--
 3 files changed, 51 insertions(+), 7 deletions(-)

diff --git a/bin/ls/ls.1 b/bin/ls/ls.1
index 6aaa5025a6e1..b634f2f23ae6 100644
--- a/bin/ls/ls.1
+++ b/bin/ls/ls.1
@@ -39,6 +39,8 @@
 .Nm
 .Op Fl ABCFGHILPRSTUWZabcdfghiklmnopqrstuvwxy1\&,
 .Op Fl -color Ns = Ns Ar when
+.Op Fl -group-directories Ns = Ns Ar order
+.Op Fl -group-directories-first
 .Op Fl D Ar format
 .Op Ar
 .Sh DESCRIPTION
@@ -303,6 +305,16 @@ options.
 Display the long
 .Pq Fl l
 format output without the file owner's name or number.
+.It Fl -group-directories Ns = Ns Ar order
+Within results for each operand,
+group directories together and print them either
+.Cm first
+or
+.Cm last.
+.It Fl -group-directories-first
+Equivalent to
+.Fl -group-directories Ns = Ns Ar first .
+Implemented for compatibility with GNU coreutils.
 .It Fl h
 When used with the
 .Fl l
@@ -914,8 +926,13 @@ and
 .St -p1003.1-2008 .
 The options
 .Fl B , D , G , I , T , U , W , Z , b , h , v , w , y
-and
+,
 .Fl ,
+.Fl -color
+and
+.Fl -group-directories Ns =
+(including
+.Fl -group-directories-first )
 are non-standard extensions.
 .Pp
 The ACL support is compatible with
diff --git a/bin/ls/ls.c b/bin/ls/ls.c
index 048cfc9293e6..ca0fb9877b7e 100644
--- a/bin/ls/ls.c
+++ b/bin/ls/ls.c
@@ -87,12 +87,24 @@ static void	 display(const FTSENT *, FTSENT *, int);
 static int	 mastercmp(const FTSENT * const *, const FTSENT * const *);
 static void	 traverse(int, char **, int);
 
-#define	COLOR_OPT	(CHAR_MAX + 1)
+enum {
+	GRP_NONE = 0,
+	GRP_DIR_FIRST = -1,
+	GRP_DIR_LAST = 1
+};
+
+enum {
+	BIN_OPT = CHAR_MAX,
+	COLOR_OPT,
+	GROUP_OPT
+};
 
 static const struct option long_opts[] =
 {
-        {"color",       optional_argument,      NULL, COLOR_OPT},
-        {NULL,          no_argument,            NULL, 0}
+        {"color",        optional_argument,      NULL, COLOR_OPT},
+        {"group-directories", optional_argument, NULL, GROUP_OPT},
+        {"group-directories-first", no_argument, NULL, GROUP_OPT},
+        {NULL,           no_argument,            NULL, 0}
 };
 
 static void (*printfcn)(const DISPLAY *);
@@ -105,6 +117,7 @@ int termwidth = 80;		/* default terminal width */
        int f_accesstime;	/* use time of last access */
        int f_birthtime;		/* use time of birth */
        int f_flags;		/* show flags associated with a file */
+static int f_groupdir = GRP_NONE;/* group directories first/last */
        int f_humanval;		/* show human-readable file sizes */
        int f_inode;		/* print inode */
 static int f_kblocks;		/* print size in kilobytes */
@@ -449,6 +462,15 @@ main(int argc, char *argv[])
 		case 'y':
 			f_samesort = 1;
 			break;
+		case GROUP_OPT:
+			if (optarg == NULL || strcmp(optarg, "first") == 0)
+				f_groupdir = GRP_DIR_FIRST;
+			else if (strcmp(optarg, "last") == 0)
+				f_groupdir = GRP_DIR_LAST;
+			else
+				errx(2, "unsupported --group-directories value '%s' (must be first or last)",
+				    optarg);
+			break;
 		case COLOR_OPT:
 #ifdef COLORLS
 			if (optarg == NULL || do_color_always(optarg))
@@ -1004,7 +1026,7 @@ label_out:
 static int
 mastercmp(const FTSENT * const *a, const FTSENT * const *b)
 {
-	int a_info, b_info;
+	int a_info, b_info, dir;
 
 	a_info = (*a)->fts_info;
 	if (a_info == FTS_ERR)
@@ -1023,5 +1045,10 @@ mastercmp(const FTSENT * const *a, const FTSENT * const *b)
 		if (b_info == FTS_D)
 			return (-1);
 	}
+
+	if (f_groupdir != GRP_NONE)
+		if ((dir = (a_info == FTS_D) - (b_info == FTS_D)) != 0)
+			return (f_groupdir * dir);
+
 	return (sortfcn(*a, *b));
 }
diff --git a/bin/ls/util.c b/bin/ls/util.c
index 40610faa5fae..d83cae0e98d1 100644
--- a/bin/ls/util.c
+++ b/bin/ls/util.c
@@ -219,9 +219,9 @@ usage(void)
 {
 	(void)fprintf(stderr,
 #ifdef COLORLS
-	"usage: ls [-ABCFGHILPRSTUWZabcdfghiklmnopqrstuvwxy1,] [--color=when] [-D format]"
+	"usage: ls [-ABCFGHILPRSTUWZabcdfghiklmnopqrstuvwxy1,] [--color=when] [-D format] [--group-directories=]"
 #else
-	"usage: ls [-ABCFHILPRSTUWZabcdfghiklmnopqrstuvwxy1,] [-D format]"
+	"usage: ls [-ABCFHILPRSTUWZabcdfghiklmnopqrstuvwxy1,] [-D format] [--group-directories=]"
 #endif
 		      " [file ...]\n");
 	exit(1);