~sircmpwn/ctools

11e35c0911351351a59a80103a31fe79f1883bac — Nicolai Dagestad 1 year, 2 months ago 30a7ec4
Implement head
8 files changed, 148 insertions(+), 1 deletions(-)

M STATUS
M doc/ctools.7.scd
A doc/head.1.scd
M doc/meson.build
M meson.build
A src/head.c
A test/head
M test/meson.build
M STATUS => STATUS +1 -1
@@ 61,7 61,7 @@ T       getconf
    W   getopts
T       grep
T       hash
T       head
  D     head
    W   iconv
T       id
T       ipcrm*

M doc/ctools.7.scd => doc/ctools.7.scd +2 -0
@@ 37,6 37,8 @@ shell environment. These tools are used for tasks such as:
:  Compare two sorted files
|  *false*(1)
:  Exit with status code 1
|  *head*(1)
:  Print the beginning of files
|  *link*(1)
:  Create a hard link
|  *logname*(1)

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

# NAME

head - print the beginning of files

# SYNOPSIS

*head* [-n _n_] _file_...

# DESCRIPTION

*head* will print the first _n_ lines of each input file specified.

# OPTIONS

*-n* _n_
	Specify the number of lines to print. The default is 10.

# 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
@@ 13,6 13,7 @@ man_files = [
	'cmp.1',
	'comm.1',
	'false.1',
	'head.1',
	'link.1',
	'logname.1',
	'nice.1',

M meson.build => meson.build +1 -0
@@ 20,6 20,7 @@ oneshots = [
	'cmp',
	'comm',
	'false',
	'head',
	'logname',
	'nice', # Included in base but only effective under XSI
	'true',

A src/head.c => src/head.c +93 -0
@@ 0,0 1,93 @@
#include <errno.h>
#include <getopt.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static void
usage(void)
{
	fprintf(stderr, "usage: head [−n number] [file...]\n");
}


static int
head(const char *path, int length, bool header, bool newline)
{
	FILE *file;
	if (path[0] == '-' && path[1] == '\0') {
		file = stdin;
	} else {
		if ((file = fopen(path, "r")) == NULL) {
			perror(path);
			return 1;
		}
	}

	ssize_t n;
	size_t buf_size = BUFSIZ;
	int curr_line = 0;
	char *buf = calloc(sizeof(char), buf_size);
	if (header) {
		printf("%s==> %s <==\n", newline? "\n" : "", path);
	}

	while ((n = getline(&buf, &buf_size, file)) > 0) {
		if (curr_line >= length) {
			break;
		}
		ssize_t offs = 0;
		while (offs < n) {
			ssize_t o = printf("%s", buf);
			if (o < 0) {
				perror("write");
				fclose(file);
				return 1;
			}
			offs += o;
		}
		curr_line++;
	}
	fclose(file);
	return 0;
}

int
main(int argc, char *argv[])
{
	int length = 10;
	bool show_header = false;
	char opt;
	while ((opt = getopt(argc, argv, "n:")) != -1) {
		switch (opt) {
		case 'n': {
			char *endptr;
			length = strtol(optarg, &endptr, 10);
			if (endptr[0] != '\0') {
				fprintf(stderr, "head: invalid number of "
						"lines: '%s'\n", optarg);
				return 1;
			}
			break;
		}
		default:
			usage();
			return 1;
		}
	}

	if ((argc - optind) < 1) {
		return head("-", length, false, false);
	}

	show_header = (optind - argc) > 1;
	for (int i = optind; i<argc; ++i){
		if (head(argv[i], length, show_header, i != optind) > 0){
			return 1;
		}
	}

	return 0;
}

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

should_read_from_stdin() (
	[ "$(echo Test Statement | head)" = "Test Statement" ]
)

should_print_10_from_stdin() (
	[ $(largefile | head | wc -l) -eq 10 ]
)

should_handle_largefile_from_stdin() (
	[ $(largefile | head -n 200 | wc -l) -eq 200 ]
)

should_handle_non_existent_file() (
	[ ! $(head inexistantfile) ]
)

runtests \
	should_read_from_stdin \
	should_handle_non_existent_file \
	should_print_10_from_stdin \
	should_handle_largefile_from_stdin

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