Skip to main content

A lightweight CLI support package.

Project description

Tryst

CLI support package.

tryst on Github

GNU GPLv3

One's body is inviolable, subject to one's own will alone.

What Tryst is:

A lightweight interface and context package for basic cli features intended for rapid atomic problem-solving. Built to support code-time chaining between apps for efficient code-reuse and 'build-outward' problem-solving methodologies.

Tryst is lightweight but means to compy with SOLID design principles and is a learning experience in action to build a better understanding of Python, CLI development, unit testing, deployment, support, and more.

What Tryst is not:

Tryst is not fancy or comprehensive. It is not intended to be flawless, nor to replace more fully-functional and well-established CLI support packages such as argparse or getopt. It is a project for learning and for rapid development of personal workflow enhancements and automation.

This is evidenced by very little validation; the burden of understanding is left to the implementing developer. For example, options and option-arguments are not duplicate-checked; undefined behavior will occur if you define more than one option object with the same brief or verbose tokens. Additionally, most of Tryst's methods are public rather than private; this is by design, to provide maximum flexibility to the implementer.

Features

  • Options and Option-Arguments specifiable via verbose (e.g. --debug) and brief (e.g. -d) tokens
    • Brief tokens for options can be stacked e.g. -abc
  • Configuration via config.json file and get_config* API
  • Decoupled output; easily avoid unnecessary spew to stdout or stderr
    • write* api allows easy to-file functionality
  • Procedural usage instructions
  • Secrets (e.g. credentials) Support via get_secret API
    • Future development may include credential encryption or similar security features

Usage

Tryst has recently been upgraded. To implement your own Tryst-based app, inherit Tryst and implement main(). Note the inputs argument which allows you or another developer to call your Tryst's main method with specifiable arguments at code-time, greatly expediting code reuse.

from tryst import Tryst     # Tryst class defines your app and its main
from tryst import Option    # Option defines options and option-arguments e.g. --debug

class MyTrystApp(Tryst):
    def main(self, inputs = None):
        self.appname = "my-tryst-app"

        # ...1. define and add your options and option-arguments here

        self.consort(inputs)

        # ...2. implement your app's behavior here based on self.userargs, self.useroptions, and self.useroptionarguments

        self.finish()   # Wrap-up and output

Additional Implementation Details

Options, or switches, allow users to toggle functionality in your app. Option-Arguments allow for value-based user-input.

  1. Define and add your options and optionarguments, establishing the rules of engagement for your tryst:
# Option("verbose-token", "description", "optional-brief-token")

# This option can be used with --my-option or -m
self.myoption = Option("my-option", "switches behavior in my app", "m")

# Add the option to your Tryst
self.add_option(self.myoption)

# This option-argument can be used with --my-option-argument="some value" or -a="some value"
self.myoptionargument = Option("my-option-argument", "lets a user specify a value for my app", "a")

# Add the option-argument to your tryst
self.add_option_argument(self.myoptionargument)
  1. Govern your app behavior based on the arguments, options, and option-arguments the user specified:
if self.myoption in self.useroptions:
    # Act on --my-option

myoptargval = self.useroptionarguments.get(self.myoptionargument)
if myoptargval:
    # Act on --my-option-argument="some value"

Provide Usage Instructions to Users

mytryst.show_usage()

Note: this may be appropriate in your app due to various criteria, such as if the user specified no arguments, or no options; because every app is different, the burden of making the call to provide this usage is on the developer. The show_usage() method creates procedural instructions based on your Tryst's specified options and optionarguments.

Decouple & Buffer Your Output

Use self.output(message) for result output and self.error(message) for error output.

If you are working to diagnose your app while developing, use self.debug(message) to only display output when --debug is specified or self.DEBUG is set to True.

Keep in mind that self.output and self.error buffer output to self.outputbuffer and self.errorbuffer respectively, which are written/flushed via self.write_stdout() and self.write_stderr().

Write Your Output

To write output and errors to stdout and stderr respectively and simply, call self.finish().

This indicates that your Tryst has concluded and its outputs are ready.

finish() does not call sys.exit(), while quit() does. sys.exit() interferes with app chaining because it ends the current python session completely. To build your app to be easily callable from another and to support app chaining, use self.finish() or call the appropriate write methods manually:

self.write_stdout()
self.write_stderr()

Tryst uses JSONHelper internally to cleanly format Python dict or list objects that may be in your Tryst's outputbuffer when writing. Expect dictionary and list objects you've passed to self.output to be written in an alphabetically sorted, readable, 4-space indented multiline string.

CLI Conventions

  • short options can be stacked
    • e.g. tryst -e2 is equivalent to tryst -e -2 and tryst --error --two respectively
  • option-arguments only allow =, not spaces; complex values should be quoted at the shell
    • e.g. --debug=true, not --debug true
    • e.g. --name="Wholesome Necromancer", not --name=Wholesome Necromancer
  • order of options, option-arguments, and arguments does not matter in usage
    • e.g. tryst.py --debug -e2 arg1, tryst.py -2e arg1 --debug, and tryst.py -e arg1 -2 --debug are equivalent

Best Practices

  • Get configuration data after consort() is called to ensure the correct configuration file and path are loaded.

Examples

Trivial example of app chaining:

#! trystchild.py

from tryst import Tryst
from tryst import Option

class TrystCapitalize(Tryst):
    def main(self, inputs = None):
        # self.DEBUG = True
        self.appname = "tryst-capitalize"
        self.authors = "wholesomenecromancer"
        self.summary = "Progeny of Tryst. Capitalizes strings."
        self.version = "0.x.y"

        self.consort(inputs)

        for argg in self.userargs:
            self.output(argg.upper())
        self.finish()
#================================================================================

class TrystChild(Tryst):
    def main(self, inputs = None):
        # self.DEBUG = True
        self.appname = "trystchild"
        self.authors = "wholesomenecromancer"
        self.summary = "Descends from Tryst. Echoes strings."
        self.version = "0.0.12a"

        # Options
        self.capitalize_option = Option("capitalize-inputs", "Capitalizes arguments before echoing using tryst-chaining.", "C")
        self.add_option(self.capitalize_option)

        # consort
        self.consort(inputs)

        self.debug("Example debug statement.")

        # use the tryst to govern app behavior
        if len(self.userargs) < 1:
            self.debug("No args given.")
            self.show_usage()
            self.quit()
        
        # Capitalize by using TrystCapitalize
        if self.capitalize_option in self.useroptions:
            captryst = TrystCapitalize()                                    # Construct
            captryst.silence()                                              # Silence console output
            captryst.main(self.userargs)                                    # Pass the user's arguments 
            if captryst.outputbuffer:                                       # Get TrystCapitalize's output directly
                self.userargs = captryst.outputbuffer                       # Replace our userargs before outputting

        for arrg in self.userargs:
            self.output(arrg)
        self.finish()
#================================================================================

#----------------------------------------
if __name__ == "__main__":
    TrystChild().main()
#----------------------------------------

Tests

Tests can be run from src/tryst/ via python -m unittest.

Documentation

Documentation is intended for use with pdoc:

pdoc tryst.py pdoc -o <destdir> tryst.py

Support

Tryst intends to be platform-agnostic but has only been tested in Windows 10 environments with PowerShell 5.x and WSL 2.0's Ubuntu 20.x bash.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tryst-0.0.2.tar.gz (28.7 kB view hashes)

Uploaded Source

Built Distribution

tryst-0.0.2-py3-none-any.whl (28.3 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page