~sircmpwn/ctools

8c80a9e6965ffb47b94bc5cb6545b72577cbf33c — Drew DeVault 1 year, 4 months ago 317104f
Implement comm
5 files changed, 350 insertions(+), 1 deletions(-)

M STATUS
M meson.build
A src/comm.c
A test/comm
M test/meson.build
M STATUS => STATUS +1 -1
@@ 25,7 25,7 @@ T       cal
  D     chown
  D     cksum
  D     cmp
T       comm
  D     comm
      N command
    W   compress
T       cp

M meson.build => meson.build +1 -0
@@ 18,6 18,7 @@ oneshots = [
	'chown',
	'cksum',
	'cmp',
	'comm',
	'false',
	'logname',
	'nice',

A src/comm.c => src/comm.c +188 -0
@@ 0,0 1,188 @@
#include <assert.h>
#include <getopt.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>

enum display_mode {
	LINES_LEFT = 1 << 0,
	LINES_RIGHT = 1 << 1,
	LINES_BOTH = 1 << 2,
};

static void
usage(void)
{
	fprintf(stderr, "usage: comm [-123] file1 file2\n");
}

static int
clamp(int n, int min, int max)
{
	return n < min ? min : n > max ? max : n;
}

static const char *
leadfor(int diff, unsigned int display_mode)
{
	const unsigned int leftright = LINES_LEFT | LINES_RIGHT;
	switch (diff) {
	case 0:
		if ((display_mode & leftright) == leftright) {
			return "\t\t";
		} else if ((display_mode & leftright)) {
			return "\t";
		}
		return "";
	case 1:
		return (display_mode & LINES_LEFT) ?  "\t" : "";
	case -1:
		/* fallthrough */
	default:
		assert(false);
	}
}

int
main(int argc, char *argv[])
{
	unsigned int display_mode =
		LINES_LEFT | LINES_RIGHT | LINES_BOTH;

	char c;
	while ((c = getopt(argc, argv, "123")) != -1) {
		switch (c) {
		case '1':
			display_mode &= ~LINES_LEFT;
			break;
		case '2':
			display_mode &= ~LINES_RIGHT;
			break;
		case '3':
			display_mode &= ~LINES_BOTH;
			break;
		}
	}

	if (argc - optind != 2) {
		usage();
		return 1;
	}

	FILE *f[2] = { 0 };
	char *names[2];
	names[0] = argv[optind], names[1] = argv[optind + 1];
	if (names[0][0] == '-' && names[0][1] == '\0') {
		f[0] = stdin;
	}
	if (names[1][0] == '-' && names[1][1] == '\0') {
		f[1] = stdin;
	}

	if (f[0] == stdin && f[1] == stdin) {
		fprintf(stderr, "comm: behavior is unspecified when "
				"both inputs are stdin\n");
		return 1;
	}

	if (strcmp(names[0], names[1]) == 0) {
		struct stat st;
		if (stat(names[0], &st) != 0) {
			perror(names[0]);
		}
		if (S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode)
				|| S_ISFIFO(st.st_mode)) {
			fprintf(stderr, "comm: behavior is unspecified when "
					"both inputs are the same special "
					"file\n");
			return 1;
		}
	}

	if (!f[0] && (f[0] = fopen(names[0], "r")) == NULL) {
		perror(names[0]);
		return 1;
	}
	if (!f[1] && (f[1] = fopen(names[1], "r")) == NULL) {
		perror(names[1]);
		return 1;
	}

	int ret = 0, diff = 0;
	char *lp[2] = { 0 }, *_lp[2] = { 0 };
	size_t sz[2] = { 0 };

	while (true) {
		for (int i = 0; i < 2; ++i) {
			if ((diff != 0 && i == (diff < 0)) || feof(f[i])) {
				/* Processing unique lines */
				continue;
			}
			if (lp[i]) {
				free(_lp[i]);
				_lp[i] = strdup(lp[i]);
			}
			if (getline(&lp[i], &sz[i], f[i]) > 0) {
				if (_lp[i] && strcmp(_lp[i], lp[i]) > 0) {
					fprintf(stderr, "Error: the behavior "
							"of this utility is "
							"unspecified for "
							"unsorted files.\n");
					ret = 1;
					goto cleanup;
				}
				continue;
			}
			/* Error occured */
			if (ferror(f[i])) {
				ret = 1;
				perror(names[i]);
				goto cleanup;
			} else if (!feof(f[!i])) {
				lp[i] = NULL;
				continue;
			} else if (feof(f[i])) {
				goto cleanup;
			}
		}

		if (lp[0] == NULL) {
			diff = 1;
		} else if (lp[1] == NULL) {
			diff = -1;
		} else {
			diff = strcmp(lp[0], lp[1]);
			diff = clamp(diff, -1, 1);
		}

		switch (diff) {
		case -1:
			if ((display_mode & LINES_LEFT)) {
				printf("%s", lp[0]);
			}
			break;
		case 0:
			if ((display_mode & LINES_BOTH)) {
				printf("%s%s", leadfor(
					diff, display_mode), lp[0]);
			}
			break;
		case 1:
			if ((display_mode & LINES_RIGHT)) {
				printf("%s%s", leadfor(
					diff, display_mode), lp[1]);
			}
			break;
		}
	}

cleanup:
	for (int i = 0; i < 2; ++i) {
		fclose(f[i]);
		free(lp[i]);
		free(_lp[i]);
	}
	return ret;
}

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

compare_with_ref() (
	ref=$(cat "$TMPDIR"/"$1")
	ref_sum=$(printf '%s' "$ref" | cksum)
	shift
	(
		cd "$TMPDIR"
		actual=$(comm "$@")
		actual_sum=$(printf '%s' "$actual" | cksum)
		if [ "$ref_sum" != "$actual_sum" ]
		then
			printf 'Args: %s\nExpected:\n%s\nActual:\n%s\n' \
				"$*" "$ref" "$actual"
			return 1
		fi
	)
)

cat >"$TMPDIR"/case_a <<EOF
1 same line
2 same line
3 different line A
4 different line A
5 same line
EOF

cat >"$TMPDIR"/case_b <<EOF
1 same line
2 same line
3 different line B
4 different line B
5 same line
EOF

cat >"$TMPDIR"/ref_ab_noflags <<EOF
		1 same line
		2 same line
3 different line A
	3 different line B
4 different line A
	4 different line B
		5 same line
EOF

should_handle_samelength() (
	compare_with_ref ref_ab_noflags case_a case_b
)

cat >"$TMPDIR"/ref_ab_1 <<EOF
	1 same line
	2 same line
3 different line B
4 different line B
	5 same line
EOF

cat >"$TMPDIR"/ref_ab_2 <<EOF
	1 same line
	2 same line
3 different line A
4 different line A
	5 same line
EOF

cat >"$TMPDIR"/ref_ab_3 <<EOF
3 different line A
	3 different line B
4 different line A
	4 different line B
EOF

cat >"$TMPDIR"/ref_ab_12 <<EOF
1 same line
2 same line
5 same line
EOF

cat >"$TMPDIR"/ref_ab_13 <<EOF
3 different line B
4 different line B
EOF

cat >"$TMPDIR"/ref_ab_23 <<EOF
3 different line A
4 different line A
EOF

should_handle_flags() (
	compare_with_ref ref_ab_1 -1 -- case_a case_b || return 1
	compare_with_ref ref_ab_2 -2 case_a case_b || return 1
	compare_with_ref ref_ab_3 -3 -- case_a case_b || return 1
	compare_with_ref ref_ab_12 -12 case_a case_b || return 1
	compare_with_ref ref_ab_13 -13 -- case_a case_b || return 1
	compare_with_ref ref_ab_23 -23 case_a case_b || return 1
)

cat >"$TMPDIR"/case_c <<EOF
1 same line
2 same line
3 different line A
4 different line A
5 same line
6 extra line 1
7 extra line 2
EOF

cat >"$TMPDIR"/case_d <<EOF
1 same line
2 same line
3 different line B
4 different line B
5 same line
EOF

cat >"$TMPDIR"/ref_cd <<EOF
		1 same line
		2 same line
3 different line A
	3 different line B
4 different line A
	4 different line B
		5 same line
6 extra line 1
7 extra line 2
EOF

cat >"$TMPDIR"/ref_cd_reverse <<EOF
		1 same line
		2 same line
	3 different line A
3 different line B
	4 different line A
4 different line B
		5 same line
	6 extra line 1
	7 extra line 2
EOF

should_handle_difflen() (
	compare_with_ref ref_cd case_c case_d || return 1
	compare_with_ref ref_cd_reverse case_d case_c || return 1
)

should_handle_stdin() (
	ref=$(cksum <"$TMPDIR"/ref_ab_noflags)
	actual=$(comm "$TMPDIR"/case_a - <"$TMPDIR"/case_b | cksum)
	[ "$ref" = "$actual" ] || return 1
	ref=$(cksum <"$TMPDIR"/ref_ab_noflags)
	actual=$(comm - "$TMPDIR"/case_b <"$TMPDIR"/case_a | cksum)
	[ "$ref" = "$actual" ] || return 1
)

runtests \
	should_handle_samelength \
	should_handle_flags \
	should_handle_difflen \
	should_handle_stdin

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