Code Monkey home page Code Monkey logo

failprint's Introduction

failprint

ci documentation pypi version gitpod gitter

Run a command, print its output only if it fails.

Tired of searching the quiet options of your programs to lighten up the output of your make check or make lint commands?

Tired of finding out that standard output and error are mixed up in some of them?

Simply run your command through failprint. If it succeeds, nothing is printed. If it fails, standard error is printed. Plus other configuration goodies 😉

Example

You don't want to see output when the command succeeds.

demo

The task runner duty uses failprint, allowing you to define tasks in Python and run them with minimalist and beautiful output:

demo_duty

Requirements

failprint requires Python 3.8 or above.

To install Python 3.8, I recommend using pyenv.
# install pyenv
git clone https://github.com/pyenv/pyenv ~/.pyenv

# setup pyenv (you should also put these three lines in .bashrc or similar)
export PATH="${HOME}/.pyenv/bin:${PATH}"
export PYENV_ROOT="${HOME}/.pyenv"
eval "$(pyenv init -)"

# install Python 3.8.17
pyenv install 3.8.17

# make it available globally
pyenv global system 3.8.17

Installation

With pip:

pip install failprint

With pipx:

python3.8 -m pip install --user pipx
pipx install failprint

Usage

% poetry run failprint -h
usage: failprint [-h] [-c {stdout,stderr,both,none}] [-f {pretty,tap}] [-y | -Y] [-p | -P] [-q | -Q] [-s | -S] [-z | -Z] [-n NUMBER]
                 [-t TITLE]
                 COMMAND [COMMAND ...]

positional arguments:
  COMMAND

optional arguments:
  -h, --help            show this help message and exit
  -c {stdout,stderr,both,none}, --capture {stdout,stderr,both,none}
                        Which output to capture. Colors are supported with 'both' only, unless the command has a 'force color'
                        option.
  -f {pretty,tap}, --format {pretty,tap}
                        Output format. Pass your own Jinja2 template as a string with '-f custom=TEMPLATE'. Available variables:
                        command, title (command or title passed with -t), code (exit status), success (boolean), failure (boolean),
                        number (command number passed with -n), output (command output), nofail (boolean), quiet (boolean), silent
                        (boolean). Available filters: indent (textwrap.indent).
  -y, --pty             Enable the use of a pseudo-terminal. PTY doesn't allow programs to use standard input.
  -Y, --no-pty          Disable the use of a pseudo-terminal. PTY doesn't allow programs to use standard input.
  -p, --progress        Print progress while running a command.
  -P, --no-progress     Don't print progress while running a command.
  -q, --quiet           Don't print the command output, even if it failed.
  -Q, --no-quiet        Print the command output when it fails.
  -s, --silent          Don't print anything.
  -S, --no-silent       Print output as usual.
  -z, --zero, --nofail  Don't fail. Always return a success (0) exit code.
  -Z, --no-zero, --strict
                        Return the original exit code.
  -n NUMBER, --number NUMBER
                        Command number. Useful for the 'tap' format.
  -t TITLE, --title TITLE
                        Command title. Default is the command itself.
from failprint.runners import run

cmd = "echo hello"

exit_code = run(
    cmd,            # str, list of str, or Python callable
    args=None,      # args for callable
    kwargs=None,    # kwargs for callable
    number=1,       # command number, useful for tap format
    capture=None,   # stdout, stderr, both, none, True or False
    title=None,     # command title
    fmt=None,       # pretty, tap, or custom="MY_CUSTOM_FORMAT"
    pty=False,      # use a PTY
    progress=True,  # print the "progress" template before running the command
    nofail=False,   # always return zero
    quiet=False,    # don't print output when the command fails
    silent=False,   # don't print anything
)

failprint's People

Contributors

pawamoy avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

failprint's Issues

Some tools use sys.stdout.buffer to write

Describe the bug
Tools like flake8 use sys.stdout.buffer to write things to the terminal:

    File "/media/data/dev/griffe/__pypackages__/3.7/lib/flake8/formatting/base.py", line 179, in _write
      sys.stdout.buffer.write(output.encode() + self.newline.encode())
  AttributeError: '_io.StringIO' object has no attribute 'buffer'

Expected behavior
This should be supported. I believe we can add a buffer method on our fake buffers that accept bytes instead of str and tries to decode them before writing them again with self.write.

Option to allow failure

Is your feature request related to a problem? Please describe.
Sometimes a command's success is optional. It would be nice to mark it as "allow failure".

Describe the solution you'd like
An option to tell the command can fail but still return a 0 exit code, with a yellow cross instead of a green check mark.

Allow running Python tool in the same process

Is your feature request related to a problem? Please describe.
Spawning Python processes is expensive.

Describe the solution you'd like
When executing a Python tool, the user could tell failprint what to import and run, for example:

failprint --python-callable=flake8.main.cli.main -- flake8 src

Catch SystemExit

Describe the bug
Failprint catches Exception when running a callable, but that won't catch SystemExit.

Intercept subprocesses output in callables

Is your feature request related to a problem? Please describe.
failprint can run a Python callable and catch the output from sys.stdout/sys.stderr.
That's nice but sometimes a callable will also run a subprocess, and failprint won't be able to catch the output of the subprocess.
Example: the mkdocs.gh_deploy callable from duty runs a Git subprocess. Output is not captured, and instead is written to stdout/stderr.

Describe the solution you'd like
Somehow intercept subprocess output. The only solution I see is: patch subprocess.Popen (we want to patch the lowest level util while keeping easy-to-maintain code) to actually capture the output and print it to (our patched) sys.stdout (depending on the args provided by the third-party code/callable).

Callables do not always have a __name__ attribute

Describe the bug

    from mkdocs.__main__ import cli as mkdocs
    ctx.run(mkdocs, args=["build", "-s"], title="Building documentation")
  File "/media/data/dev/griffe/__pypackages__/3.7/lib/failprint/formats.py", line 134, in as_python_statement
    return f"{func.__name__}({args_str})"
AttributeError: 'Group' object has no attribute '__name__'

Expected behavior
If no __name__ attribute is present, find another way to get a name 😕

Option to not print the command even if there's a title (pretty format)

Is your feature request related to a problem? Please describe.
Sometimes the command is a Python statement with lots of arguments. It might take a lot of space on the screen for nothing.

Describe the solution you'd like
An option to tell the formatter not to print the command in any case.

Describe alternatives you've considered
Wrapping the call in a lambda?

Add progress option

With a small animation (bar/square turning) while the process is running. At the end, write \r + line length spaces + \r.

Not all executables are found in a shell command on Windows

Describe the bug
The first exec of a shell command on Windows is "whiched" to make sure it is found. However a shell command can run multiple executables (with pipes, etc.), so some exec might not be "whiched" and the process results in "exec is not recognized as a command...".

To Reproduce
On Windows, run:

from failprint.runners import run

run("poetry show | grep failprint")

There's a high chance grep is not found.

Expected behavior
Every executable is correctly "whiched". Or some other clever trick.

System:

  • failprint version: 0.4.1
  • Python version: 3.6
  • OS: Windows

Additional context
Aditionally, the output often cannot be decoded as UTF-8 (see #7).

Allow running Python function

Is your feature request related to a problem? Please describe.
In the same way as #3, Python processes are expensive.
Moreover, there's not always an executable file or a Python module we can call.

Describe the solution you'd like
Being able to run a Python function. Basically, failprint would run the function and catch stdout and stderr, catch any exception and return 1, or return 0 if bool(func_result) or 1.

Cannot decode output when exec is not found on Windows

Describe the bug
It seems CMD is not using UTF-8 by default. When a command fail (for example when an exec is not found, see #6), and failprint tries to decode the output, it fails.

To Reproduce
Same command as in #6.

Expected behavior
The error message is at least properly decoded and printed by failprint.

System (please complete the following information):

  • failprint version: 0.4.1
  • Python version: 3.6
  • OS: Windows

Additional context
The encoding used by the console can be obtained with the command chcp. It prints a sentence in the current locale with, I hope always in this format, the "code page" at the end, something like Page de codes active : 850. This code page can then be used to decode the output. We could do something like this:

try:
    _, chcp_output = _run_process(["chcp"])
    os_encoding = re.search(r"(\d+)").group(1)  # code page does not lose characters
except UnicodeDecodeError:
    import locale  # locale sometimes lose characters (é -> ,)
    os_encoding = locale.getpreferredencoding()

try:
    stdout = stdout.decode("utf8")
except UnicodeDecodeError:
    if not os_encoding:
        raise
    stdout = stdout.decode(os_encoding)

Things to try to fix runs on Windows...

ns = collection.Collection(directories)
ns.configure({
    'run': {
        'shell': os.environ.get('COMSPEC', os.environ.get('SHELL'))
    }
})

Or always use double-quotes to wrap arguments in context.run.
Or run commands with bash -c.

Use Rich for markup

Is your feature request related to a problem? Please describe.
Ansimarkup (currently used) crashes when it encounters unknown markup tags (typically XML).
I've opened an issue more than one year ago but didn't get feedback from the maintainer (gvalkov/python-ansimarkup#17).

Describe the solution you'd like
A solution would be to switch to Rich which provides an escape method: https://rich.readthedocs.io/en/stable/reference/markup.html#rich.markup.escape. It also uses [] instead of <> so would not have any problems with XML in the output.

Describe alternatives you've considered
Implementing an escape function in Ansimarkup.

Additional context
Rich would also allow much more rich (pun intended) formats.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.