2 * uniqt: uniq -c, but for time
3 * Copyright (C) 2023 Scott Worley <scottworley@scottworley.com>
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.
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.
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/>.
18 #define _POSIX_C_SOURCE 200809L
25 void die(const char *message
) {
26 fputs(message
, stderr
);
31 void die_err(const char *message
) {
36 char *encode_time(time_t t
) {
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");
54 const time_t NULL_TIME
= (time_t)-1;
56 static time_range_t
make_time_range(time_t t
) { return (time_range_t
){t
, t
}; }
58 static void extend_time_range(time_range_t
*r
, time_t t
) { r
->end
= t
; }
60 static char *read_line() {
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')
73 static void format_time(conf_t
*conf
, char *buf
, size_t size
, time_t *t
) {
74 struct tm
*tm
= localtime(t
);
76 die_err("Couldn't unpack time");
77 if (strftime(buf
, size
, conf
->time_format
, tm
) == 0)
78 die_err("Couldn't format time");
81 static void write_line(conf_t
*conf
, time_range_t
*range
, char *line
) {
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");
93 if (printf("%ld %ld %s\n", range
->start
, range
->end
, line
) < 0)
94 die("Couldn't write");
96 if (fflush(stdout
) == EOF
)
97 die_err("Couldn't flush");
100 static int same(char *a
, char *b
) { return a
&& b
&& strcmp(a
, b
) == 0; }
102 static void uniqt(conf_t
*conf
) {
103 char *current_line
= NULL
;
104 time_range_t current_time_range
= make_time_range(NULL_TIME
);
106 char *new_line
= read_line();
107 if (new_line
== NULL
)
109 time_t now
= time(NULL
);
110 if (same(current_line
, new_line
)) {
112 extend_time_range(¤t_time_range
, now
);
114 write_line(conf
, ¤t_time_range
, current_line
);
116 current_line
= new_line
;
117 current_time_range
= make_time_range(now
);
120 write_line(conf
, ¤t_time_range
, current_line
);
124 void usage() { die("usage: uniqt [-f time_format]"); }
126 conf_t
parse_command_line(int argc
, char *argv
[]) {
128 conf
.time_format
= NULL
;
130 for (int i
= 1; i
< argc
; i
++) {
131 if (strcmp(argv
[i
], "-f") == 0) {
133 die("-f requires an argument");
135 conf
.time_format
= argv
[i
];
143 int main(int argc
, char *argv
[]) {
144 conf_t conf
= parse_command_line(argc
, argv
);