diff options
author | Robin Haberkorn <robin.haberkorn@googlemail.com> | 2025-08-03 15:41:28 +0300 |
---|---|---|
committer | Robin Haberkorn <robin.haberkorn@googlemail.com> | 2025-08-03 16:09:33 +0300 |
commit | 51bd183f064d0c0ea5e0184d9f6b6b62e5c01e50 (patch) | |
tree | 9820e9671db37fbf5657d1327ef93e3081f8a6ab | |
parent | 5a85721a0a1b592287cb67188c5f0c5b55b3e348 (diff) | |
download | sciteco-51bd183f064d0c0ea5e0184d9f6b6b62e5c01e50.tar.gz |
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.
-rw-r--r-- | README | 3 | ||||
-rw-r--r-- | doc/sciteco.1.in | 62 | ||||
-rw-r--r-- | src/interface.c | 7 | ||||
-rw-r--r-- | src/interface.h | 2 | ||||
-rw-r--r-- | src/main.c | 41 | ||||
-rw-r--r-- | src/view.c | 63 | ||||
-rw-r--r-- | src/view.h | 2 |
7 files changed, 171 insertions, 9 deletions
@@ -100,6 +100,7 @@ Features to be used interactively on system terminals, can be integrated into UNIX pipelines and can be extended with external command-line tools (see `EC` command). + It can easily replace tools like *sed* and *awk*. * Themeability and consistency: Color settings (or schemes) are applied consistenly across all supported platforms. Gtk+ builds allow further customization using CSS. The user interface is kept minimalistic and is consistent in spirit across the @@ -152,7 +153,7 @@ Community if you can (or write an E-Mail to the author). * You can also use [Github Discussions](https://github.com/rhaberkorn/sciteco/discussions) for asking questions. -* We are also present in the [alt.lang.teco](https://newsgrouper.org.uk/alt.lang.teco) Usenet group, +* We are also present in the [alt.lang.teco](https://newsgrouper.org/alt.lang.teco) Usenet group, but it is not restricted to SciTECO. Additional Documentation diff --git a/doc/sciteco.1.in b/doc/sciteco.1.in index 55fc185..0eec35f 100644 --- a/doc/sciteco.1.in +++ b/doc/sciteco.1.in @@ -17,6 +17,9 @@ Scintilla-based \fBT\fPext \fBE\fPditor and \fBCO\fPrrector .SY @PACKAGE@ .OP "-h|--help" .OP "-v|--version" +.OP "-q|--quiet" +.OP "-i|--stdin" +.OP "-o|--stdout" .OP "-e|--eval" macro .OP "-m|--mung" .OP "--no-profile" @@ -75,17 +78,21 @@ problems because \*(ST might try to interpret these options. argument (which will always be the munged file name in a script invocation). . .LP -.SCITECO_TOPIC argv arguments Upon startup \*(ST's buffer ring contains only one unnamed empty buffer. -All command line arguments after the \*(ST options are passed as +First \*(ST reads from \fIstdin\fP into the unnamed buffer if \(lq--stdin\(rq +was given. +\# FIXME: This must change +.SCITECO_TOPIC argv arguments +Afterwards all command line arguments after the \*(ST options are passed as .I arguments -to the munged macro by placing each argument on its own line in -the buffer. +to the munged macro by appending each argument on its own line to +the unnamed buffer. The \fIscript\fP file name expected when \(lq--mung\(rq is given is currently \fBnot\fP considered a macro argument. In any case the current buffer position (called .IR dot ) is left at the beginning of the buffer. +The buffer is left \fIdirty\fP if anything was added to it. Optionally \(lq\-\-\(rq might be used to explicitly separate \*(ST options and macro arguments, but is never passed down as a macro argument. Since it's sometimes useful to pass down \(rq\-\-\(rq to the profile macro, @@ -120,7 +127,7 @@ to these streams while in interactive mode, or messages are continued to be written to these streams (in addition to being displayed in the GUI). .IP \(bu Messages logged to \fIstdout\fP or \fIstderr\fP \(em except -for messages written explicitly via some \*(ST command \(em +for \(lquser\(rq-level messages (written explicitly via some \*(ST command) \(em are prefixed with a string signifying the message's severity. In interactive mode, messages are also shown in a GUI-dependant manner. @@ -188,6 +195,25 @@ Display a short help text on the console. .SCITECO_TOPIC "-v" "--version" Display the \*(ST version in an easy to parse way. See also the \fBEO\fP command. +.IP "\fB-q\fR, \fB--quiet\fR" +.SCITECO_TOPIC "-q" "--quiet" +Do not print any messages to \fIstdout\fP except for \(lquser\(rq-level +messages, i.e. messages printed via \fBT\fP, \fB^T\fP and related +commands. +This is useful when piping the output of \*(ST into other programs. +.IP "\fB-i\fR, \fB--stdin\fR" +.SCITECO_TOPIC "-i" "--stdin" +Reads from \fIstdin\fP into the unnamed buffer before executing any macro. +Allows to easily integrate \*(ST into an UNIX pipeline, at least +if you don't need to process infinite streams. +This normalizes end-of-line (EOL) characters just like when opening +any other file. +.IP "\fB-o\fR, \fB--stdout\fR" +.SCITECO_TOPIC "-o" "--stdout" +\# FIXME: Perhaps this should imply --quiet? +Prints the current document's contents to \fIstdout\fP immediately before +terminating the program, restoring the original EOL characters. +This is also useful when piping the output of \*(ST into other programs. .IP "\fB-e\fR, \fB--eval\fR \fImacro" .SCITECO_TOPIC "-e" "--eval" Evaluate (execute) @@ -506,7 +532,30 @@ In order to execute a stand-alone script or custom profile macro: .EE .RE . -.ig \" FIXME: Requires --quiet to be an useful example +.LP +The easiest way to integrate \*(ST into an UNIX pipeline is +by using the \(lq--quiet --stdin --stdout\(rq or \(lq-qio\(rq +parameters. +For instance in order to prefix all lines with a line number: +.RS +.SCITECO_TT +.EX +dmesg | @PACKAGE@ -qioe '<%a\\@I/ / :L;>' +.SCITECO_TT_END +.EE +.RE +. +.LP +Suppose you would want to print only those lines from \fIstdin\fP +matching \(lqiwm0:\(rq: +.RS +.SCITECO_TT +.EX +dmesg | @PACKAGE@ -qie '<@S/iwm0:/; :L; -T>' +.SCITECO_TT_END +.EE +.RE +. .LP In order to query the installation path of the standard library, which is useful for authors of third-party macro packages: @@ -517,7 +566,6 @@ which is useful for authors of third-party macro packages: .SCITECO_TT_END .EE .RE -.. . .SH SEE ALSO . 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); @@ -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] = '-'; @@ -453,17 +467,39 @@ main(int argc, char **argv) } /* + * 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; @@ -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. * @@ -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) \ |