This is an example implementation of a Pokemon plugin for Nushell in Python. I wanted to create this to clearly lay out how someone should provide named arguments.
See the asciinema example too:
You can optionally build the container:
$ docker build -t vanessa/nu-plugin-pokemon .
If you don't, it's available on Docker Hub and will be pulled automatically.
Run the container to access the nu entrypoint!
$ docker run -it vanessa/nu-plugin-pokemon
Once inside you can ask for help for the plugin:
/code> help pokemon
Catch an asciinema pokemon on demand.
--avatar AVATAR generate a pokemon avatar for some unique id.
--pokemon POKEMON generate ascii for a particular pokemon (by name)
--catch catch a random pokemon!
--list list pokemon available
--list-sorted list pokemon available (sorted)
--help show this usage
Usage:
> pokemon {flags}
flags:
--avatar <String>
--pokemon <String>
--catch
--help
--list-sorted
I also implemented the (more expected) --help command, which looks cleaner too:
/code> pokemon --help
Catch an asciinema pokemon on demand.
--avatar AVATAR generate a pokemon avatar for some unique id.
--pokemon POKEMON generate ascii for a particular pokemon (by name)
--catch catch a random pokemon!
--list list pokemon available
--list-sorted list pokemon available (sorted)
--help show this usage
To catch a random pokemon, use the --catch flag.
/code> pokemon --catch
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@,@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@+,.,@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@,::.:@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@.,,:*.@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@,,,:**S@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*,,,:**S@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@,,,,**S.***@@@@@@@@@@@
@@@+,,,,,,,,,*@@@@@@@@@@@@@@@@@@@@@@@*,,:,**%++..*@@@@@@@@@@
@,,,,,,,,,,,,,,,@,.@@@@@@@@@@@@@@@@@@*,:::*.::+++.@@@@@@@@@@
,,,,,,,,,,,,,,,,,,,,@,@@@@@@@@@@@@@@@@::::*.S,:::+@@@@@@@@@@
@,,,,,,,,,,,,,::,,,,,,,@.@@@@@@@@@@@@@*:::**.S::,:.+.@@@@@@@
@@,,,,,,,,,:::::::::::,,,,*@@@@@@@@@@@@*:::*.S..%%.++++.S@@@
@@@,,,,,,,,:*:::**.::*:**::,@@@@@@@@@@...+::*@@@@@@@@@@@@@@@
@@@@.,,,,,,:*::*@@@@@@@@...::,@@@@@@@*:+**@@@.@@@@@@@@@@@@@@
@@@@@@*,,,,,*::.@@@@@@@@@@@..::@@@@@:*:,:*@@@@@@@@@@@@@@@@@@
@@@@@@@@:,,:::.*:@@@@@@@@@@@@.:,@@@****::+@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@.,::*.+@@@@@@@@@@@@,*@@+:**.::+@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@*::*.,@@@@@@@@@@@@*:::::+::*@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@::..@@@@@@@.:,::::::.::.@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@*+.:::::::::***:*::.@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@.*:::::::++:::::****+**S*S.@@@@@@@@@@@@@@@@@
@@@@@@@@@@,+::::::::::.+.::::*****.....S:*++@@@@@@@@@@@@@@@@
@@@@@+****.:::::::.:+++::*******S+++++****+S...@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@:.++***********+.+S***...?@@@@...@@@@@@@@@@
@@@@@@@@@@@@@@@:...,...*******.++.+..****@@@@@@@@***.@@@@@@@
@@@@@@@@@@@@@,****@@@+......+++..******,..*@@@@@@@@:::.@@@@@
@@@@@@@@@@@@::::,@@@@@@@+..++******+@@@@@@@....,@@@@::::@@@@
@@@@@@@@@@@::::*,,:.:*::**.@@@@@@@@@@@@@@@@@@@@*:::*:*:::@@@
@@@@@@@@@:*:****::*:***.@@@@@@@@@@@@@@@@@@@@@@@@@@@@:.::::@@
@@@@@@@@@***:*::****+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@****:*::.@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Cresselia http://pokemondb.net/pokedex/cresselia
You can generate a unique avatar based on a string input (maybe your alias?)
/code> pokemon --avatar dinosaur
@@@@@@@@@@@@.?%..%%.@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@%*.........****.*%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@?.+S%?%.+++++.....++?.*..+@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@.......%...**+.++..%+.+##.#@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@,.%......%...S....?+%SSSS+S.@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@SS*..+***..........*+?+S+?.S@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@:+*..,,,:::*S+......*.++++.*@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@%S@,,,:::#.:%........+++++@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@S,,,::::.?..*........++++,@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@,,::::,,::::**....+%*.+++,@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@.S,@+,:::::::*:*.....%++S@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@.:::::.*...*..S++@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@,:::,@.%+++++S+@@@@@@@@@@@@@@@@@@.*..?@@@@
@@@@@@@@@@@@@@@@@@,:::,@@@@@@@@@@@@@@@@@@@@@@@@@@@+....++@@@
@@@@@@@@@@@@@@@S..,::::S%*@@@@@@@@@@@@@@@@@@@@+...++++++++++
@@@@@@@@:.,,,,:+::::::::*%+:*,,#@@@@@@@@@@@@@+....+.#S.S+...
@@@@@@@,,,:.,,:,::::::,::::::,#::,.@@@@@@@@@@@.++++%*.#+++++
@@@@@@@@.,,,,,,:?,,,,,,?::::::::,@@@@@@@@@@@@@@@@@S?::S+,,@@
@@@@@@@*,,,:,++SS:,,,,,?S+%:::::,,@@@@@@@@@@@@@@@@,,::@@@@@@
@@@@@@@%+%?++....*@,,,,SS+#S?S+@@@@@@@@@@@@@@@@@@+,,:.@@@@@@
@@@@:.+..S+*........*.S...S%S++@@@@@@@@@@@@@@@@@.,,:.@@@@@@@
@@@,....S%%............?.+.++S#%@@@@@@@@@@@@@@+,,,::@@@@@@@@
@@@@%S+S.+S..............?S+?.++%@@@@@@@@@@+,,,,::%@@@@@@@@@
@@@@@@.......................++++%@@@@:%*,,,,,::,:@@@@@@@@@@
@@@@@@?...+?++.....+++.......+++++.******::::::?@@@@@@@@@@@@
@@@@@@:....+?+++++++++?.....++++++%******::::%@@@@@@@@@@@@@@
@@@@@@@@.+++++#++++++++.++++++S++++:******::@@@@@@@@@@@@@@@@
@@@@@@@@@@@.++++..++++++.++++++++++:***:%@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@.+++.@@@@*????#%++++++?@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@*..+++:@@@@@@@@@@@@@@++++#@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@%.S%S.++++?@@@@@@@@@@@@@@@S+++++@@@@@@@@@@@@@@@@@@@@@@
@@@S.S?+SSS+++++*@@@@@@@@@@@@@%?...%?.@@@@@@@@@@@@@@@@@@@@@@
@%S?+SSSSSS+.@@@@@@@@@@@@@@@.*SS++%SSS+@@@@@@@@@@@@@@@@@@@@@
@,.SSS%.:@@@@@@@@@@@@@@@@@@S.SSSS#+SSSS+@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@.SSSS,%SSSSSS@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@,S+.@@@%SS+?@@@@@@@@@@@@@@@@@@@@@@
dinosaur
You can list all the pokemon names in the database
/code> pokemon --list
...
Sliggoo
Goodra
Klefki
Phantump
Trevenant
Pumpkaboo
Gourgeist
Bergmite
Avalugg
Noibat
Noivern
Xerneas
Yveltal
Zygarde
Diancie
Hoopa
Volcanion
You can also list them sorted (ABC-Z)
/code> pokemon --list-sorted
Once you know a specific pokemon, you can request him by name
/code> pokemon --pokemon goodra
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@..@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@.::::*S,,,,,,:,@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@.%..+,+,,,::::::,:*@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@+:..?:::::::::::::::,@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@:,,,:::::%++*:::::::@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@,,,,,,::::::SS@*::::,:@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@,,,,:+,*++::.S@@@:::::*@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@,,,:.+:.S+SS:S@@@@.::::@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@,S.......*:*S@@@@@,:::,@@@@@@@@::
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@::?....%%S@@@@@@@%::::S@@@@.:@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@,@S....%@+*@@@@@@@@,*:::::S@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@:S....*@*.@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@,:.....@@@@...+@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@S::%....,@@@@@@*S@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@+:::..:..:,,,,.@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@::,::::::,::::*@@S:,,,,,.?@@@@@@@@@@
@@@@@,,,,+@@@@@@@@@@@,::::::::::::::::....::::.@@@@@@@@@@@@@
@@,:,,,.::@@@@@@@@@,:::::::.+:::::.::.....%::,@@@@@@@@@@@@@@
@@,,,+,::@@@@@@@@,:::::::....:*.....:S@@@@@,,@@@@@@@@@@@@@@@
@*:,:::.@@@@@@@@*::::::....*::*......:*@@@@@@@@@@@@@@@@@@@@@
:::,:::::.@@@@@::::::::..*::::*......%::@@@@@@@@@@@@@@@@@@@@
.::::::::::::,:::,::::::+:::::.......+:::@@@@@@@@@@@@@@@@@@@
:.:,:::::::::*:::,:::::::*::*.........::,,@@@@@@@@@@@@@@@@@@
@SS:,,:::::,,:::,,,:::::::...........:::,,@@@@@@@@@@@@@@@@@@
@@+++S::,,,,,::::,,,:::::::.........::::::@@@@@@@@@@@@@@@@@@
@@@S+S+SS.++..::::::::::::*+++++++........@@@@@@@@@@@@@@@@@@
@@@@%+++S.SS+..............++++S.........@@@@@@@@@@@@@@@@@@@
@@@@@@@[email protected]@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@*%++++........,@@@@@@@%.....:@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@.....@@@@@@@@@@@@,.%.@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@...+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Goodra
It's possible to test without nushell by giving json objects to the python script, and you'll need Python3 installed on your host.
$ ./nu_plugin_pokemon.py
{"method":"config"}
{"jsonrpc": "2.0", "method": "response", "params": {"Ok": {"name": "pokemon", "usage": "Catch an asciinema pokemon on demand.\n\n --avatar AVATAR generate a pokemon avatar for some unique id.\n --pokemon POKEMON generate ascii for a particular pokemon (by name)\n --message MESSAGE add a custom message to your ascii!\n --catch catch a random pokemon!\n --list list pokemon available\n --list-sorted list pokemon available (sorted)\n", "positional": [], "rest_positional": null, "named": {"avatar": {"Optional": "String"}, "pokemon": {"Optional": "String"}, "message": {"Optional": "String"}, "catch": "Switch", "list": "Switch", "list-sorted": "Switch"}, "is_filter": false}}}
Unlike a filter, the method we care about here is "sink" - where basically nu will give full control of stdout to the plugin, along with all the input parameters from the user. Here is an example with requesting to catch a pokemon:
$ ./nu_plugin_pokemon.py
{"jsonrpc":"2.0","method":"sink","params":[{"args":{"positional":null,"named":{"catch":{"tag":{"anchor":null,"span":{"start":10,"end":15}},"item":{"Primitive":{"Boolean":true}}}}},"name_tag":{"anchor":null,"span":{"start":0,"end":7}}}]}
The above will print the pokemon to the screen.
Keep in mind that when nushell finds the plugin on the path, it's going to:
- discover the plugin by way of being on the path
- call the "config" method to get metadata and register it
- call sink when it's invoked
- nu passes stdout to the plugin
- the plugin can print to stdout to it's heart's content!
That's a very high level description, see the plugin page to read more about discovery and usage.
Note that since I'm going to be using the plugin in a container, I don't
mind logging to a temporary file at /tmp/nu-plugin-pokemon.log
. This
will show basic operations for each execution, for example:
/code> cat /tmp/nu_plugin_pokemon.log
root - INFO - REQUEST {"jsonrpc":"2.0","method":"sink","params":[{"args":{"positional":null,"named":{"catch":{"tag":{"anchor":null,"span":{"start":10,"end":15}},"item":{"Primitive":{"Boolean":true}}}}},"name_tag":{"anchor":null,"span":{"start":0,"end":7}}},[]]}
root - INFO - METHOD sink
root - INFO - PARAMS {'catch': True}
root - INFO - We want to catch a random pokemon!
It's helpful to use nu -l trace
to confirm that nu found your plugin,
and see other debugging information (along with our temporary log file).
/code(add/circleci)> nu -l trace
...
TRACE nu::cli > Trying "/usr/local/bin/nu_plugin_pokemon"
TRACE nu::cli > processing response (717 bytes)
For those interested in seeing the responses that get shared with the plugin from nu, here are a few examples. This was done when I was just developing, and testing named arguments for:
- mandatory
- optional
- switch
/code> pokemon --mandatory MANDATORYARG
/code> cat /tmp/nu_plugin_pokemon.log
root - INFO - REQUEST {"jsonrpc":"2.0","method":"sink","params":[{"args":{"positional":null,"named":{"mandatory":{"tag":{"anchor":null,"span":{"start":20,"end":32}},"item":{"Primitive":{"String":"MANDATORYARG"}}}}},"name_tag":{"anchor":null,"span":{"start":0,"end":7}}},[]]}
root - INFO - METHOD sink
/code> pokemon --mandatory MANDATORYARG --optional OPTIONALARG --switch
/code> cat /tmp/nu_plugin_pokemon.log
root - INFO - REQUEST {"jsonrpc":"2.0","method":"sink","params":[{"args":{"positional":null,"named":{"switch":{"tag":{"anchor":null,"span":{"start":58,"end":64}},"item":{"Primitive":{"Boolean":true}}},"mandatory":{"tag":{"anchor":null,"span":{"start":20,"end":32}},"item":{"Primitive":{"String":"MANDATORYARG"}}},"optional":{"tag":{"anchor":null,"span":{"start":44,"end":55}},"item":{"Primitive":{"String":"OPTIONALARG"}}}}},"name_tag":{"anchor":null,"span":{"start":0,"end":7}}},[]]}
I hope it helps someone!