]> git.scottworley.com Git - uniqt/blob - uniqt.c
Release 1.0.0
[uniqt] / uniqt.c
1 /*
2 * uniqt: uniq -c, but for time
3 * Copyright (C) 2023 Scott Worley <scottworley@scottworley.com>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17
18 #define _POSIX_C_SOURCE 200809L
19
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <time.h>
24
25 void die(const char *message) {
26 fputs(message, stderr);
27 fputc('\n', stderr);
28 exit(1);
29 }
30
31 void die_err(const char *message) {
32 perror(message);
33 exit(1);
34 }
35
36 char *encode_time(time_t t) {
37 struct tm tm;
38 localtime_r(&t, &tm);
39 const size_t size = 20;
40 char *out = (char *)malloc(size);
41 if (strftime(out, size, "%Y %m %d %H %M %S", &tm) != size - 1)
42 die("Couldn't format time");
43 return out;
44 }
45
46 typedef struct {
47 char *time_format;
48 } conf_t;
49
50 typedef struct {
51 time_t start, end;
52 } time_range_t;
53
54 const time_t NULL_TIME = (time_t)-1;
55
56 static time_range_t make_time_range(time_t t) { return (time_range_t){t, t}; }
57
58 static void extend_time_range(time_range_t *r, time_t t) { r->end = t; }
59
60 static char *read_line() {
61 char *line = NULL;
62 int scanf_ret = scanf("%m[^\n]", &line);
63 if (scanf_ret == EOF && ferror(stdin))
64 die_err("Error reading");
65 int newline = getchar();
66 if (newline != EOF && newline != (int)'\n')
67 die("Expected newline");
68 if (scanf_ret == 0 && newline == (int)'\n')
69 return strdup("\n");
70 return line;
71 }
72
73 static void format_time(conf_t *conf, char *buf, size_t size, time_t *t) {
74 struct tm *tm = localtime(t);
75 if (tm == NULL)
76 die_err("Couldn't unpack time");
77 if (strftime(buf, size, conf->time_format, tm) == 0)
78 die_err("Couldn't format time");
79 }
80
81 static void write_line(conf_t *conf, time_range_t *range, char *line) {
82 if (line == NULL)
83 return;
84 if (conf->time_format) {
85 const size_t MAX_TIMESTAMP_LENGTH = 1024;
86 char a[MAX_TIMESTAMP_LENGTH];
87 char b[MAX_TIMESTAMP_LENGTH];
88 format_time(conf, a, MAX_TIMESTAMP_LENGTH, &range->start);
89 format_time(conf, b, MAX_TIMESTAMP_LENGTH, &range->end);
90 if (printf("%s %s %s\n", a, b, line) < 0)
91 die("Couldn't write");
92 } else {
93 if (printf("%ld %ld %s\n", range->start, range->end, line) < 0)
94 die("Couldn't write");
95 }
96 if (fflush(stdout) == EOF)
97 die_err("Couldn't flush");
98 }
99
100 static int same(char *a, char *b) { return a && b && strcmp(a, b) == 0; }
101
102 static void uniqt(conf_t *conf) {
103 char *current_line = NULL;
104 time_range_t current_time_range = make_time_range(NULL_TIME);
105 for (;;) {
106 char *new_line = read_line();
107 if (new_line == NULL)
108 break;
109 time_t now = time(NULL);
110 if (same(current_line, new_line)) {
111 free(new_line);
112 extend_time_range(&current_time_range, now);
113 } else {
114 write_line(conf, &current_time_range, current_line);
115 free(current_line);
116 current_line = new_line;
117 current_time_range = make_time_range(now);
118 }
119 }
120 write_line(conf, &current_time_range, current_line);
121 free(current_line);
122 }
123
124 void usage() { die("usage: uniqt [-f time_format]"); }
125
126 conf_t parse_command_line(int argc, char *argv[]) {
127 conf_t conf;
128 conf.time_format = NULL;
129
130 for (int i = 1; i < argc; i++) {
131 if (strcmp(argv[i], "-f") == 0) {
132 if (i + 1 >= argc)
133 die("-f requires an argument");
134 i++;
135 conf.time_format = argv[i];
136 } else
137 usage();
138 }
139
140 return conf;
141 }
142
143 int main(int argc, char *argv[]) {
144 conf_t conf = parse_command_line(argc, argv);
145 uniqt(&conf);
146 return 0;
147 }