summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Voss <thomas.voss@humanwave.nl> 2025-09-05 15:46:26 +0200
committerThomas Voss <thomas.voss@humanwave.nl> 2025-09-05 15:46:26 +0200
commitdf78b72fe58251bb31c8bc5ab34460e44220383d (patch)
treee2fa5436c73d9f11ccf6e5cdce3450e3472c2a1f
Genesis commit
-rw-r--r--.gitignore2
-rw-r--r--Makefile21
-rw-r--r--main.c184
-rw-r--r--po/messages.pot38
-rw-r--r--po/sv/LC_MESSAGES/messages.po40
5 files changed, 285 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..10af64e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+tiktok
+*.mo
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..e5e7cb4
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,21 @@
+CC = cc
+CFLAGS = -Wall -Wextra -Wpedantic -std=c23 \
+ -I$$(brew --prefix gettext)/include \
+ -L$$(brew --prefix gettext)/lib \
+ -lintl
+
+all: tiktok
+
+tiktok: main.c
+ $(CC) $(CFLAGS) -o $@ $<
+
+extract:
+ xgettext --from-code=UTF-8 -k_ -o po/messages.pot main.c
+ find po -name '*.po' -exec msgmerge {} po/messages.pot -o {} \;
+
+translations:
+ find po -name '*.po' | while read -r file; do msgfmt "$$file" -o "$${file%po}mo"; done
+
+clean:
+ rm tiktok
+ find po -name '*.mo' -delete
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..968a807
--- /dev/null
+++ b/main.c
@@ -0,0 +1,184 @@
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <getopt.h>
+#include <libgen.h>
+#include <locale.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <libintl.h>
+
+#define _(x) gettext(x)
+
+#define NSEC_PER_SEC UINT64_C(1'000'000'000)
+
+#if __APPLE__
+#define TIMER_ABSTIME (1)
+
+int clock_nanosleep(clockid_t, int, const struct timespec *, struct timespec *);
+#endif
+
+time_t syncs(time_t), syncm(time_t), synch(time_t);
+
+static void
+usage(const char *argv0, int code)
+{
+ fprintf(stderr, _("Usage: %s [-h] [-i interval] [format]\n"), argv0);
+ exit(code);
+}
+
+int
+main(int argc, char **argv)
+{
+ setlocale(LC_ALL, "");
+ bindtextdomain("messages", "./po");
+ textdomain("messages");
+
+ char interval = 's';
+ const char *argv0 = basename(argv[0]);
+ const char *dfmt = "%c", *optstr = "hi:";
+ static struct option longopts[] = {
+ {"help", no_argument, nullptr, 'h'},
+ {"interval", required_argument, nullptr, 'i'},
+ {nullptr, 0, nullptr, 0 },
+ };
+
+ for (;;) {
+ int opt = getopt_long(argc, argv, optstr, longopts, nullptr);
+ if (opt == -1)
+ break;
+
+ switch (opt) {
+ case 'h':
+ usage(argv0, EXIT_SUCCESS);
+ case 'i':
+ if (strlen(optarg) == 1) {
+ interval = *optarg;
+ if (interval == 's' || interval == 'm' || interval == 'h')
+ break;
+ }
+ errx(EXIT_FAILURE,
+ _("invalid interval ‘%s’\nRead the %s(1) manual page for valid intervals"),
+ optarg, argv0);
+ default:
+ usage(argv0, EXIT_FAILURE);
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc > 1)
+ usage(argv0, EXIT_FAILURE);
+ if (argc != 0)
+ dfmt = argv[0];
+
+ time_t (*sync)(time_t) =
+ interval == 's' ? syncs
+ : interval == 'm' ? syncm
+ : interval == 'h' ? synch
+ : /* default */ syncs;
+
+ for (;;) {
+ struct timespec before, after;
+ if (clock_gettime(CLOCK_REALTIME, &before) == -1)
+ warn(_("failed to get the time"));
+
+ time_t now = time(NULL);
+ struct tm *tm = localtime(&now);
+
+ char buf[256];
+ size_t n = strftime(buf, sizeof(buf), dfmt, tm);
+ if (n == 0)
+ warnx(_("buffer too small"));
+ puts(buf);
+
+ if (clock_gettime(CLOCK_REALTIME, &after) == -1)
+ warn(_("failed to get the time"));
+
+ /* Duration of the clock formatting and printing */
+ struct timespec Δ = {
+ after.tv_sec - before.tv_sec,
+ after.tv_nsec - before.tv_nsec,
+ };
+ if (Δ.tv_nsec < 0) {
+ Δ.tv_nsec += NSEC_PER_SEC;
+ Δ.tv_sec--;
+ }
+
+ struct timespec rqtp = {
+ .tv_sec = sync(after.tv_sec),
+ .tv_nsec = 0,
+ };
+
+ int rv;
+ do
+ rv = clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &rqtp, nullptr);
+ while (rv == -1 && errno == EINTR);
+ }
+
+ /* We should never get here */
+ return EXIT_FAILURE;
+}
+
+time_t
+syncs(time_t n)
+{
+ return n + 1;
+}
+
+time_t
+syncm(time_t n)
+{
+ return n + 60 - n%60;
+}
+
+time_t
+synch(time_t n)
+{
+ return n + 3600 - n%3600;
+}
+
+#if __APPLE__
+
+int
+clock_nanosleep(clockid_t clock_id, int flags, const struct timespec *req,
+ struct timespec *rem)
+{
+ /* The rest not implemented */
+ assert(flags == TIMER_ABSTIME);
+
+ struct timespec now, Δ;
+
+ if (clock_gettime(clock_id, &now) != 0)
+ return errno;
+
+ Δ.tv_sec = req->tv_sec - now.tv_sec;
+ Δ.tv_nsec = req->tv_nsec - now.tv_nsec;
+
+ if (Δ.tv_sec < 0) {
+ Δ.tv_sec = 0;
+ Δ.tv_nsec = 0;
+ goto out;
+ }
+
+ if (Δ.tv_nsec < 0) {
+ if (Δ.tv_sec == 0) {
+ Δ.tv_sec = 0;
+ Δ.tv_nsec = 0;
+ goto out;
+ }
+
+ Δ.tv_sec--;
+ Δ.tv_nsec += NSEC_PER_SEC;
+ }
+
+out:
+ return nanosleep(&Δ, rem);
+}
+
+#endif
diff --git a/po/messages.pot b/po/messages.pot
new file mode 100644
index 0000000..01929bf
--- /dev/null
+++ b/po/messages.pot
@@ -0,0 +1,38 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2025-09-05 15:43+0200\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: main.c:30
+#, c-format
+msgid "Usage: %s [-h] [-i interval] [format]\n"
+msgstr ""
+
+#: main.c:65
+#, c-format
+msgid ""
+"invalid interval ‘%s’\n"
+"Read the %s(1) manual page for valid intervals"
+msgstr ""
+
+#: main.c:89 main.c:101
+msgid "failed to get the time"
+msgstr ""
+
+#: main.c:97
+msgid "buffer too small"
+msgstr ""
diff --git a/po/sv/LC_MESSAGES/messages.po b/po/sv/LC_MESSAGES/messages.po
new file mode 100644
index 0000000..be09d24
--- /dev/null
+++ b/po/sv/LC_MESSAGES/messages.po
@@ -0,0 +1,40 @@
+# Swedish translations for PACKAGE package.
+# Copyright (C) 2025 THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# Thomas Voß <mail@thomasvoss.com>, 2025.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2025-09-05 15:43+0200\n"
+"PO-Revision-Date: 2025-09-05 15:18+0200\n"
+"Last-Translator: Thomas Voss <mail@thomasvoss.com>\n"
+"Language-Team: Swedish <tp-sv@listor.tp-sv.se>\n"
+"Language: sv\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: main.c:30
+#, c-format
+msgid "Usage: %s [-h] [-i interval] [format]\n"
+msgstr "Användning: %s [-h] [-i intervall] [format]\n"
+
+#: main.c:65
+#, c-format
+msgid ""
+"invalid interval ‘%s’\n"
+"Read the %s(1) manual page for valid intervals"
+msgstr ""
+"ogiltigt intervall ”%s”\n"
+"Läs %s(1)-manualsidan för giltiga intervall"
+
+#: main.c:89 main.c:101
+msgid "failed to get the time"
+msgstr "misslyckades med att få tiden"
+
+#: main.c:97
+msgid "buffer too small"
+msgstr "bufferten är för liten"