sdep

A simple "date+event" line parser
git clone https://git.tronto.net/sdep
Download | Log | Files | Refs | README | LICENSE

commit 3a33fa3c7921c69efbc9beaaa922452ba2eaf271
parent 022d9c173d78c04ec2569a2394e2b04b81d60794
Author: Sebastiano Tronto <sebastiano@tronto.net>
Date:   Mon, 15 May 2023 18:28:45 +0200

Makefile fix

Diffstat:
MMakefile | 2+-
Asdep-0.2/LICENSE | 21+++++++++++++++++++++
CMakefile -> sdep-0.2/Makefile | 0
Asdep-0.2/sdep.1 | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asdep-0.2/sdep.c | 277+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 393 insertions(+), 1 deletion(-)

diff --git a/Makefile b/Makefile @@ -32,7 +32,7 @@ clean: dist: clean mkdir -p sdep-${VERSION} - cp -R LICENSE Makefile README sdep.1 sdep.c sdep-${VERSION} + cp -R LICENSE Makefile README.md sdep.1 sdep.c sdep-${VERSION} tar -cf sdep-${VERSION}.tar sdep-${VERSION} gzip sdep-${VERSION}.tar rm -rf sdep-${VERSION} diff --git a/sdep-0.2/LICENSE b/sdep-0.2/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021-2023 Sebastiano Tronto + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/sdep-0.2/Makefile diff --git a/sdep-0.2/sdep.1 b/sdep-0.2/sdep.1 @@ -0,0 +1,94 @@ +.Dd May 13, 2021 +.Dt SDEP 1 +.Os +.Sh NAME +.Nm sdep +.Nd a simple "date+event" line parser + +.Sh SYNOPSIS +.Nm +.Op Fl dv +.Op Ar +format +.Op Fl f Op Ar date +.Op Fl t Op Ar date +.Op Fl w Ar format +.Op Fl s Ar string + +.Sh DESCRIPTION +.Nm +reads lines of the form + +.Dl Ar "date text" + +from stdin and writes to stdout those lines such that +.Ar date +is between the dates specified by the +.Fl f +and +.Fl t +options, both defaulting to the current minute. +The dates should correspond to a unique minute in time. The format for +.Ar date +can be specified with the same syntax as for +.Xr date 1 . + +The options are as follows: +.Bl -tag -width Ds +.It Fl d +print the default date format and exit. +.It Fl f Op Ar date +initial date for the range (default: current minute). If +.Ar date +is not specified there will be no lower bound for the dates. +.It Fl s Ar string +change the string that separates the date from the text in the output +lines (deafult: "\t"). +.It Fl t Op Ar date +final date for the range (default: current minute). If +.Ar date +is not specified then there will be no upper bound for the dates. +.It Fl v +print version information and exit. +.It Fl w Ar format +change the format in which the date is written in the output lines. + +.Sh EXAMPLES +If +.Ar events.txt +contains lines formatted as +.Ar "date text" +then + +.Dl sdep -f <events.txt + +will print all the lines whose date is in the past, while + +.Dl sdep -t -w "%A" <events.txt + +will print all lines whose date is in the future, showing only the day of the +week and the text. + +.Dl sdep -f "1999-01-01 00:00" -t "1999-12-31 23:59" -w "" <events.txt + +will show only the +.Ar text +of all lines with a date in 1999. You can specify a different format for the +dates, for example + +.Dl sdep +"%m/%d/%Y %I:%M%p" -t "12/31/2020 11:59pm" -w "" <events.txt + +will match all dates from December 31st, 2020, one minute before midnight +(included). Note: this only works if your locale has an am/pm format, see +.Xr date 1 . + +.Sh AUTHORS +.An Sebastiano Tronto Aq Mt sebastiano.tronto@gmail.com + +.Sh SOURCE CODE +Source code is available at +.Lk https://github.com/sebastianotronto/sdep + +.Sh SEE ALSO +.Xr date 1 , +.Xr strftime 3 , +.Xr strptime 3 diff --git a/sdep-0.2/sdep.c b/sdep-0.2/sdep.c @@ -0,0 +1,277 @@ +/* See LICENSE file for copyright and license details. */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +/* + * Maximum number of characters in a line. The rest will be truncated. + * Change this if you need very long lines. + */ +#define MAXLEN 10000 + +/* + * Default date format. Anything that strftime(3) understands works, but + * it should determine a date completely up to the minute. + */ +static char *default_format = "%Y-%m-%d %H:%M"; + + +typedef struct Event Event; +typedef struct EventList EventList; +typedef struct EventNode EventNode; +typedef struct Options Options; + +struct Event{ + struct tm time; + char *text; +}; + +struct EventList { + EventNode *first; + EventNode *last; + int count; +}; + +struct EventNode { + Event ev; + EventNode *next; +}; + +struct Options { + struct tm from; + struct tm to; + char *format_in; + char *format_out; + char *separator; +}; + +static void add_event(struct tm, char *, EventList *); +static int compare_tm(const void *, const void *); +static int compare_event(const void *, const void *); +static Options default_op(void); +static int events_in_range(EventList *, Options, Event *); +static char *format_line(Event, Options, char *); +static int is_space(char); +static void read_input(Options, EventList *); +static Options read_op(int, char *[]); +static void str_copy(char *, char *, int); +static char *str_trim(char *); +static void write_output(Options, Event *, int); + + +static void +add_event(struct tm t, char *text, EventList *evlist) +{ + size_t l = strlen(text)+1; + EventNode *next = malloc(sizeof(EventNode)); + + next->ev.time = t; + next->ev.text = malloc(l); + str_copy(next->ev.text, text, l); + next->ev.text = str_trim(next->ev.text); + next->next = NULL; + + if (++evlist->count == 1) { + evlist->first = next; + evlist->last = next; + } else { + evlist->last->next = next; + evlist->last = next; + } +} + +static int +compare_tm(const void *v1, const void *v2) +{ + const struct tm *t1 = v1; + const struct tm *t2 = v2; + + if (t1->tm_year != t2->tm_year) + return t1->tm_year - t2->tm_year; + if (t1->tm_mon != t2->tm_mon) + return t1->tm_mon - t2->tm_mon; + if (t1->tm_mday != t2->tm_mday) + return t1->tm_mday - t2->tm_mday; + if (t1->tm_hour != t2->tm_hour) + return t1->tm_hour - t2->tm_hour; + return t1->tm_min - t2->tm_min; +} + +static int +compare_event(const void *v1, const void *v2) +{ + const Event *ev1 = v1; + const Event *ev2 = v2; + + return compare_tm(&ev1->time, &ev2->time); +} + +static Options +default_op(void) +{ + Options op; + time_t t_now = time(NULL); + struct tm *now = localtime(&t_now); + + op.format_in = malloc(MAXLEN); + op.format_out = malloc(MAXLEN); + op.separator = malloc(MAXLEN); + strcpy(op.format_in, default_format); + strcpy(op.format_out, default_format); + strcpy(op.separator, "\t"); + op.from = *now; + op.to = *now; + + return op; +} + +/* + * Saves the events in ev[] that happen between op->from and op->to in sel[] + * sorted by date and returns their number. + */ +static int +events_in_range(EventList *evlist, Options op, Event *sel) +{ + EventNode *i; + int n = 0; + + for (i = evlist->first; i != NULL; i = i->next) + if (compare_tm(&i->ev.time, &op.from) >= 0 && + compare_tm(&i->ev.time, &op.to) <= 0) + sel[n++] = i->ev; + + qsort(sel, n, sizeof(Event), compare_event); + + return n; +} + +static char * +format_line(Event ev, Options op, char *out) +{ + size_t l; + + strftime(out, MAXLEN, op.format_out, &ev.time); + l = strlen(out); + + str_copy(out+l, op.separator, MAXLEN - l); + l = strlen(out); + + str_copy(out+l, ev.text, MAXLEN - l); + + return out; +} + +static int +is_space(char c) +{ + return c == ' ' || c == '\t'; +} + +static void +read_input(Options op, EventList *evlist) +{ + struct tm t; + char line[MAXLEN], *text_ptr; + + while (fgets(line, MAXLEN, stdin) != NULL) + if ((text_ptr = strptime(line, op.format_in, &t)) != NULL) + add_event(t, text_ptr, evlist); +} + +static Options +read_op(int argc, char *argv[]) +{ + Options op = default_op(); + int i; + + /* Check for format specification. + * This changes the way other options are read */ + for (i = 1; i < argc; i++) { + if (argv[i][0] == '+') { + str_copy(op.format_in, &argv[i][1], MAXLEN); + str_copy(op.format_out, &argv[i][1], MAXLEN); + } + } + + for (i = 1; i < argc; i++) { + if (!strcmp(argv[i], "-v")) { + puts("sdep-"VERSION); + exit(0); + } else if (!strcmp(argv[i], "-d")) { + puts(default_format); + exit(0); + } else if (!strcmp(argv[i], "-s")) { + str_copy(op.separator, argv[++i], MAXLEN); + } else if (!strcmp(argv[i], "-f")) { + if (i+1 >= argc || + strptime(argv[i+1], op.format_in, &op.from) == NULL) + op.from.tm_year = -1000000000; /* Very large number */ + else + i++; + } else if (!strcmp(argv[i], "-t")) { + if (i+1 >= argc || + strptime(argv[i+1], op.format_in, &op.to) == NULL) + op.to.tm_year = 1000000000; /* Very small number */ + else + i++; + } else if (!strcmp(argv[i], "-w")) { + op.format_out = argv[++i]; + } else if (argv[i][0] != '+' && strlen(argv[i]) != 0) { + fprintf(stderr, "usage: sdep [-dv]"); + fprintf(stderr, " [+format] [-f [date]] [-t [date]]"); + fprintf(stderr, " [-w format] [-s string]\n"); + exit(1); + } + } + + return op; +} + +/* + * Copy up to n characters of the string str to dst and append '\0'. + * Suggested by NRK. + */ +static void +str_copy(char *dst, char *src, int n) +{ + if (memccpy(dst, src, '\0', n) == NULL) + dst[n-1] = '\0'; +} + +static char * +str_trim(char *t) +{ + char *s; + + for (s = &t[strlen(t)-1]; s != t && is_space(*s); *s = '\0', s--); + for (; *t != '\0' && is_space(*t); t++); + + return t; +} + +static void +write_output(Options op, Event *ev, int n) +{ + char outline[MAXLEN]; + int i; + + for (i = 0; i < n; i++) + printf("%s\n", format_line(ev[i], op, outline)); +} + +int +main(int argc, char *argv[]) +{ + Options op; + EventList evlist = {0}; + Event *selected; + + op = read_op(argc, argv); + read_input(op, &evlist); + selected = malloc(sizeof(Event) * evlist.count); + write_output(op, selected, events_in_range(&evlist, op, selected)); + + return 0; +}