wdye: optionally produce asciicast v2 logs
I've been fairly disappointed with asciinema, but it's slightly better than nothing.
This commit is contained in:
parent
914e743dc4
commit
6c47e384f5
@ -126,6 +126,12 @@ Example
|
||||
rot13:send "Hello\r"
|
||||
expect(rot13:exact {"Uryyb\r"})
|
||||
|
||||
Environment
|
||||
-----------
|
||||
*WDYE_LOGGING*::
|
||||
When this environment variable is present, *wdye* produces asciicast v2
|
||||
files for every spawned program, in the current working directory.
|
||||
|
||||
Reporting bugs
|
||||
--------------
|
||||
Use https://git.janouch.name/p/liberty to report bugs, request features,
|
||||
|
@ -222,6 +222,45 @@ pty_fork (int *ptrfdm, char **slave_name,
|
||||
return pid;
|
||||
}
|
||||
|
||||
// --- JSON --------------------------------------------------------------------
|
||||
|
||||
static void
|
||||
write_json_string (FILE *output, const char *s, size_t len)
|
||||
{
|
||||
fputc ('"', output);
|
||||
for (const char *last = s, *end = s + len; s != end; last = s)
|
||||
{
|
||||
// Here is where you realize the asciicast format is retarded for using
|
||||
// JSON at all. (Consider multibyte characters at read() boundaries.)
|
||||
int32_t codepoint = utf8_decode (&s, end - s);
|
||||
if (codepoint < 0)
|
||||
{
|
||||
s++;
|
||||
fprintf (output, "\\uFFFD");
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (codepoint)
|
||||
{
|
||||
break; case '"': fprintf (output, "\\\"");
|
||||
break; case '\\': fprintf (output, "\\\\");
|
||||
break; case '\b': fprintf (output, "\\b");
|
||||
break; case '\f': fprintf (output, "\\f");
|
||||
break; case '\n': fprintf (output, "\\n");
|
||||
break; case '\r': fprintf (output, "\\r");
|
||||
break; case '\t': fprintf (output, "\\t");
|
||||
break; default:
|
||||
if (!utf8_validate_cp (codepoint))
|
||||
fprintf (output, "\\uFFFD");
|
||||
else if (codepoint < 32)
|
||||
fprintf (output, "\\u%04X", codepoint);
|
||||
else
|
||||
fwrite (last, 1, s - last, output);
|
||||
}
|
||||
}
|
||||
fputc ('"', output);
|
||||
}
|
||||
|
||||
// --- Global state ------------------------------------------------------------
|
||||
|
||||
static struct
|
||||
@ -435,6 +474,9 @@ struct process
|
||||
int ref_term; ///< Terminal information
|
||||
struct str buffer; ///< Terminal input buffer
|
||||
int status; ///< Process status iff pid is -1
|
||||
|
||||
int64_t start; ///< Start timestamp (Unix msec)
|
||||
FILE *asciicast; ///< asciicast script dump
|
||||
};
|
||||
|
||||
static struct process *
|
||||
@ -462,6 +504,8 @@ xlua_process_gc (lua_State *L)
|
||||
kill (-self->pid, SIGKILL);
|
||||
luaL_unref (L, LUA_REGISTRYINDEX, self->ref_term);
|
||||
str_free (&self->buffer);
|
||||
if (self->asciicast)
|
||||
fclose (self->asciicast);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -500,6 +544,14 @@ xlua_process_send (lua_State *L)
|
||||
return luaL_error (L, "write failed: %s", strerror (errno));
|
||||
else if (written != len)
|
||||
return luaL_error (L, "write failed: %s", "short write");
|
||||
|
||||
if (self->asciicast)
|
||||
{
|
||||
double timestamp = (clock_msec () - self->start) / 1000.;
|
||||
fprintf (self->asciicast, "[%f, \"i\", ", timestamp);
|
||||
write_json_string (self->asciicast, arg, len);
|
||||
fprintf (self->asciicast, "]\n");
|
||||
}
|
||||
}
|
||||
lua_pushvalue (L, 1);
|
||||
return 1;
|
||||
@ -667,6 +719,14 @@ process_feed (struct process *self)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (self->asciicast)
|
||||
{
|
||||
double timestamp = (clock_msec () - self->start) / 1000.;
|
||||
fprintf (self->asciicast, "[%f, \"o\", ", timestamp);
|
||||
write_json_string (self->asciicast, buf, n);
|
||||
fprintf (self->asciicast, "]\n");
|
||||
}
|
||||
|
||||
// TODO(p): Add match_max processing, limiting the buffer size.
|
||||
str_append_data (&self->buffer, buf, n);
|
||||
return n > 0;
|
||||
@ -888,9 +948,17 @@ spawn_protected (lua_State *L)
|
||||
}
|
||||
process->ref_term = luaL_ref (L, LUA_REGISTRYINDEX);
|
||||
|
||||
struct winsize ws = { .ws_row = 24, .ws_col = 80 };
|
||||
if ((entry = str_map_find (&ctx->term, "lines"))
|
||||
&& entry->kind == TERMINFO_NUMERIC)
|
||||
ws.ws_row = entry->numeric;
|
||||
if ((entry = str_map_find (&ctx->term, "columns"))
|
||||
&& entry->kind == TERMINFO_NUMERIC)
|
||||
ws.ws_col = entry->numeric;
|
||||
|
||||
// Step 5: Spawn the process, which gets a new process group.
|
||||
process->pid =
|
||||
pty_fork (&process->terminal_fd, NULL, NULL, NULL, &ctx->error);
|
||||
pty_fork (&process->terminal_fd, NULL, NULL, &ws, &ctx->error);
|
||||
if (process->pid < 0)
|
||||
{
|
||||
return luaL_error (L, "failed to spawn %s: %s",
|
||||
@ -905,6 +973,28 @@ spawn_protected (lua_State *L)
|
||||
_exit (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// Step 6: Create a log file.
|
||||
if (getenv ("WDYE_LOGGING"))
|
||||
{
|
||||
const char *name = ctx->argv.vector[0];
|
||||
const char *last_slash = strrchr (name, '/');
|
||||
if (last_slash)
|
||||
name = last_slash + 1;
|
||||
|
||||
char *path = xstrdup_printf ("%s-%s.%d.cast",
|
||||
PROGRAM_NAME, name, (int) process->pid);
|
||||
if (!(process->asciicast = fopen (path, "w")))
|
||||
print_warning ("%s: %s", path, strerror (errno));
|
||||
free (path);
|
||||
}
|
||||
process->start = clock_msec ();
|
||||
if (process->asciicast)
|
||||
{
|
||||
fprintf (process->asciicast, "{\"version\": 2, "
|
||||
"\"width\": %u, \"height\": %u, \"env\": {\"TERM\": \"%s\"}}\n",
|
||||
ws.ws_col, ws.ws_row, term);
|
||||
}
|
||||
|
||||
set_cloexec (process->terminal_fd);
|
||||
return 1;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user