Skip to content

Commit

Permalink
initial public release
Browse files Browse the repository at this point in the history
  • Loading branch information
oxij committed Feb 1, 2024
0 parents commit d584140
Show file tree
Hide file tree
Showing 11 changed files with 1,825 additions and 0 deletions.
429 changes: 429 additions & 0 deletions README.md

Large diffs are not rendered by default.

Empty file added abarms/__init__.py
Empty file.
800 changes: 800 additions & 0 deletions abarms/__main__.py

Large diffs are not rendered by default.

208 changes: 208 additions & 0 deletions abarms/argparse_better.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
# This file is a part of kisstdlib project.
#
# This file can be distributed under the terms of the MIT-style license given
# below or Python Software Foundation License version 2 (PSF-2.0) as published
# by Python Software Foundation.
#
# Copyright (c) 2022-2023 Jan Malakhovski <oxij@oxij.org>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import typing as _t
from argparse import *
from gettext import gettext as _

class BetterHelpFormatter(HelpFormatter):
"""Like argparse.HelpFormatter, but with better formatting.
Also, it adds `add_code` function.
"""

def _fill_text(self, text : str, width : int, indent : str) -> str:
import textwrap
res = []
for line in text.splitlines():
if line == "":
res.append(line)
continue

for sub in textwrap.wrap(line, width - len(indent)):
sub = indent + sub
res.append(sub)
return "\n".join(res)

def _split_lines(self, text : str, width : int) -> _t.List[str]:
import textwrap
res = []
for line in text.splitlines():
res += textwrap.wrap(line, width)
return res

def add_code(self, text : str) -> None:
self.add_text(text.strip())

class MarkdownBetterHelpFormatter(BetterHelpFormatter):
"""BetterHelpFormatter that outputs stuff formatted in Markdown"""

def add_code(self, text : str) -> None:
self.add_text("```\n" + text.strip() + "\n```")

def _format_usage(self, usage : _t.Optional[str], actions : _t.Any, groups : _t.Any, prefix : _t.Optional[str]) -> str:
return super()._format_usage(usage, actions, groups, "")

def _format_action(self, action : _t.Any) -> str:
# determine the required width and the entry label
action_header = self._format_action_invocation(action)

tup = self._current_indent, '', "`" + action_header + "`"
action_header = '%*s- %s\n' % tup

# collect the pieces of the action help
parts = [action_header]

# if there was help for the action, add it
if action.help and action.help.strip():
first = True
for line in self._expand_help(action).splitlines():
if first:
parts.append('%*s: %s\n' % (self._current_indent, '', line))
else:
parts.append('%*s %s\n' % (self._current_indent, '', line))
first = False

# or add a newline if the description doesn't end with one
elif not action_header.endswith('\n'):
parts.append('\n')

# if there are any sub-actions, add their help as well
for subaction in self._iter_indented_subactions(action):
parts.append(self._format_action(subaction))

# return a single string
return self._join_parts(parts)

class _Section(HelpFormatter._Section):
def format_help(self) -> str:
if self.parent is not None:
self.formatter._indent()
join = self.formatter._join_parts
item_help = join([func(*args) for func, args in self.items])
if self.parent is not None:
self.formatter._dedent()

# return nothing if the section was empty
if not item_help:
return ''

# add the heading if the section was non-empty
if self.heading is not SUPPRESS and self.heading is not None:
heading = '%*s- %s:\n' % (self.formatter._current_indent, '', self.heading)
else:
heading = ''

# join the section-initial newline, the heading and the help
return join(['\n', heading, item_help, '\n'])

class BetterArgumentParser(ArgumentParser):
"""Like argparse.ArgumentParser but uses BetterHelpFormatter by default,
adds `--help` only to the root node, and
that `--help` prints the help for all the subcommands at once.
Also, provides `add_version` option.
"""

formatter_class : _t.Any

def __init__(self,
prog : _t.Optional[str] = None,
version : _t.Optional[str] = None,
add_version : bool = False, # we set these two to False by default
add_help : bool = False, # so that subparsers don't get them enabled by default
additional_sections : _t.List[_t.Callable[[BetterHelpFormatter], None]] = [],
formatter_class : _t.Any = BetterHelpFormatter,
*args : _t.Any, **kwargs : _t.Any) -> None:
super().__init__(prog, *args, formatter_class = formatter_class, add_help = False, **kwargs) # type: ignore

if version is None:
version = "dev"
if prog is not None:
try:
import importlib.metadata as meta
try:
version = meta.version(prog)
except meta.PackageNotFoundError:
pass
except ImportError:
pass

self.version = version
self.add_version = add_version
self.add_help = add_help
self.additional_sections = additional_sections

default_prefix = '-' if '-' in self.prefix_chars else self.prefix_chars[0]
if self.add_version:
self.add_argument(default_prefix*2 + "version", action="version", version="%(prog)s " + version)

if self.add_help:
self.add_argument(
default_prefix + "h", default_prefix*2 + "help", action='help',
default=SUPPRESS, help=_('show this help message and exit'))

def set_formatter_class(self, formatter_class : _t.Any) -> None:
self.formatter_class = formatter_class
if hasattr(self._subparsers, "_group_actions"):
for grp in self._subparsers._group_actions: # type: ignore
for choice, e in grp.choices.items(): # type: ignore
if e.formatter_class != formatter_class:
e.formatter_class = formatter_class

def format_help(self, width : _t.Optional[int] = None) -> str:
if width is None:
import shutil
width = shutil.get_terminal_size().columns - 2
formatter = self.formatter_class(prog=self.prog, width=width)

formatter.add_usage(self.usage, self._actions, self._mutually_exclusive_groups)
formatter.add_text(self.description)

if hasattr(self, "_action_groups"):
for action_group in self._action_groups:
formatter.start_section(action_group.title)
formatter.add_text(action_group.description)
formatter.add_arguments(action_group._group_actions)
formatter.end_section()

res : str = "# " + formatter.format_help()

if hasattr(self._subparsers, "_group_actions"):
seen = set()
for grp in self._subparsers._group_actions: # type: ignore
for choice, e in grp.choices.items(): # type: ignore
if e in seen: continue
seen.add(e)
e.formatter_class = self.formatter_class
res += "\n#" + e.format_help(width=width)

for gen in self.additional_sections:
formatter = self.formatter_class(prog=self.prog, width=width)
gen(formatter)
res += "\n" + formatter.format_help()

formatter.add_text(self.epilog)

return res
44 changes: 44 additions & 0 deletions abarms/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# This file is a part of kisstdlib project.
#
# This file can be distributed under the terms of the MIT-style license given
# below or Python Software Foundation License version 2 (PSF-2.0) as published
# by Python Software Foundation.
#
# Copyright (c) 2023 Jan Malakhovski <oxij@oxij.org>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

"""Exceptions with printable descriptions.
"""

import typing as _t

class CatastrophicFailure(Exception):
def __init__(self, what : str, *args : _t.Any) -> None:
super().__init__()
self.description = what % args

def __str__(self) -> str:
return self.description

def elaborate(self, what : str, *args : _t.Any) -> None:
self.description = what % args + ": " + self.description

class Failure(CatastrophicFailure):
pass
Loading

0 comments on commit d584140

Please sign in to comment.