Tiny, dependency-light helpers for beautiful terminal output, interactive prompts, a less-style pager, structured tables, and a cute ASCII world map. Designed to drop straight into scripts and CLIs.
- Pretty status lines:
[*],[+],[-],[!],[i]with color and bold. - Interactive input with history and ANSI prompts (
prompt_toolkit). - Built-in pager (less-like) for long output (
Enter/Space/a/q). - Logging of all output and prompts to a file (opt-in).
- Tables: quick, readable ASCII tables.
- ASCII World Map: plot a dot by latitude/longitude.
- Command wrapper: utilities for building interactive shells.
Note: This project is typically installed from source/git.
pip install git+https://github.com/EntySec/Badgesfrom badges import Badges
ui = Badges()
# Optional: configure once per process
ui.set_less(True) # enable less-like paging
ui.set_log("mytool.log") # log all prints & prompts
ui.set_history(".mytool_history") # persistent input history
ui.print_process("Starting...")
ui.print_success("All good.")
ui.print_warning("Heads up.")
ui.print_error("Something went wrong.")
ui.print_information("FYI only.")
answer = ui.input_question("Proceed? [y/N]: ")
if answer.lower() in ("y", "yes"):
ui.print_process("Continuing...")
else:
ui.print_warning("Aborted by user.")The package exposes four main entry points:
from badges import Badges, Tables, MapBadges extends the IO class (see below). It provides convenience prefixed prints and inputs:
print_empty(message, *, start='%remove%end', end='%newline', time=False, log=None, less=None)print_usage(message, start='%remove', ...)→Usage: ...print_process(message, start='%remove', ...)→%bold%blue[*]%endprefixprint_success(message, start='%remove', ...)→%bold%green[+]%endprint_error(message, start='%remove', ...)→%bold%red[-]%endprint_warning(message, start='%remove', ...)→%bold%yellow[!]%endprint_information(message, start='%remove', ...)→%bold%white[i]%end
Where start/end accept ColorScript tokens. Common tokens include:
%bold,%red,%green,%blue,%yellow,%white%end(reset styles),%newline(append newline),%remove(clear line),%clear(clear screen),%line(underline)
The underlying render call is
IO.print(message, start, end, time, log, less).
input_empty(message='', start='%end', end='', **ptk_kwargs) -> strinput_question(message, start='%end', ...) -> str→%bold%white[?]%endpromptinput_arrow(message, start='%end', ...) -> str→%bold%white[>]%endprompt
Input uses a single PromptSession with optional history (set_history(path)).
set_log(path: str)– when set, every print and prompt is appended topath.set_history(path: str)– enablesprompt_toolkit.FileHistory(path)for inputs.set_less(enabled: bool)– turns the pager on/off globally (default:Trueif unset).
You can temporarily override per call via log= or less= parameters to print().
Timestamped messages:
ui.print_process("Connecting...", time=True) # [*] 14:32:10 - Connecting...Disable pager for a single long line:
ui.print_information(big_text, less=False)Sensitive input (avoid logging this one):
ui.set_log("app.log")
secret = ui.input_question("Enter API key: ", log=False)Core lower-level I/O utilities:
-
print(message='', start='%remove%end', end='%newline', time=False, log: Optional[bool]=None, less: Optional[bool]=None) -> NoneRendersstart + message + endviaColorScriptand prints via:- Pager (
print_less) whenlessisTrue. - Direct
stdoutotherwise. Iflogis enabled (globally or via arg), writes the rendered string to file.
- Pager (
-
input(message='', start='%end', end='', **kwargs) -> strBuilds the prompt (with ANSI) and returns the user’s input. Iflogis enabled, logs the prompt and the typed response. -
set_log(path),set_history(path),set_less(bool)– global setters described above. -
print_less(data: str)A simple pager:- Shows
(rows - 3)lines per “screen”. - Controls: Enter (advance one line), Space (+10 lines), a (show all), q (quit).
- Shows
-
suppress_function(fn, *a, **kw) -> AnyRun a function while suppressing itsstdout/stderr. -
print_function(fn, *a, **kw) -> AnyRun a function, capture itsstdout/stderr, then print the captured output (through the pager).
Tip: In CI/non-TTY environments the pager may wait for a keypress. Disable it with set_less(False) or pass less=False on prints.
from badges import Tables
tbl = Tables()
rows = [
("111", "Ivan Nikolskiy"),
("112", "Jane Doe"),
]
tbl.print_table("Users", ("ID", "Name"), *rows)Sample output:
Users:
ID Name
-- ----
111 Ivan Nikolskiy
112 Jane Doe
Notes
- The implementation is deliberately simple and favors readability over speed.
- It handles ANSI sequences by adjusting width so colored cells align.
- Optional
extra_fill=kwarg sets padding per column (default4).
from badges import Map
m = Map() # 'world' map, red dot by default
m.deploy(55.751244, 37.618423) # Moscow
print(m.get_map())You’ll get a world ASCII map with a colored dot marking the point. Constructor options:
Map(map_name='world', dot='%red*%end')
Helpers:
location(latitude, longitude) -> (x, y)deploy(latitude, longitude) -> Noneget_map() -> str
badges/cmd.py provides a higher-level scaffold (Cmd) for CLI shells with:
- Intro and customizable prompt (ColorScript aware).
- Command discovery (internal
do_<name>methods and external command modules). - Shortcuts (aliases), autocompletion, history,
!system-command passthrough, etc.
Minimal sketch:
from badges.cmd import Cmd, Command
class Hello(Cmd):
def do_hello(self, args):
"""Say hello."""
self.print_success("Hello!")
if __name__ == "__main__":
shell = Hello(prompt='%green$ %end', intro='%bold%lineDemo%end')
shell.loop()See badges/cmd.py for the full capabilities: external command loading, verify_command, argument parsing utilities, and built-ins like help, clear, exit, source, ! (system).
ui = Badges()
def noisy():
print("stdout noise")
import sys
print("stderr noise", file=sys.stderr)
return 42
result = ui.suppress_function(noisy) # prints nothing, returns 42
ui.print_information("Now showing captured output:")
ui.print_function(noisy) # prints via pagerimport sys
ui = Badges()
ui.set_less(sys.stdout.isatty())ui = Badges()
ui.set_log("audit.log")
ui.print_process("Starting up", time=True)
name = ui.input_question("Name: ")Q: Why do my logs contain escape sequences? A: The library writes the rendered line (with ANSI codes) to the log file. Some viewers will show them literally; others will colorize. If you need plain text logs, strip ANSI codes yourself.
Q: Can I disable logging for a specific prompt or print?
A: Yes. Pass log=False into print() or input() calls.
Q: The pager is blocking in my CI.
A: Disable it globally with set_less(False) or per call with less=False.
Q: Are Badges objects thread-safe?
A: No. They share module-global state (log, history, less, and a singleton PromptSession). Keep I/O on the main thread or add your own synchronization.
- Keep APIs small and predictable.
- Mind cross-platform behavior of
getchand terminal sizes. - PRs for table formatting and pager ergonomics are welcome.