Skip to main content

A Redis-backed REPL that saves command history, output, & errors

Project description

chloop is a Redis-backed character-driven REPL framework designed for developers who want to build custom interactive tools quickly and efficiently while providing progressive complexity. The library optimizes for single-keystroke efficiency while also leveraging vim’s colon command paradigm to transform command-line interactions from sequential operations into immediate context-aware experiences.

The core philosophy centers on cognitive load reduction through muscle memory development. Instead of forcing users to remember complex command syntax or navigate menus, chloop enables the creation of interfaces where common actions become instinctive single keystrokes, while advanced functionality remains accessible through discoverable colon commands.

Who benefits from this library: - Domain experts who need immediate control over complex systems (audio/video exploration, API testing, data analysis) - Interactive application developers building tools for power users who value efficiency over discovery - CLI tool creators who want to provide both accessibility for beginners and efficiency for experts - Content analysts needing distraction-free, keyboard-driven workflows for media exploration - Researchers who need to rapidly navigate and annotate large datasets or media collections

Real-world implementations include the mocp-cli package for interactive audio exploration with timestamp marking, and vlc-helper for precise video control and screenshot capture, demonstrating chloop’s effectiveness in media analysis workflows.

Install

If you don’t have docker installed, install Redis and start server

sudo apt-get install -y redis-server

or

brew install redis
brew services start redis

Install with pip

pip install chloop

Optionally install ipython with pip install ipython to enable :ipython colon command on a GetCharLoop instance. Also pip install pdbpp for an improved debug experience when using :pdb colon command.

QuickStart

The GetCharLoop class is provided by the chloop package. Calling an instance of this class starts a REPL session, which the user can end by pressing Ctrl + d or Ctrl + c.

The first character you type at the REPL prompt is significant.

The colon

Hitting the : key at the prompt will allow you to enter a command and any arguments you need to pass to that command.

  • :docstrings to view docstrings of methods defined on the class

  • :errors to view colon commands that raised exceptions

  • :history view colon commands issued

  • :pdb to start a pdb session (debugging/inspection)

  • :ipython to start ipython shell

  • :shortcuts to view hotkey shortcuts

Any methods added to your sub-class of GetCharLoop are callable as colon commands, as long as they do not start with an underscore (_). Methods should only accept ``*args``, if anything.

For any methods/commands that should not be logged to the history, append the method name to the end of the self._DONT_LOG_CMDS list.

The dash

Hitting the - key at the prompt will allow you to type a note.

The question mark

Hitting the ? key at the prompt will display the class docstring(s) and the startup message.

Hitting the ? key a second time will show the available colon commands and shortcuts (equivalent to issuing :docstrings and :shortcuts)

Other keys

Hitting any other key at the prompt will do one of the following:

  • call a registered shortcut function bound to the key (use :shortcuts command to see what is available)

  • display the character and its integer ordinal

A hotkey can be bound to any callable object that accepts no arguments. Use functools.partial (if necessary) to create a callable accepting no arguments.

Basic example

The default prompt if none is specified is >.

% python3 -c 'from chloop import GetCharLoop; GetCharLoop()()'

> ?
 Loop forever, receiving character input from user and performing actions

    - ctrl+d or ctrl+c to break the loop
    - ':' to enter a command (and any arguments)
        - any method defined on GetCharLoop (or a sub-class) will be callable
          as a "colon command" (if its name does not start with '_')
        - the method for the `:command` should only accept `*args`
    - '-' to allow user to provide input that will be processed by the `input_hook`
    - '?' to show class doc and the startup message
    - '??' to show class doc, the startup message, docstrings (:commands), and shortcuts

:docstrings to see all colon commands
:shortcuts to see all hotkeys


> :docstrings
.:: chars ::.
Show chars (hotkeys) pressed during current session

.:: cmds ::.
Show colon commands typed during current session

.:: docstrings ::.
Print/return the docstrings of methods defined on this class

.:: errors ::.
Print/return any colon commands that raised exceptions (w/ traceback)

.:: history ::.
Print/return successful colon commands used (default 10)

.:: ipython ::.
Start ipython shell. To continue back to the input loop, use 'ctrl + d'

.:: pdb ::.
Start pdb (debugger). To continue back to the input loop, use 'c'

.:: shortcuts ::.
Print/return any hotkey shortcuts defined on this class

.:: wishlist ::.
Show the wishlist (of hotkeys and commands that don't exist yet)

Sub-class example

  • Import GetCharLoop and sub-class it

  • Initialize the sub-class and call it

Save the following to mine.py

from functools import partial
from chloop import GetCharLoop


class Mine(GetCharLoop):
    """A sub-class of GetCharLoop"""
    def __init__(self, *args, **kwargs):
        # Process any extra/custom kwargs here and set some attributes
        self._mything = kwargs.pop('mything', 'some default value')

        super(Mine, self).__init__(*args, **kwargs)

        # Add some single-key shorcuts that call methods on `self`
        self._chfunc_dict_update([
            ('h', (self.history,
                  'display recent command history')),
            ('e', (self.errors,
                  'display recent errors')),
        ])


    def somefunc(self, *args):
        """Joins the args passed to it into a string"""
        args_as_one = ' '.join(args)
        print(repr(args_as_one))
        return args_as_one

    def lame(self):
        """raise exception"""
        return 1/0


if __name__ == '__main__':
    m = Mine(prompt='\nmyprompt> ')
    m._add_hotkey('a', lambda: print('hello'), 'say hello')
    m()

Assuming the above code is in a file called mine.py

% python mine.py

myprompt> :somefunc here are some args
u'here are some args'

myprompt> :shortcuts
'e' -- display recent errors
'h' -- display recent command history
'a' -- say hello

myprompt> a
hello

myprompt> :lame
======================================================================
Traceback (most recent call last):
  File "/home/ken/chloop/chloop/__init__.py", line 232, in __call__
    value = cmd_func()
  File "main.py", line 33, in lame
    return 1/0
ZeroDivisionError: integer division or modulo by zero

cmd: u'lame'
args: []

Extended example

This example shows a subset of actual functionality from mocp-cli and assumes the moc wrapper is avaialbe.

from functools import partial
from collections import OrderedDict
from chloop import GetCharLoop
import moc


class SimpleMusicPlayer(GetCharLoop):
    """Simplified music player interface using MOC backend"""

    def seek(self, num):
        """Seek forward or backward by specified seconds"""
        moc.seek(int(num))

    def go(self, timestamp):
        """Jump to a particular timestamp"""
        moc.go(timestamp)

    def find(self, *glob_patterns):
        """Find and select audio files at specified glob patterns"""
        moc.find_select_and_play(*glob_patterns)


shortcuts = OrderedDict([
    (' ', (moc.toggle_pause, 'pause/unpause')),
    ('i', (lambda: print(moc.info_string()), 'show info about currently playing file')),
    ('f', (partial(moc.find_and_play, '.'), 'find and play audio files found in current directory')),
    ('F', (partial(moc.find_select_and_play, '.'), 'find, select, and play audio files found in current directory')),
    ('Q', (moc.stop_server, 'stop MOC server and quit')),
    ('n', (moc.next, 'next file in playlist')),
    ('p', (moc.previous, 'previous file in playlist')),
    ('H', (partial(moc.seek, -30), 'rewind 30 seconds')),
    ('h', (partial(moc.seek, -5), 'rewind 5 seconds')),
    ('L', (partial(moc.seek, 30), 'fast forward 30 seconds')),
    ('l', (partial(moc.seek, 5), 'fast forward 5 seconds')),
    ('j', (moc.volume_down, 'lower volume')),
    ('k', (moc.volume_up, 'raise volume')),
    ('q', (lambda: None, 'quit')),
])

# Create and start the music player interface
player = SimpleMusicPlayer(
    chfunc_dict=shortcuts,
    name='simple-music',
    prompt='music> ',
    break_chars=['q', 'Q']
)

if __name__ == '__main__':
    player()

Then after starting the repl:

music> :shortcuts
Space -- pause/unpause
i -- show info about currently playing file
f -- find and play audio files found in current directory
F -- find, select, and play audio files found in current directory
Q -- stop MOC server and quit
n -- next file in playlist
p -- previous file in playlist
H -- rewind 30 seconds
h -- rewind 5 seconds
L -- fast foward 30 seconds
l -- fast foward 5 seconds
j -- lower volume
k -- raise volume
q -- quit

API Overview

Core Class

  • ``GetCharLoop(*args, **kwargs)`` - Main framework class for building character-driven interactive tools

    • chfunc_dict: OrderedDict mapping characters to (function, help_text) tuples for hotkeys

    • prompt: String to display when asking for input (default: '\n> ')

    • name: Value for Redis collection naming to isolate different tool instances

    • break_chars: List of characters that exit the loop (can trigger functions before exit)

    • input_hook: Callable receiving **kwargs to process user input after ‘-’ key pressed

    • pre_input_hook: Callable receiving no args executed when - is pressed, returning dict of data to input_hook

    • post_input_hook: Callable receiving no args executed after input collection, returning dict of data to input_hook

    • Returns: Configured GetCharLoop instance ready for direct use

    • Internal calls: rh.Collection()

Primary Methods

  • ``GetCharLoop.__call__()`` - Start the interactive character loop

    • Returns: None (runs until interrupted with Ctrl+C or Ctrl+D)

    • Internal calls: ih.getchar(), self.docstrings(), self.shortcuts(), ih.user_input_fancy(), bh.call_func(), ih.user_input(), ih.start_ipython()

Built-in Commands

All public methods automatically become :commands through naming convention:

  • ``GetCharLoop.docstrings(*args)`` - Display all available commands with their documentation

    • Returns: String containing formatted docstring output

    • Internal calls: None

  • ``GetCharLoop.shortcuts(*args)`` - Show configured hotkey mappings and their descriptions

    • Returns: String containing formatted shortcut output

    • Internal calls: None

  • ``GetCharLoop.history(*args)`` - Display recent successful commands

    • *args - Optional limit parameter (default 10)

    • Returns: None (prints formatted history)

    • Internal calls: ih.from_string()

  • ``GetCharLoop.errors(*args)`` - Show recent command errors with full tracebacks

    • *args - Optional limit parameter (default 10)

    • Returns: None (prints formatted error output)

    • Internal calls: ih.from_string()

  • ``GetCharLoop.chars()`` - Display characters pressed during current session

    • Returns: None (prints character history)

    • Internal calls: None

  • ``GetCharLoop.cmds()`` - Show colon commands typed during current session

    • Returns: None (prints command history)

    • Internal calls: None

  • ``GetCharLoop.wishlist()`` - Display captured user intent for non-existent commands and hotkeys

    • Returns: None (prints formatted wishlist)

    • Internal calls: None

Special Commands

  • ``:pdb`` - Launch Python debugger

  • ``:ipython`` - Start IPython shell with self available for inspection

Extension Methods

  • ``GetCharLoop._add_hotkey(ch, func, help_string)`` - Register single-character hotkey

    • ch: Character to trigger the hotkey

    • func: Callable object accepting no arguments (use functools.partial if needed)

    • help_string: Description shown in :shortcuts output

    • Returns: None

    • Internal calls: None

  • ``GetCharLoop._chfunc_dict_update(obj)`` - Bulk update hotkey mappings

    • obj: List of tuples or dict containing (character, (function, help_text)) mappings

    • Returns: None

    • Internal calls: None

Project details


Download files

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

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

chloop-0.2.24-py3-none-any.whl (10.1 kB view details)

Uploaded Python 3

File details

Details for the file chloop-0.2.24-py3-none-any.whl.

File metadata

  • Download URL: chloop-0.2.24-py3-none-any.whl
  • Upload date:
  • Size: 10.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.8.10

File hashes

Hashes for chloop-0.2.24-py3-none-any.whl
Algorithm Hash digest
SHA256 4ca05a4794a3a2ec36b10dbdbb7ec8b4b1246ddf7b541cd76bcb41276e229feb
MD5 af21dab85b964d710151460b05db0c53
BLAKE2b-256 a2e5c08948973ee5574e9b4b0b5dcb3316287b325eec0eb4344d09da8fa6b665

See more details on using hashes here.

Supported by

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