From 51bd183f064d0c0ea5e0184d9f6b6b62e5c01e50 Mon Sep 17 00:00:00 2001 From: Robin Haberkorn Date: Sun, 3 Aug 2025 15:41:28 +0300 Subject: added --quiet, --stdin and --stdout for easier integration into UNIX pipelines * In principle --stdin and --stdout could have been done in pure TECO code using the <^T> command. Having built-in command-line arguments however has several advantages: * Significantly faster than reading byte-wise with ^T. * Performs EOL normalization unless specifying --8bit of course. * Significantly shortens command-lines. `sciteco -qio` and `sciteco -qi` can be real replacements for sed and awk. * You can even place SciTECO into the middle of a pipeline while editing interactively: foo | sciteco -qio --no-profile | bar Unfortunately, this will not currently work when munging the profile as command-line parameters are also transmitted via the unnamed buffer. This should be changed to use special Q-registers (FIXME). * --quiet can help to improve the test suite (TODO). Should probably be the default in TE_CHECK(). * --stdin and --stdout allow to simplify many SciTECO scripts, avoiding temporary files, especially for womenpage generation (TODO). * For processing potentially infinite streams, you will still have to read using ^T. --- src/interface.c | 7 +++++++ src/interface.h | 2 ++ src/main.c | 41 ++++++++++++++++++++++++++++++++++++- src/view.c | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/view.h | 2 ++ 5 files changed, 114 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/interface.c b/src/interface.c index c3103fd..cfc8279 100644 --- a/src/interface.c +++ b/src/interface.c @@ -36,6 +36,9 @@ //#define DEBUG +/** minimum level of messages to print to stdout/stderr */ +teco_msg_t teco_interface_msg_level = TECO_MSG_USER; + teco_view_t *teco_interface_current_view = NULL; TECO_DEFINE_UNDO_CALL(teco_interface_show_view, teco_view_t *); @@ -114,6 +117,10 @@ teco_interface_msg(teco_msg_t type, const gchar *fmt, ...) void teco_interface_stdio_msg(teco_msg_t type, const gchar *str, gsize len) { + /* "user"-level messages are always printed */ + if (type != TECO_MSG_USER && type < teco_interface_msg_level) + return; + switch (type) { case TECO_MSG_USER: fwrite(str, 1, len, stdout); diff --git a/src/interface.h b/src/interface.h index 02af8a2..c0c41bd 100644 --- a/src/interface.h +++ b/src/interface.h @@ -66,6 +66,8 @@ typedef enum { TECO_MSG_ERROR } teco_msg_t; +extern teco_msg_t teco_interface_msg_level; + /** @pure */ void teco_interface_msg_literal(teco_msg_t type, const gchar *str, gsize len); diff --git a/src/main.c b/src/main.c index 42e1520..2be81ea 100644 --- a/src/main.c +++ b/src/main.c @@ -42,6 +42,7 @@ #include "parser.h" #include "goto.h" #include "qreg.h" +#include "view.h" #include "ring.h" #include "undo.h" #include "error.h" @@ -109,6 +110,9 @@ teco_get_default_config_path(void) #endif static gboolean teco_show_version = FALSE; +static gboolean teco_quiet = FALSE; +static gboolean teco_stdin = FALSE; +static gboolean teco_stdout = FALSE; static gchar *teco_eval_macro = NULL; static gboolean teco_mung_file = FALSE; static gboolean teco_mung_profile = TRUE; @@ -122,6 +126,12 @@ teco_process_options(gchar ***argv) static const GOptionEntry option_entries[] = { {"version", 'v', 0, G_OPTION_ARG_NONE, &teco_show_version, "Show version"}, + {"quiet", 'q', 0, G_OPTION_ARG_NONE, &teco_quiet, + "Don't print any non-user-level messages to stdout"}, + {"stdin", 'i', 0, G_OPTION_ARG_NONE, &teco_stdin, + "Read stdin into the unnamed buffer"}, + {"stdout", 'o', 0, G_OPTION_ARG_NONE, &teco_stdout, + "Print current buffer to stdout before program termination"}, {"eval", 'e', 0, G_OPTION_ARG_STRING, &teco_eval_macro, "Evaluate macro", "macro"}, {"mung", 'm', 0, G_OPTION_ARG_NONE, &teco_mung_file, @@ -195,6 +205,10 @@ teco_process_options(gchar ***argv) exit(EXIT_SUCCESS); } + if (teco_quiet) + /* warnings and errors will still be printed to stderr */ + teco_interface_msg_level = TECO_MSG_WARNING; + if ((*argv)[0] && !g_strcmp0((*argv)[1], "-S")) { /* translate -S to --, this is always passed down */ (*argv)[1][1] = '-'; @@ -452,18 +466,40 @@ main(int argc, char **argv) goto cleanup; } + /* + * Load stdin into the unnamed buffer. + * This will also perform EOL normalization. + * This is not done automatically when isatty(0) == 0 + * since you might want to read from stdin manually (^T). + * Loading stdin also won't work if the stream is infinite. + * + * NOTE: The profile hasn't run yet, so it cannot guess the + * documents encoding. This should therefore be done by the profile + * for any preexisting unnamed buffer. + * + * FIXME: The unnamed buffer is also currently used for + * command-line parameters. + * Therefore you practically cannot pipe into SciTECO + * while using opener.tes. + */ + if (teco_stdin && !teco_view_load_from_stdin(teco_ring_current->view, TRUE, &error)) + goto cleanup; + /* * Add remaining arguments to unnamed buffer. * * FIXME: This is not really robust since filenames may contain linefeeds. * Also, the Unnamed Buffer should be kept empty for piping. - * Therefore, it would be best to store the arguments in Q-Regs, e.g. $0,$1,$2... + * Therefore, it would be best to store the arguments in Q-Regs, e.g. ^A0,^A1,^A2... */ for (gint i = 1; argv_utf8[i]; i++) { teco_interface_ssm(SCI_APPENDTEXT, strlen(argv_utf8[i]), (sptr_t)argv_utf8[i]); teco_interface_ssm(SCI_APPENDTEXT, 1, (sptr_t)"\n"); } + if (teco_interface_ssm(SCI_GETLENGTH, 0, 0) > 0) + teco_ring_dirtify(); + /* * Execute macro or mung file */ @@ -571,6 +607,9 @@ main(int argc, char **argv) goto cleanup; cleanup: + if (!error && teco_stdout) + teco_view_save_to_stdout(teco_ring_current->view, &error); + if (error != NULL) { teco_error_display_full(error); ret = EXIT_FAILURE; diff --git a/src/view.c b/src/view.c index 0fc1986..e44b386 100644 --- a/src/view.c +++ b/src/view.c @@ -350,6 +350,44 @@ teco_view_load_from_file(teco_view_t *ctx, const gchar *filename, return TRUE; } +/** + * Load stdin until EOF into view's document. + * + * @param ctx The view to load. + * @param clear Whether to completely replace document + * (leaving dot at the beginning of the document) or insert at dot + * (leaving dot at the end of the insertion). + * @param error A GError. + * @return FALSE in case of a GError. + * + * @memberof teco_view_t + */ +gboolean +teco_view_load_from_stdin(teco_view_t *ctx, gboolean clear, GError **error) +{ +#ifdef G_OS_WIN32 + g_autoptr(GIOChannel) channel = g_io_channel_win32_new_fd(0); +#else + g_autoptr(GIOChannel) channel = g_io_channel_unix_new(0); +#endif + g_assert(channel != NULL); + + /* + * The file loading algorithm does not need buffered + * streams, so disabling buffering should increase + * performance (slightly). + */ + g_io_channel_set_encoding(channel, NULL, NULL); + g_io_channel_set_buffered(channel, FALSE); + + if (!teco_view_load_from_channel(ctx, channel, clear, error)) { + g_prefix_error_literal(error, "Error reading stdin: "); + return FALSE; + } + + return TRUE; +} + #if 0 /* @@ -554,6 +592,31 @@ teco_view_save_to_file(teco_view_t *ctx, const gchar *filename, GError **error) return TRUE; } +/** @memberof teco_view_t */ +gboolean +teco_view_save_to_stdout(teco_view_t *ctx, GError **error) +{ +#ifdef G_OS_WIN32 + g_autoptr(GIOChannel) channel = g_io_channel_win32_new_fd(1); +#else + g_autoptr(GIOChannel) channel = g_io_channel_unix_new(1); +#endif + g_assert(channel != NULL); + + /* + * teco_view_save_to_channel() expects a buffered and blocking channel + */ + g_io_channel_set_encoding(channel, NULL, NULL); + g_io_channel_set_buffered(channel, TRUE); + + if (!teco_view_save_to_channel(ctx, channel, error)) { + g_prefix_error_literal(error, "Error writing to stdout: "); + return FALSE; + } + + return TRUE; +} + /** * Convert a glyph index to a byte offset as used by Scintilla. * diff --git a/src/view.h b/src/view.h index 4e4b85e..82a5c99 100644 --- a/src/view.h +++ b/src/view.h @@ -56,6 +56,7 @@ gboolean teco_view_load_from_channel(teco_view_t *ctx, GIOChannel *channel, gboolean clear, GError **error); gboolean teco_view_load_from_file(teco_view_t *ctx, const gchar *filename, gboolean clear, GError **error); +gboolean teco_view_load_from_stdin(teco_view_t *ctx, gboolean clear, GError **error); /** @memberof teco_view_t */ #define teco_view_load(CTX, FROM, CLEAR, ERROR) \ @@ -66,6 +67,7 @@ gboolean teco_view_load_from_file(teco_view_t *ctx, const gchar *filename, gboolean teco_view_save_to_channel(teco_view_t *ctx, GIOChannel *channel, GError **error); gboolean teco_view_save_to_file(teco_view_t *ctx, const gchar *filename, GError **error); +gboolean teco_view_save_to_stdout(teco_view_t *ctx, GError **error); /** @memberof teco_view_t */ #define teco_view_save(CTX, TO, ERROR) \ -- cgit v1.2.3