~sircmpwn/ctools

0e9f63c42367ef836a3dbe9369cb987558b0d9e3 — Christopher Vittal 1 year, 2 months ago d3db9d4
Add dirname
8 files changed, 179 insertions(+), 1 deletions(-)

M STATUS
M doc/ctools.7.scd
A doc/dirname.1.scd
M doc/meson.build
M meson.build
A src/dirname.c
A test/dirname
M test/meson.build
M STATUS => STATUS +1 -1
@@ 39,7 39,7 @@ T       dd
    W   delta
T       df
T       diff
T       dirname
  D     dirname
T       du
T       echo
T       ed*

M doc/ctools.7.scd => doc/ctools.7.scd +2 -0
@@ 35,6 35,8 @@ shell environment. These tools are used for tasks such as:
:  Compare two files
|  *comm*(1)
:  Compare two sorted files
|  *dirname*(1)
:  Print the directory part of a pathname
|  *false*(1)
:  Exit with status code 1
|  *head*(1)

A doc/dirname.1.scd => doc/dirname.1.scd +30 -0
@@ 0,0 1,30 @@
dirname(1) "ctools"

# NAME

dirname - print the directory part of a pathname

# SYNOPSIS

*dirname* _string_

# DESCRIPTION

*dirname* will interpret _string_ as a pathname and print only the directory
portion of the path.

# UNSPECIFIED BEHAVIOR

The POSIX standard does not unambiguously specify the behavior of this command
under certain conditions. Under such conditions, the ctools implementation of
*dirname* behaves as follows:

- If the directory part of _string_ is equal to "//", *dirname* may randomly
  choose to output either "/" or "//".

# DISCLAIMER

This command is part of ctools and is compatible with POSIX-1.2017, and may
optionally support XSI extensions. This man page is not intended to be a
complete reference, and where it disagrees with the specification, the
specification takes precedence.

M doc/meson.build => doc/meson.build +1 -0
@@ 12,6 12,7 @@ man_files = [
	'cksum.1',
	'cmp.1',
	'comm.1',
	'dirname.1',
	'false.1',
	'head.1',
	'link.1',

M meson.build => meson.build +1 -0
@@ 19,6 19,7 @@ oneshots = [
	'cksum',
	'cmp',
	'comm',
	'dirname',
	'false',
	'head',
	'logname',

A src/dirname.c => src/dirname.c +88 -0
@@ 0,0 1,88 @@
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

static void
usage(void)
{
	fprintf(stderr, "usage: dirname string\n");
}

int
main(int argc, char *argv[])
{
	if (getopt(argc, argv, "") != -1) {
		usage();
		return 1;
	}
	argc -= optind;
	argv += optind;
	if (argc <= 0 || argc > 1) {
		usage();
		return 1;
	}

	char *string = argv[0];
	size_t len = strlen(string);

	srand((unsigned int)time(NULL));

	/* If string is "//", skip steps 2 to 5. */
	if (strcmp(string, "//") == 0) {
		goto step6;
	}

	/* If string consists entirely of '/' characters, string shall
	 * be set to a single '/' character.  In this case, skip steps
	 * 3 to 8. */
	if (len > 0 && strspn(string, "/") == len) {
		string = "/";
		goto output;
	}

	/* If there are any trailing '/' characters in string, they
	 * shall be removed */
	while (string[len - 1] == '/') {
		string[--len] = '\0';
	}

	/* If there are no '/' characters remaining in string, string
	 * shall be set to a single '.' character. In this case skip
	 * steps 5 to 8 */
	if (memchr(string, '/', len) == NULL) {
		string = ".";
		goto output;
	}

	/* If there are any trailing non-'/' characters in string, they
	 * shall be removed. */
	while (len > 0 && string[len - 1] != '/') {
		string[--len] = '\0';
	}

step6:
	/* If the remaining string is "//" it is implementation defined whether
	 * steps 7 and 8 are skipped or processed */
	if (strcmp(string, "//") == 0 && rand() % 2 == 0) {
		goto output;
	}

	/* If there are any trailing '/' characters in string, they shall be
	 * removed */
	while (len > 0 && string[len - 1] == '/') {
		string[--len] = '\0';
	}

	/* If the remaining string is empty, string shall be set to a single '/'
	 * character. */
	if (len == 0) {
		string = "/";
	}

output:
	/* The resulting string shall be written to standard output */
	puts(string);
	return 0;
}

A test/dirname => test/dirname +55 -0
@@ 0,0 1,55 @@
#!/bin/sh
. "$HARNESS" dirname

should_handle_null_string() (
	dn="$(dirname '')"
	[ "$dn" = "." ]
)

should_handle_single_slash() (
	dn="$(dirname '/')"
	[ "$dn" = "/" ]
)

should_handle_double_slash() (
	dn="$(dirname '//')"
	[ "$dn" = "/" ] || [ "$dn" = "//" ]
)

should_handle_trailing_slash() (
	dn="$(dirname 'foo/')"
	[ "$dn" = "." ]
)

should_handle_no_slash() (
	dn="$(dirname 'foo')"
	[ "$dn" = "." ]
)

should_handle_double_slashes() (
	dn="$(dirname '//foo//bar//')"
	[ "$dn" = "//foo" ]
)

should_handle_leading_slash() (
	dn="$(dirname '/foo')"
	[ "$dn" = "/" ]
)

should_handle_dirname() (
	dn="$(dirname 'foo/bar/baz')"
	[ "$dn" = "foo/bar" ]
)

should_handle_ddash dirname foo/bar/

runtests \
	should_handle_ddash \
	should_handle_null_string \
	should_handle_single_slash \
	should_handle_double_slash \
	should_handle_trailing_slash \
	should_handle_no_slash \
	should_handle_double_slashes \
	should_handle_leading_slash \
	should_handle_dirname

M test/meson.build => test/meson.build +1 -0
@@ 9,6 9,7 @@ test_files = [
	'cksum',
	'cmp',
	'comm',
	'dirname',
	'false',
	'head',
	'logname',