trun

Script for parsing any output. Yes, it is all it does.
git clone git://gtms.dev/trun.git
Log | Files | Refs | README | LICENSE

commit 295932c4d57612863de2c3943d1856247ccfa1d0
parent b19e7c94c42e1faa6a9fd3381e26accc41735927
Author: Tomas Nemec <nemi@skaut.cz>
Date:   Mon,  9 Aug 2021 09:01:43 +0200

feat: make it public #2

Diffstat:
MREADME | 150+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Aexamples/handler_lastline.lua | 18++++++++++++++++++
Mexamples/handler_simple.lua | 38+++++++++++---------------------------
Mexamples/handler_temp_output_file.lua | 38++++++++++++++++----------------------
Ahandlers/angulardart.lua | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ahelper_functions.lua | 33+++++++++++++++++++++++++++++++++
Atest/gen.lua | 24++++++++++++++++++++++++
Atest/handler.lua | 44++++++++++++++++++++++++++++++++++++++++++++
Atest/run.zsh | 7+++++++
Mtools/print_status.lua | 38++++++++++----------------------------
Mtrun.lua | 2+-
11 files changed, 340 insertions(+), 138 deletions(-)

diff --git a/README b/README @@ -1,91 +1,121 @@ -# Track run -TrackRun parse stdin of command and cache status to file. -Status is taken from handler module via `handle` function. +# Track run [v1.1.0 09.08.2021] + TrackRun listen on stdin and for each line it gets status which is written to + status_file. + Status is taken from handler module via `handle` function. + + ! Lots of thing are explained inside scripts so check them out. -! Lots of thing are explained inside scripts so check them out. # Dependencies -It is written in lua and used/tested on linux. I have no plan to make it work -for windows. But besides that nothing else is really needed. -- If you want to trun to better handle process signals you will want to install - luaposix via loarocks (luarocks install luaposix). So trun can cleanup files on sig{term,kill,int,hup} + It is written in lua and used/tested on linux. I have no plan to make it work + for windows. But besides that nothing else is really needed. + - If you want to `run` to better handle process signals you will want to + install luaposix via loarocks (luarocks install luaposix). So `trun` can + cleanup files on sig{term,kill,int,hup} + -And for some specific task you would need other tools: -- For sending command to neovim instances you will need neovim-remote - (https://github.com/mhinz/neovim-remote). +# Project structure + Inside project you will find number of different files. + - helper_functions.lua - useful functions for handler usage. (send command + to neovim instances and more) + - examples/ - handler examples + - handlers/ - real life handlers i use. (contact me to add more) + - TODO + - tools/ - other tools that uses `trun` to make life easier + - print_status.lua: i use it for show semaphor of cmd (green,orange,red) + - trun_to_neovim_quickfix.lua: plugin to fill qf-list from cmd -# Folders -Inside project you will find number of different files. -- examples/ - handler examples -- handlers/ - real life handlers i use. (contact me to add more) - - TODO -- tools/ - other tools that uses trun to make life easier - - print_status.lua: i use it for show semaphor of cmd (green,orange,red) - - trun_to_neovim_quickfix.lua: plugin to fill qf-list from cmd # Install -You can install trun with: -make install + You can install `trun` with: + make install + + Also uninstall via `make uninstall`. + But you can install it manually. Just take a look at Makefile and see what it + does... It is straightforward. -Also uninstall via `make uninstall`. But take a look at Makefile and see what -it does... It is straightforward. + Code inside other files are made for you to copy and modify as you need. # Usage -<cmd> 1> >(trun <handler> <name>) + <cmd> >&1 > >(trun <handler> <name>) + NOTE: this cmd is for zsh. -_explanation_ -`1>` - copy output of cmd to stdout -`>(...)` - copy output of cmd to stdin of subprocess -So if you don't need output in terminal you can simply `|` (pipe) to trun + _explanation_ + `>&1` - copy output of cmd to terminal + `> >(...)` - copy output of cmd to stdin of subprocess + So if you don't need output in terminal you can simply `|`(pipe) to `trun` # Handler -Handler is module that implements specific functions to catch some key -moment of tracking. Every handler must implement 'handle' function that -returns status for that line. It takes two arguments: -1. `userdata` is table for storing data between hooks -2. `line` is current line to get status from. + Handler is module that implements specific functions to catch some key + moment of tracking: + There are these hook functions you can implement: + - handle(userdata, line): returns status for current line + - (optional) on_start(userdata): before first line is passed to handle function + - (optional) on_update(userdata, line, status): when status change + - (optional) on_end(userdata): when script end -Returned status can be anything you want to store. Everything is just written -to status_file - -Handlers must have `lua` ext. and be inside specific folder. viz->#Config + Every handler must implement 'handle' function, others are optional. + Handlers must have `lua` ext. and be inside specific folder. viz->#Config ## Handle function -handle function can return either table or string. When table is returned, -every item will end up on new line in status_file so it can be easily used -by other scripts. - -## Hooks -There are these hook functions you can implement: -- on_start(userdata): before first line is passed to handle function -- on_update(userdata, line, status): when status change -- on_end(userdata): when script end + Returned status can be anything you want to store. Everything is just written + to status_file. Handle function can return either table or string. When table + is returned, every item inside table will end up on new line in status_file so + it can be easily used by other scripts. ## Userdata -userdata has pre filled `name` key with value of trun name + userdata has pre filled `name` key with value of `trun` name + # Config -There are 2 ENV variables you may want to set: -- TRUN_HANDLERS_DIR: directory where handlers are stored (default to $XDG_CONFIG_HOME/trun) -- TRUN_STATUS_DIR: directory to store files with statuses (default to $XDG_CACHE_HOME/trun) + There are 2 ENV variables you may want to set: + - TRUN_HANDLERS_DIR: directory where handlers are stored + (defaults to $XDG_CONFIG_HOME/trun) + - TRUN_STATUS_DIR: directory to store files with statuses + (defaults to $XDG_CACHE_HOME/trun) + + +# Usage steps +1. create handler + inside $TRUN_HANDLERS_DIR add `your_handler.lua` file. + viz -> examples + +2. Run cmd and pipe output to trun like `<cmd> | trun your_handler my_app`. + + For output also in terminal: `<cmd> >&1 > >(trun your_handler my_app)` + NOTE: this is for zsh! + +3. status file + Inside $TRUN_STATUS_DIR is created `my_app.your_handler` file with current + status in it. + + +# Test + Inside test/ folder is playground. You can edit handler, modify gen.lua for + your specific output and run `run.zsh` to see behaviour. + +# FAQ + + 1. What is different between storing status in `status_file` or in `userdata` + + Data inside status_file you can use outside of handler + (viz->tools/print_status.lua). And `userdata` is passed to each hook + function. + + 2. I want to contribute. + Sure thing! Send me an email with suggestion on feature or better with git + patch and we can discuss. viz->#Contact -# Example -1. angular handler -inside $TRUN_HANDLERS_DIR/angular.lua you have module for angular serving -output. Inside it you have simple handling just for status if build is -running, succeeded or has error. -viz -> examples/handler_simple.lua for way to run neovim command + 3. Why your English so bad? -2. You can run angular serving cmd and pipe output to trun like `<cmd> | trun angular my_app`. + Sorry for that, i am trying my best. -3. angular status file -Inside $TRUN_STATUS_DIR is created `my_app.angular` with current status in it. # Contact - Bugs, Suggestions, Questions... -Feel free to contact me via mail on: <nemi@skaut.cz> + Feel free to contact me via mail on: <nemi@skaut.cz> diff --git a/examples/handler_lastline.lua b/examples/handler_lastline.lua @@ -0,0 +1,18 @@ +-- Trun handler to store last line of build. +-- +-- interface +-- - handle(userdata, line): return status +-- - (optional) on_start(userdata): call once before start reading +-- - (optional) on_update(userdata, line, status): calls every time when status changed +-- - (optional) on_end(userdata): calls once after reading end +-- +return { + -- handle(userdata, line): return status + handle = function(_, line) return line end, + + -- on_update(userdata, line, status): calls every time when status changed + on_update = function(_, _, status) + -- status contain last line of command output + -- For example we can parse progress if cmd is providing it. + end, +} diff --git a/examples/handler_simple.lua b/examples/handler_simple.lua @@ -1,4 +1,4 @@ ---- Trun simple handler for just status of command +-- Trun simple handler -- -- interface -- - handle(userdata, line): return status @@ -6,40 +6,24 @@ -- - (optional) on_update(userdata, line, status): calls every time when status changed -- - (optional) on_end(userdata): calls once after reading end -- -local status_map = {['running'] = 0, ['success'] = 1, ['error'] = -1} - --- send nvim_cmd to all neovim instances -local nvim_cmd = function(nvim_cmd) - local servers = io.popen('nvr --serverlist') - for socket in servers:lines() do - local cmd = string.format('nvr --servername "%s" -cc "%s" --nostart -s &', socket, nvim_cmd) - print(cmd) - os.execute(cmd) - end - servers:close() -end - return { -- handle(userdata, line): return status handle = function(_, line) - if line:find('SUCCESS') then - return status_map.success - elseif line:find('RUNNING') then - return status_map.running + if line:find('success') then + return 1 + elseif line:find('running') then + return 0 else - return status_map.error + return -1 end end, -- on_update(userdata, line, status): calls every time when status changed - on_update = function(data, _, status) - - -- you can send linux notifications - os.execute(string.format('notify-send "trun status changed for %s" "%s"', data.name, status)) + on_update = function(_, _, status) + if status == 1 or status == -1 then + os.execute('notify-send "build finished"') + end - -- or you can run neovim command to trigger neotification. - -- You need neovim-remote (https://github.com/mhinz/neovim-remote) to make it work - nvim_cmd(string.format('lua require(\'notify\')(\'%s\', \'%s\')', data.name, 'error')) - -- make sure to use single quotes so vim can properly handle command. + -- viz->examples/handler_functions.lua for examples functions to use. end, } diff --git a/examples/handler_temp_output_file.lua b/examples/handler_temp_output_file.lua @@ -1,27 +1,22 @@ --- Trun advanced handler for *track status* and *cache* last output betwen status --- changes in tmp_file. It saves path to that file in status_file on second line. +-- Trun advanced handler _caches_ output betwen status changes to tmp_file. -- --- So for exmple, on error you can load this output to quickfix list --- inside vim. --- viz->tools/trun_to_neovim_quickfix.lua and examples/handler_simple.lua for --- examples of neovim-remote (to notify inside neovim or you can run Trunqf to --- load quickfix list on fly) +-- So for exmple, on error you fill quickfix list inside vim. +-- +-- viz->tools/trun_to_neovim_quickfix.lua and helper_functions.lua +-- for examples to send command to neovim instances. -- -- interface -- - handle(userdata, line): return status --- - (optional) on_start(userdata): call once before start reading --- - (optional) on_update(userdata, line, status): calls every time when status changed --- - (optional) on_end(userdata): calls once after reading end +-- - (optional) on_start(userdata): before start reading +-- - (optional) on_update(userdata, line, status): when status changed +-- - (optional) on_end(userdata): after reading end -- local status_map = {['running'] = 0, ['success'] = 1, ['error'] = -1} -local notify = - function(status) os.execute('notify-send "trun status changed" "' .. status .. '"') end - local function get_status(line) - if line:find('SUCCESS') then + if line:find('success') then return status_map.success - elseif line:find('RUNNING') then + elseif line:find('running') then return status_map.running else return status_map.error @@ -30,9 +25,9 @@ end return { - -- on_start(userdata): call once before start reading + -- on_start(userdata): before start reading on_start = function(data) - -- re-create tmp_file and save it to data table. + -- re-create tmp_file and save it to userdata table. data.tmpfile = '/tmp/' .. data.name .. '.trun' os.execute('rm ' .. data.tmpfile .. ' 2>/dev/null') end, @@ -44,22 +39,21 @@ return { tmpfile:write(line, '\n') tmpfile:close() return {get_status(line), data.tmpfile} + -- if you send table, each item is added on new line to tmpfile end, - -- on_update(userdata, line, status): calls every time when status changed + -- on_update(userdata, line, status): time when status changed on_update = function(_, status, data) -- on success, clear tmp_file. + -- Here status is the same table as it was given inside `handle` function. if status[1] == status_map.success then io.open(data.tmpfile, 'w'):close() end - notify() - -- viz->examples/handler_simple.lua for other examples wit neovim-remote end, - -- on_end(userdata): calls once after reading end + -- on_end(userdata): after reading end on_end = function(data) - notify() -- remove tmp_file os.execute('rm ' .. data.tmpfile) end, diff --git a/handlers/angulardart.lua b/handlers/angulardart.lua @@ -0,0 +1,86 @@ +--- Trun module handler for `pub run build_runner serve` +-- NOTE: This does not work for `webdev server` +-- +-- Status number i use for semaphor in WM status bar. +-- viz->tools/print_status.lua +-- +-- Last output is inside tmpfile so i can load errors to quickfix list +-- inside vim. +-- viz->examples/handler_temp_output_file.lua -> for caching last output +-- viz->tools/trun_to_neovim_quickfix.lua -> for load qf-list +-- +-- +local s_map = {['running'] = 0, ['success'] = 1, ['severe'] = -1} + +local notify = function() + -- Notify system, neovim, etc... +end + +-- Output types: +-- [SEVERE] Failed after 11.4s +-- [INFO] Succeeded after 9.8s with 304 outputs (8 actions) +-- +-- Sometimes build is not ending with these lines. There may be couple of empty +-- lines after. + +local succeeded = function(line) + if line:match('%[INFO%] Succeeded') then + return true + elseif line:match('Serving') then + return true + end +end + +local last_status +local get_status = function(line) + if last_status and #line == 0 then + return last_status + end + if (succeeded(line)) then + return s_map.success + end + + local line_type = line:match('^%[(.*)%] .*'); + if not line_type then + return s_map.running + end + + if (line_type == 'SEVERE') then + return s_map.severe + end + + return s_map.running +end + +return { + + -- on_start(userdata): before start reading + on_start = function(data) + data.tmpfile = '/tmp/' .. data.name .. '.trun' + os.execute('rm -f ' .. data.tmpfile .. ' 2>/dev/null') + end, + + -- handle(userdata, line): return status + handle = function(data, line) + local tmpfile = io.open(data.tmpfile, 'a') + tmpfile:write(line, '\n') + tmpfile:close() + local status = get_status(line) + last_status = status + return {status, data.tmpfile} + end, + + -- on_update(userdata, line, status): when status changed + on_update = function(data, _, status) + if status[1] == s_map.success then + io.open(data.tmpfile, 'w'):close() + end + notify() + end, + + -- on_end(userdata): after reading end + on_end = function(data) + notify() + os.execute('rm -f ' .. data.tmpfile) + end, +} diff --git a/helper_functions.lua b/helper_functions.lua @@ -0,0 +1,33 @@ +#!/usr/bin/env lua + +-- This is list of functions you can use inside handler +-- +-- Run any os command (add `&` for non blocking execution) +os.execute('... &') + +-- Send command to all neovim instances +local nvim_cmd = function(nvim_cmd) + local servers = io.popen('nvr --serverlist') + for socket in servers:lines() do + local cmd = string.format('nvr --servername "%s" -cc "%s" --nostart -s &', + socket, nvim_cmd) + os.execute(cmd) + end + servers:close() +end +-- TODO: send command to only specific neovim instances. +-- NOTE: to send command to only specific neovim instance, you can create +-- custom function inside neovim that will determine this. So for example if +-- you are inside right directory. `vim.fn.getcwd()` + +-- Send notification (in this case: https://github.com/rcarriga/nvim-notify) to +-- neovim instances. Use with `nvim_cmd`. +local nvim_notify = function(msg, title, level) + local cmd = string.format( + 'lua require(\'notify\')(\'%s\', \'%s\', {title=\'%s\'})', msg, + level, title) + nvim_cmd(cmd) +end + +-- TODO: Add more... + diff --git a/test/gen.lua b/test/gen.lua @@ -0,0 +1,24 @@ +#!/usr/bin/env lua +-- simple script that output lines every second. Run run.zsh script to test +-- handler.lua and watch status file being updated +-- +local sleep = function(s) os.execute('sleep ' .. s) end + +local success = 'success' +local normal = 'normal' +local error = 'error' + +local out +local leaps = 10 +while leaps > 0 do + if (leaps == 5) then + out = error + elseif (leaps == 1) then + out = success + else + out = normal + end + print(out) + sleep(1) + leaps = leaps - 1 +end diff --git a/test/handler.lua b/test/handler.lua @@ -0,0 +1,44 @@ +--- Trun test handler +-- +-- interface +-- - handle(userdata, line): return status +-- - (optional) on_start(userdata): call once before start reading +-- - (optional) on_update(userdata, line, status): calls every time when status changed +-- - (optional) on_end(userdata): calls once after reading end +-- +local status_map = {['running'] = 0, ['success'] = 1, ['error'] = -1} + +-- send nvim_cmd to all neovim instances +local nvim_cmd = function(nvim_cmd) + local servers = io.popen('nvr --serverlist') + for socket in servers:lines() do + local cmd = string.format('nvr --servername "%s" -cc "%s" --nostart -s &', + socket, nvim_cmd) + os.execute(cmd) + end + servers:close() +end + +return { + -- handle(userdata, line): return status + handle = function(_, line) + if line:find('success') then + return status_map.success + elseif line:find('normal') then + return status_map.running + else + return status_map.error + end + end, + + -- on_update(userdata, line, status): calls every time when status changed + on_update = function(data, _, status) + if status == status_map.success or status == status_map.error then + local type_map = {[1] = 'info', [-1] = 'error'} + local cmd = string.format( + 'lua require(\'notify\')(\'%s\', \'%s\', {title=\'%s\'})', + data.name, type_map[status], 'Build') + nvim_cmd(cmd) + end + end, +} diff --git a/test/run.zsh b/test/run.zsh @@ -0,0 +1,7 @@ +#!/usr/bin/env zsh + +# Set handler dir and status dir to this test dir. +env \ + TRUN_HANDLERS_DIR=@/ \ + TRUN_STATUS_DIR=@/ \ + ./gen.lua >&1 > >(../trun.lua handler test) diff --git a/tools/print_status.lua b/tools/print_status.lua @@ -1,6 +1,6 @@ #!/usr/bin/env lua --- Usage: trun_status.lua [-] [<name>] +-- Usage: trun_status.lua [<name>] -- -- Return formatted string for all truns to use inside status bar of your WM. -- It depends on status codes to be: @@ -12,31 +12,21 @@ -- -- example output: [%{F#ff0000}NAME%{F-} %{F#00ff00}OTHERNAME%{F-}] -- --- To ignore formatting add `-`(single dash) argument. Mostly for debugging. --- - It will just print output of status_file --- -- To get only single trun, add `name` as argument. -- --- It does not print any errors. (TODO) --- -- Config -local trun_status = os.getenv('TRUN_STATUS_DIR') or os.getenv('XDG_CONFIG_HOME') .. '/trun' +local status_dir_def = os.getenv('XDG_CONFIG_HOME') .. '/trun' +local status_dir = os.getenv('TRUN_STATUS_DIR') or status_dir_def -- map status to foreground colors. local status_map = {[0] = 'ffa500', [1] = '00ff00', [-1] = 'ff0000'} --- -- silent fail if dir not exists -local status_dir = trun_status if not io.open(status_dir) then return '' end local trun_name = arg[1] -local raw -- single dash argument means - no formatting, just print -if arg[1] == '-' then - raw = true - trun_name = arg[2] -end -- get all status files local status_files = {} @@ -51,22 +41,18 @@ local format = function(file, name) if not file then table.insert(output, '') else - if raw then - local status = file:read('*a') - output = status - else - local status = file:read('*n') - -- Edit this to your liking output - output = '%{F#' .. status_map[tonumber(status)] .. '} ' .. name:upper() .. ' %{F-}' - file:close() - end + local status = file:read('*n') -- 'n' means read a number + local color = status_map[tonumber(status)] + -- Edit this to your liking + output = string.format('%%{F#%s} %s %%{F-}', color, name:upper()) + file:close() end return output end local result = {} -- For every file fill out results -for _, status_file_name in pairs(status_files) do +for _, status_file_name in ipairs(status_files) do local name, _ = status_file_name:match('(.*)%.(.*)') if name then local status_file_path = status_dir .. '/' .. status_file_name @@ -83,9 +69,5 @@ end -- print out results if #result > 0 then - if raw then - print(table.concat(result, '\n')) - else - print('[' .. table.concat(result, ',') .. ']') - end + print('[' .. table.concat(result, ',') .. ']') end diff --git a/trun.lua b/trun.lua @@ -1,6 +1,6 @@ #!/usr/bin/env lua --- Version: 1.0.0 ( 08.08.2021 ) +-- Version: 1.1.0 ( 09.08.2021 ) -- -- Usage -- <cmd> 1> >(trun <handler> <name>)