~sircmpwn/ctools

Implement cal v3 PROPOSED

Lorenzo Candeago: 1
 Implement cal

 3 files changed, 387 insertions(+), 0 deletions(-)
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~sircmpwn/ctools/patches/10121/mbox | git am -3
Learn more about email & git
View this thread in the archives

[PATCH v3] Implement cal Export this patch

Sorry for the mess with git send-email. I didn't push the previous
changes to a repository,and i don't have access currently to the laptop.
Since I've moved code arround quite a bit, i thought it would be easier
just to send a new patch. Hope i didn't do too much of a mess.

I've tried to address the issues raised in your previous reviews:

-Highlight today only if we are in a tty (and hence we assume that we can 
use ANSI escape sequences)

-The number of characters per row is the env variable COLUMNS if we are
able to retrieve it, otherwise default to 80. It works on mrsh.

-remove some of the too deep nested for loops in the previous patch

-I've choose not to use log10 to get the number of digits to avoid
including math.h, but if its ok, I can include it.

An example of test the behaviour on a system where cal already exists:
diff --ignore-all-space  <(cal 1752) <(./build/cal 1752)

Thanks,
best,
Lorenzo
---
doc/cal.1.scd |  41 ++++++
meson.build   |   1 +
src/cal.c     | 345 ++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 387 insertions(+)
create mode 100644 doc/cal.1.scd
create mode 100644 src/cal.c

diff --git a/doc/cal.1.scd b/doc/cal.1.scd
new file mode 100644
index 0000000..ce25ec5
--- /dev/null
+++ b/doc/cal.1.scd
@@ -0,0 +1,41 @@
cal(1) "ctools"

# NAME

cal - print a calendar

# SYNOPSIS

*cal* [[_month_] _year_]

# DESCRIPTION

*cal* will print a calendar to _stdout_ using the Julian calendar for dates
from January 1, 1 thourgh September 2,1752 and the Gregorian calendar for
dates from September 14, 1752 throigh December 31, 9999.
If no operand is given, *cal* will print one-month calendar for the current
month.
If only the _year_ operand is given, *cal* will print a calendar for the 12
months of the given year.
If both _month_ and _year_ are specified, *cal* will print a calendar for the
given month and year.

# OPTIONS

*month*
       Specify the month to be displayed, represented as a decimal integer
       from 1 (January) to 12 (December).
*year*
       Specify the year for which the calendar is displayed, represented as a
       decimal integer from 1 to 9999.

# NOTES
If run in a tty, cal will highlight, using ANSI escape sequences the current
day.

# 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.
diff --git a/meson.build b/meson.build
index fbf4840..6195c1e 100644
--- a/meson.build
+++ b/meson.build
@@ -12,6 +12,7 @@ project(

oneshots = [
	'basename',
	'cal',
	'cat',
	'chgrp',
	'chmod',
diff --git a/src/cal.c b/src/cal.c
new file mode 100644
index 0000000..21d5061
--- /dev/null
+++ b/src/cal.c
@@ -0,0 +1,345 @@
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

static void usage(void) { fprintf(stderr, "usage: cal [[month] year]\n"); }
enum {
	/*number of spaces between two days*/
	INNER_SPACE = 1,

	/*number of spaces between two months*/
	COL_ROW_SPACES = 3
};

struct config {
	int month_width;
	bool highlight;
	int cols;
	int n_rows;
	int n_cols;
	int total_month_width;
	int total_line_width;
	int current_year;
	int current_month;
	int current_day;
};

static void
setup_config(struct config *c, struct tm *ct)
{
	/* month width: 2 chars per day, 6 INNER_SPACE between days*/
	c->month_width = 2 * 7 + 6 * INNER_SPACE;

	/* default to 80 if is not possible to get the width of the terminal*/
	const char *cols = getenv("COLUMNS");
	c->cols = cols ? atoi(cols) : 80;
	/*
	 * If we are in a tty, we assume we can use ANSI escape characters to
	 * highlight today when printing the current month
	 */
	c->highlight = isatty(STDOUT_FILENO);

	/*
	 * To get a decent printing of the calendar in the terminal, print the
	 * months in rows of either 1, 2 or 3 months per row based on the width
	 * of the terminal
	 */
	if (c->cols >= 2 * COL_ROW_SPACES + 3 * c->month_width) {
		c->n_cols = 3;
	} else if (c->cols >= COL_ROW_SPACES + 2 * c->month_width) {
		c->n_cols = 2;
	} else {
		c->n_cols = 1;
	}
	c->n_rows = 12 / c->n_cols;

	c->total_line_width = c->n_cols * c->month_width + (c->n_cols - 1) *
		COL_ROW_SPACES;

	c->current_year = ct->tm_year + 1900;
	c->current_month = ct->tm_mon;
	c->current_day = ct->tm_mday;
}

const char *days_of_the_week[7] = {"Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"};

const int days_in_months[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

/*
 * On from September 14, 1752, the Gregorian calendar has been adopted. Hence,
 * the first week of September 1952 is the following, where -1 will be replaced
 * with two spaces later when printing the calendar.
 */
const int first_week_sep_1752[7] = {-1, -1, 1, 2, 14, 15, 16};

const char *months[12] = {"January","February","March",	"April","May","June",
	"July",	"August","September","October","November","December"};

static int
n_digits(int n)
{
	int n_digits = 0;
	while (n != 0) {
		n /= 10;
		++n_digits;
	}
	return n_digits;
}

static int
days(int year, int month)
{
	/* check if February is a leap year */
	if (month == 1) {
		if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) {
			return 29;
		}
	}
	return days_in_months[month];
}

static int
first_day_of_month(int year, int month)
{
	/* Zeller's congruence */
	int h;
	int y = month <= 1 ? year - 1 : year;
	int m = month > 1 ? month + 1 : month + 13;
	if (year < 1752 || (year == 1752 && month < 9)) {
		h = 1 + 13 * (m + 1) / 5 + y + y / 4 + 5;
	} else {
		h = 1 + (13 * (m + 1)) / 5 + y + y / 4 - y / 100 + y / 400;
	}
	h = h % 7;
	/*Zeller's formula starts from 0 = Sat, adjust it to get 0=Sun*/
	return h - 1 >= 0 ? h - 1 : 6;
}

struct month {
	char *month_year_header;
	char *month_header;
	char year[5];
	int days_numbers[6][7];
	int day_highlight;
};

void
center_print(char *dest, const char *src, size_t dest_size)
{
	/*Fills	dest with spaces and then center src*/
	memset(dest, ' ', dest_size);
	dest[dest_size - 1] = '\0';
	int center = strlen(dest) / 2;
	size_t src_len = strlen(src);
	int start_index = center - src_len / 2;
	strncpy(&dest[start_index], src, src_len);
}

void
create_month(struct month *m,int year,int month,struct config *c)
{
	if (year == c->current_year && month == c->current_month &&
			c->highlight) {
		m->day_highlight = c->current_day;
	} else {
		m->day_highlight = -2;
	}

	int year_digits = n_digits(year);
	size_t l = strlen(months[month]) + year_digits + 1;
	char s[l + 1];
	sprintf(s, "%s %d", months[month], year);

	m->month_year_header = malloc(c->month_width);
	center_print(m->month_year_header, s, c->month_width + 1);

	char y[year_digits + 1];
	sprintf(y, "%d", year);
	center_print(m->year, y, year_digits + 1);

	m->month_header = malloc(c->month_width + 1);
	center_print(m->month_header, months[month], c->month_width + 1);
	/*
	 *    September 1752
         * Su Mo Tu We Th Fr Sa
         *        1  2 14 15 16
         * 17 18 19 20 21 22 23
         * 24 25 26 27 28 29 30
        */
	if (year == 1752 && month == 8) {
		memcpy(m->days_numbers[0],
			first_week_sep_1752,
			sizeof(first_week_sep_1752));
		int k = 17;
		for (int i = 7; i < 21; i++) {
			m->days_numbers[i / 7][i % 7] = k++;
		}
		for (int i = 21; i < 6 * 7; i++) {
			m->days_numbers[i / 7][i % 7] = -1;
		}
	} else {

		int first_day = first_day_of_month(year, month);
		int days_in_month = days(year, month);

		for (int i = 0; i < first_day; i++) {
			m->days_numbers[0][i] = -1;
		}
		int f = first_day + days_in_month;
		int k = 1;
		for (int i = first_day; i < f; i++) {
			m->days_numbers[i / 7][i % 7] = k++;
		}

		for (int i = f; i < 6 * 7; i++) {
			m->days_numbers[i / 7][i % 7] = -1;
		}
	}
}


static void
print_week(struct month *m, int week_number)
{
	int *r = m -> days_numbers[week_number];
	for (int i = 0; i < 7; i++) {
		int d = r[i];
		if (d == -1) {
			printf("%2s", "");
		} else if (m->day_highlight == d) {
			printf("%s%2d%s", "\033[7m", d, "\033[27m");
		} else {
			printf("%2d", d);
		}
		if (i < 6) {
			printf("%*s", INNER_SPACE, "");
		}
	}
}

void
print_cal(int year, int month, struct config *c)
{
	char week_header[c->month_width + 1];
	/*Skip left space for the first day*/
	sprintf(week_header, "%s", days_of_the_week[0]);
	for (int i = 1; i < 7; i++) {
		sprintf(&week_header[2 + (i - 1) * (INNER_SPACE + 2)],
			"%*s", 2 + INNER_SPACE,	days_of_the_week[i]);
	}
	/* if month is specified, print a single month */
	if (month != -1) {
		struct month m;
		create_month(&m, year, month, c);
		printf("%s\n", m.month_year_header);
		printf("%s\n", week_header);

		for (int w = 0; w < 6; w++) {
			print_week(&m,w);
			printf("\n");
		}
		printf("\n");
	} else {
		struct month months[12];

		for (int i = 0; i < 12; i++) {
			create_month(&months[i], year, i, c);
		}

		char header[c->total_line_width];
		center_print(header, months[0].year, c->total_line_width);
		printf("%s\n", header);
		printf("\n");

		for (int r = 0; r < c->n_rows; r++) {

			/* January, February,... */
			for (int p = 0; p < c->n_cols; p++) {
				printf("%-*s",
					c->month_width + COL_ROW_SPACES,
					months[p + r * c->n_cols]
						.month_header);
			}
			printf("\n");

			/* Su Mo Tu We... */
			for (int p = 0; p < c->n_cols; p++) {
				printf("%-*s",
					c->month_width + COL_ROW_SPACES,
					week_header);
			}
			printf("\n");

			/* each month has 6 rows to be printed */
			for (int w = 0; w < 6; w++) {
				for (int p = 0; p < c->n_cols; p++) {
					print_week(&months[p + r * c->n_cols],
							w);
					printf("%*s", COL_ROW_SPACES, "");
				}

				printf("\n");
			}
		}
	}
}

int
main(int argc, char *argv[])
{
	if (argc > 3) {
		usage();
		return 1;
	}

	int month = -1;
	int year = -1;

	time_t now = time(0);
	struct tm *ct;
	ct = localtime(&now);

	/* if no argument is passed consider current date years since 1900 */
	if (argc == 1) {
		year = ct->tm_year + 1900;
		/* tm_mon in [0,11] as for the other inputs */
		month = ct->tm_mon;
	} else if (argc == 2) {
		/* only year specified */
		char *endptr;
		errno = 0;
		year = strtol(argv[1], &endptr, 10);
		if (endptr[0] != '\0' || year > 9999 || year <= 0 ||
				errno != 0) {
			fprintf(stderr, "Illegal year value: '%s'\n", argv[1]);
			return 1;
		}
	} else if (argc == 3) {
		errno = 0;
		char *endptr;
		month = strtol(argv[1], &endptr, 10);
		if (errno != 0 || endptr[0] != '\0' || month > 12 ||
				month <= 0) {
			fprintf(stderr, "Illegal month value: '%s'\n", argv[2]);
			return 1;
		} else {
			/* input month [1-12] we want [0-11] */
			month = month - 1;
		}
		year = strtol(argv[2], &endptr, 10);
		if (errno != 0 || endptr[0] != '\0' || year > 9999 ||
				year <= 0) {
			fprintf(stderr, "Illegal year value: '%s'\n", argv[1]);
			return 1;
		}
	}
	struct config c;
	setup_config(&c, ct);

	print_cal(year, month, &c);
	return 0;
}
-- 
2.26.0