shpool is a tool like tmux and screen to keep persistent shell sessions across hangups.
It’s quite new (first released 2023), and extremely simple to use.
Usually I only need one persistent shell in remote machine, and this tool, unlike tmux,
is designed to support only one shell per session.
That makes me very satisfied.
Currently there is no Bash auto-complete setup provided from the official repo.
Therefore I implemented one for myself. With that, I can make my life easier. 🤣
1 2
shpool <tab> # complete sub-commands like attach / list / help ... shpool attach <tab> # complete available sessions
Here is the auto-complete setup script for Bash, for anyone who have same requirement.
if [[ ${#COMP_WORDS[@]} -eq 2 ]]; then local commands=(attach detach kill list version help) local partial="${COMP_WORDS[$COMP_CWORD]}" mapfile -t COMPREPLY < <(compgen -W "${commands[*]}" -- "${partial}") return 0 fi
[[ ${#COMP_WORDS[@]} -ne 3 ]] && return 0
# Now, try completing session names for commands that take them local partial="${COMP_WORDS[$COMP_CWORD]}" localcommand="${COMP_WORDS[$COMP_CWORD-1]}"
case"${command}"in attach|detach|kill) local -a sessions mapfile -t sessions < <(shpool list | tail -n +2 | awk '{print $1}') mapfile -t COMPREPLY < <(compgen -W "${sessions[*]}" -- "${partial}") return 0 ;; *) # No need for other commands, they don't take arguments COMPREPLY=() return 0 ;; esac }
assert5 == 2 | Adder(3) # Is 5 equals to 2 + 3 ? Yes!!
This works because the | operator of integer 2 check the type of Adder(3) and found that
is not something it recognized, so it returns NotImplemented and our reverse magic method goes.
In C++, the | operator is overloaded(?) on range adaptors to accept ranges.
So maybe we can make something similar, having some object implements __ror__ that accept
an iterable and return another value (probably a iterator).
Pipe-able Higher Order Function
So back to our motivation, Python already have something like filtermapreduce,
and also the powerful generator expression to filter and/or map without explicit function call.
1
values = filter(lambda v: v % 2 == 0, range(10))
1
values = (v for v inrange(10) if v % 2 == 0)
But it’s just hard to chain multiple operations together while preserving readability.
So let’s make a filter object that support | operator
It just take some time for we to write the class representation for filter, map, reduce,
take, any … and any higher function you may think useful.
Wait, it looks so tedious. Python should be a powerful language, isn’t it?
Piper and Decorators
The function capturing and __ror__ implementation can be so annoying for all high order function.
If we can make sure __ror__ only take left operand, and return the return value of the captured
function, than we can extract a common Piper class. We just need another function to produce a
function that already capture the required logic.
Now it looks a little nicer … but we still need to implement all wrapper functions for all
kinds of operations?
Again, the only difference between these wrapped functions is the logic inside apply function,
so we can extract this part again, with a decorator!! :D
The on decorator accept some function func, and return a function that first take the
tail arguments of func and return a function that accept head argument of func through
pipe operator.
So now we can express our thoughts in our codebase using pipeline style code,
just with one helper class and one helper decorator! :D
1 2 3 4 5 6 7 8
values = range(10) result = ( values | filter(lambda val: val % 2 == 0) | map(str) | on(lambda chunks: "".join(chunks))() # create pipe-able object on the fly ) assert result == "02468"
or
1 2
for val inrange(10) | filter(lambda val: val % 2 == 0): print(val)
defon(func: Callable[Concatenate[_T, _P], _R]) -> Callable[_P, Piper[_T, _R]]: """ "on" decorates a func into pipe-style function. The result function first takes the arguments, excluding first, and returns an object that takes the first argument through "|" operator. """
Thanks to the symbol-lazy-loading ability in Unix environment,
we can do many interesting thing on functions from some shared library
when executing some executables.
All we need to do are
Implement a shared library that contains the functions we want to hijack.
Run the executable with our magic library inserted.
Make a Shared Library
If we want to replace some function with stub / fake implementation.
we can just implement a function with the same name and the same signature.
For example, if we want to fixed the clock during unit test …
1 2 3 4 5 6 7 8 9 10 11
// in hijack.c
#include<time.h>
// a "time" function which always return the timestamp of the epoch. time_ttime(time_t* arg) { if (arg) *arg = 0;
return0; }
If we want do observation about some function call, but still delegate the
call to the original function, we can implement a function that load
corresponding function at runtime and pass the function call.
For example, if we want to monitor the call sequence of file open action.