aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMitchell Riedstra <mitch@riedstra.dev>2023-02-04 22:02:09 -0500
committerMitchell Riedstra <mitch@riedstra.dev>2023-02-04 22:23:21 -0500
commite6bb137aa20133589079d1378ecef8f7f1e9a84f (patch)
tree3b662ec202d3032b13ac85142532519e79929da7
parentcdda89a3c76fef80f29951389df88ed23d052d52 (diff)
downloadpm-e6bb137aa20133589079d1378ecef8f7f1e9a84f.tar.gz
pm-e6bb137aa20133589079d1378ecef8f7f1e9a84f.tar.xz
Switch formatting from AWK to a C program.
Due mostly to poor performance, calling out to the shell for time formatting quickly degrades in performance with a large list, this solves that problem. Yes, `gawk` has strftime, no, I don't want to use gawk.
-rw-r--r--.gitignore2
-rw-r--r--Makefile11
-rwxr-xr-xpm22
-rw-r--r--pm-fmtdb.c277
-rw-r--r--readme.md8
-rw-r--r--strftime.c65
6 files changed, 293 insertions, 92 deletions
diff --git a/.gitignore b/.gitignore
index 50152f1..4cea3a5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,2 @@
ses.vim
-strftime
+/pm-fmtdb
diff --git a/Makefile b/Makefile
index 2da16ac..5aeb748 100644
--- a/Makefile
+++ b/Makefile
@@ -1,10 +1,13 @@
PREFIX ?= /usr/local
-all: strftime
+CC ?= cc
+CFLAGS ?= -D_DEFAULT_SOURCE -std=c99 -pedantic -Wall -O2 -s
-install: strftime
- install -m 755 strftime $(PREFIX)/bin/strftime
+all: pm-fmtdb
+
+install: pm-fmtdb
+ install -m 755 pm-fmtdb $(PREFIX)/bin/pm-fmtdb
install -m 755 pm $(PREFIX)/bin/pm
clean:
- rm -f strftime
+ rm -f pm-fmtdb
diff --git a/pm b/pm
index b102d41..aa142ea 100755
--- a/pm
+++ b/pm
@@ -21,20 +21,6 @@ PASTE_MANAGER_X_SELECTION="${PASTE_MANAGER_X_SELECTION:-clipboard}"
# the only option called elsewhere is `-p`
_menu="dmenu -l $PASTE_MANAGER_LINES -i $DMENU_FLAGS"
-# This awk script controls how the output is formatted, which, will probably
-# be quite unwieldly if you don't have a fuzzy finder patch in dmenu.
-#shellcheck disable=SC2016
-awkPrettyPrint='
-BEGIN{
- FS="::";
-}
-{
- # "strftime \"%H:%M:%S %m.%d.%y\" " $2 | getline created;
- "strftime \"%H:%M:%S %m.%d.%y\" " $3 | getline updated;
- printf("ID: %s Updated %s Name: %-60s Content: %s\n",
- $1, updated, $4, $5);
-}'
-
genid() {
{ dd if=/dev/urandom 2>/dev/null | tr -C -d 'a-zA-Z0-9'; echo; } \
| fold -w 8 | sed 1q
@@ -76,7 +62,7 @@ insert() {
}
sel() {
- awk "$awkPrettyPrint" < "$PASTE_MANAGER_FILE" \
+ pm-fmtdb -f "$PASTE_MANAGER_FILE" \
| $_menu -p "$@" \
| sed -re 's/^ID: ([^ ]*).*$/\1/'
}
@@ -134,7 +120,7 @@ typeName() {
rename() {
sel "Item to rename" > "$_fifo" &
read -r id < "$_fifo"
- item="$(grep "^$id" "$PASTE_MANAGER_FILE" | awk "$awkPrettyPrint" )"
+ item="$(grep "^$id" "$PASTE_MANAGER_FILE" | pm-fmtdb -f - )"
name="$(selectName "$id")"
$_menu -p "Currently: ( $name ) New Name" > "$_fifo" &
read -r name < "$_fifo"
@@ -154,7 +140,7 @@ rename() {
update() {
sel "Item to update" > "$_fifo" &
read -r id < "$_fifo"
- item="$(grep "^$id" "$PASTE_MANAGER_FILE" | awk "$awkPrettyPrint" )"
+ item="$(grep "^$id" "$PASTE_MANAGER_FILE" | pm-fmtdb -f - )"
output="$(selectContent "$id")"
$_menu -p "Currently: ( $output ) New Content" > "$_fifo" &
read -r content < "$_fifo"
@@ -174,7 +160,7 @@ update() {
delete() {
sel "Item to delete" > "$_fifo" &
read -r id < "$_fifo"
- item="$(grep "^$id" "$PASTE_MANAGER_FILE" | awk "$awkPrettyPrint" )"
+ item="$(grep "^$id" "$PASTE_MANAGER_FILE" | pm-fmtdb -f - )"
echo Item: "$item"'
yes
no' | $_menu -p "Are you sure?" > "$_fifo" &
diff --git a/pm-fmtdb.c b/pm-fmtdb.c
new file mode 100644
index 0000000..2683112
--- /dev/null
+++ b/pm-fmtdb.c
@@ -0,0 +1,277 @@
+/*
+ * Copyright (c) 2023 Mitchell Riedstra
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+ * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Small program for quickly formatting the database file, reads it in,
+ * formats the date and spits it out in reversed order.
+ * Format is easily adjusted below at compile time.
+ *
+ */
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#define TIME_FMT_CREATED "%H:%M:%S %m.%d.%y"
+#define TIME_FMT_UPDATED "%m.%d.%y"
+
+/* Format string for the dump output */
+#define OUTPUT_FMT "ID: %s Updated: %s Name: %-60s Content %s\n"
+/* See the Entry struct below for available fields */
+#define OUTPUT_FMT_ARGS cur->Id, updated, cur->name, cur->content
+
+/*
+ * Be careful changing this one, the `pm` shell script will need to be updated
+ * too.
+ */
+#define DB_DELIM "::"
+
+#define TIME_FMT_MAX 256
+#define ENTRY_MAX 1024
+
+struct Entry {
+ char *Id;
+ time_t created;
+ time_t updated;
+ char *name;
+ char *content;
+
+ struct Entry *next;
+ struct Entry *prev;
+};
+
+void
+help()
+{
+ puts("pm-fmtdb [-f <file>]");
+ exit(1);
+}
+
+void
+die(const char *msg)
+{
+ perror(msg);
+ exit(1);
+}
+
+void *
+ecalloc(size_t nmemb, size_t size)
+{
+ void *ret;
+ ret = calloc(nmemb, size);
+ if (!ret) {
+ die("calloc");
+ }
+ return ret;
+}
+
+int
+FmtTimestamp(char **dest, time_t t, const char *fmt, size_t bufsize)
+{
+ struct tm *tm;
+ char *out = ecalloc(bufsize, sizeof(char *));
+
+ tm = localtime(&t);
+
+ if (strftime(out, bufsize, fmt, tm) == 0) {
+ free(out);
+ return -1;
+ }
+
+ *dest = out;
+ return 0;
+}
+
+char **
+charArrAppend(char **arr, size_t curlen, char *s)
+{
+ arr = reallocarray(arr, curlen+1, sizeof(char **));
+ if (!arr) {
+ die("reallocarray");
+ }
+
+ arr[curlen] = s;
+ return arr;
+}
+
+
+char **
+split(char *buf, const char *delim) {
+ int outlen = 0;
+ int len = strlen(buf);
+ char **out = NULL;
+ int dl = strlen(delim);
+ int dpos = 0;
+
+ int s = 0;
+ for (int n = 0; n < len; n++) {
+
+ if (buf[n] == '\n') {
+ char *str = ecalloc(n-s+1, sizeof(char *));
+ strncpy(str, &buf[s], n-s);
+ out = charArrAppend(out, outlen++, str);
+ break;
+ }
+
+ if (dpos+1 == dl && buf[n] == delim[dpos]) {
+ char *str = ecalloc((n-s)+1, sizeof(char *));
+ strncpy(str, &buf[s], n-dl-s+1);
+ s = n+1;
+
+ out = charArrAppend(out, outlen++, str);
+
+ dpos = 0;
+ continue;
+ }
+
+ if (buf[n] == delim[dpos]) {
+ dpos++;
+ } else {
+ dpos = 0;
+ }
+
+ }
+ return charArrAppend(out, outlen++, NULL);
+}
+
+struct Entry *
+loadEntries(FILE *f) {
+ int ret;
+ size_t max = ENTRY_MAX;
+ char *buf = ecalloc(max, sizeof(char *));
+ char **fields;
+ struct Entry *e = ecalloc(1, sizeof(struct Entry));
+ struct Entry *eprev = NULL;
+ int n;
+
+ for (;;) {
+ ret = getdelim(&buf, &max, '\n', f);
+ if (ret == -1 && ret != EOF) {
+ die("read");
+ }
+
+ if (ret == EOF)
+ break;
+
+ fields = split(buf, DB_DELIM);
+ char **f = fields;
+ for (n = 0;*f;f++) {
+ switch(n) {
+ case 0:
+ e->Id = *f;
+ break;
+ case 1:
+ errno = 0;
+ e->created = strtol(*f, NULL, 10);
+ if (errno != 0)
+ perror("bad timestamp: strtol");
+ break;
+ case 2:
+ errno = 0;
+ e->updated = strtol(*f, NULL, 10);
+ if (errno != 0)
+ perror("bad timestamp: strtol");
+ break;
+ case 3:
+ e->name = *f;
+ break;
+ case 4:
+ e->content = *f;
+ }
+ n++;
+ }
+
+ eprev = e;
+ e = ecalloc(1, sizeof(struct Entry));
+ eprev->next = e;
+ e->prev = eprev;
+ }
+
+ free(buf); buf = NULL;
+
+ return eprev;
+}
+
+char *
+strAppend(char *a, char *b)
+{
+ int alen = strlen(a);
+ char *n = ecalloc(alen+strlen(b)+1, sizeof(char));
+ strcpy(n, a);
+ strcpy(n+alen, b);
+ return n;
+}
+
+void
+dumpDB(FILE *db)
+{
+ struct Entry *cur, *last;
+ last = loadEntries(db);
+ if (!last) {
+ puts("Failed to load entries");
+ exit(1);
+ }
+
+ for (cur = last; cur; cur = cur->prev) {
+ char *updated;
+ if (FmtTimestamp(&updated, cur->updated, TIME_FMT_UPDATED,
+ TIME_FMT_MAX) == -1) {
+ die("FmtTimestamp");
+ }
+ char *created;
+ if (FmtTimestamp(&created, cur->created, TIME_FMT_CREATED,
+ TIME_FMT_MAX) == -1) {
+ die("FmtTimestamp");
+ }
+
+ printf(OUTPUT_FMT, OUTPUT_FMT_ARGS);
+ free(updated); updated = NULL;
+ }
+}
+
+int
+main(int argc, char **argv) {
+ char *fn = getenv("PASTE_MANAGER_FILE");
+ char **a = argv+1;
+ FILE *f;
+
+ for (;*a;a++) {
+ if (strcmp(*a, "-f") == 0 && *(++a)) {
+ fn = *a;
+ } else {
+ if (*a)
+ printf("Unknown option: '%s'\n", *a);
+
+ help();
+ }
+ }
+
+ if (!fn) {
+ fn = strAppend(getenv("HOME"), "/.pastes");
+ }
+
+ if (*fn == '-') {
+ f = stdin;
+ } else {
+ f = fopen(fn, "r");
+ if (!f) {
+ die("open");
+ }
+ }
+
+ dumpDB(f);
+}
diff --git a/readme.md b/readme.md
index 6597f97..1ffbda7 100644
--- a/readme.md
+++ b/readme.md
@@ -8,8 +8,8 @@ This is *NOT* for passwords. Do *NOT* use it for them. See my own
[`dpw`](https://git.riedstra.dev/mitch/dpw/about/) or
[pass](https://www.passwordstore.org/) for a solution there.
-Date formatting is a bit of a pain, so there's a `strftime` command line utility
-that's bundled, yes `gawk` provides `strftime` but that's not on every system.
+Output is controlled via a small c program called `pm-fmtdb`, see the top
+of the file for adjusting the format.
Works best with `dmenu` fuzzy finder patch. Utilizes list mode by default.
@@ -53,8 +53,8 @@ leave it set to clipboard, the default.
Edit the source code for further tweaks. The one people may have the most
-interest in is the format sent to `dmenu`, that's controlled via the `awk` script
-in the `awkPrettyPrint` variable near the top.
+interest in is the format sent to `dmenu`, that's controlled via the small c
+program 'pm-fmtdb' near the top of the file.
## Bugs
diff --git a/strftime.c b/strftime.c
deleted file mode 100644
index 70d171b..0000000
--- a/strftime.c
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (c) 2023 Mitchell Riedstra
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
- * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
- * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
- * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
- * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
- * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
- * PERFORMANCE OF THIS SOFTWARE.
- *
- * A fairly basic interface to `strftime`, since dates are apparently difficult
- * to handle with regular command line utilites and the POSIX spec for `date`
- * is rather lacking.
- */
-#include <stdio.h>
-#include <stdlib.h>
-#include <time.h>
-#include <errno.h>
-
-
-void
-help()
-{
- puts("strftime <fmt> [timestamp]");
- exit(1);
-}
-
-int
-main(int argc, char **argv) {
- time_t t = time(NULL);
- char **a = argv+1;
- struct tm *tm;
- char *fmt;
- char out[256] = {0};
-
- if (!*a) {
- help();
- }
-
- fmt = *a;
- a++;
-
- if (*a) {
- errno = 0;
- t = strtol(*a, NULL, 10);
- if (errno != 0) {
- perror("strtol");
- exit(1);
- }
- }
-
- tm = localtime(&t);
-
- if (strftime(out, 256, fmt, tm) == 0) {
- perror("strftime");
- exit(1);
- }
-
- puts(out);
-}