Skip to content

Commit 02ea447

Browse files
committed
Improved txt_terminal_render helper
1 parent c715d28 commit 02ea447

File tree

8 files changed

+83
-27
lines changed

8 files changed

+83
-27
lines changed

src/tinyscript/VERSION.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.30.13
1+
1.30.14

src/tinyscript/features/notify/__init__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,14 @@ def set_notify_items(glob):
2121
"""
2222
a = glob['args']
2323
enabled = getattr(a, a._collisions.get("notify") or "notify", False)
24-
appname = glob.get('__banner__', glob.get('__script__'))
24+
appname = glob.get('__banner__', glob.get('__script__', "my-app"))
2525
timeout = positive_int(glob.get('NOTIFICATION_TIMEOUT', 5), zero=False)
2626
icon_path = folder_exists(glob.get('NOTIFICATION_ICONS_PATH', dirname(__file__)))
2727
level = positive_int(glob.get('NOTIFICATION_LEVEL', logging.SUCCESS))
2828

2929
class NotificationHandler(logging.Handler):
3030
def emit(self, record):
31-
title = "{}[{}]:".format(appname, record.name) if record.name != "main" else appname
31+
title = f"{appname}[{record.name}]:" if record.name != "main" else appname
3232
icon = record.levelname.lower()
3333
ipath = join(icon_path, icon) + ".png"
3434
if isfile(ipath):

src/tinyscript/helpers/text.py

+26-10
Original file line numberDiff line numberDiff line change
@@ -84,15 +84,36 @@ def hexdump(data, width=16, first=0, last=0):
8484
yield "%0.8x: %s %s" % (i, h.ljust(width*2+(width//2-1)), b)
8585

8686

87-
def txt_terminal_render(text, format=None, debug=False, alignleft=True):
87+
def txt_terminal_render(text, format=None, debug=False, alignleft=True, pad=None, syntax=None):
8888
""" This renders input text based on the selected format.
8989
9090
:param format: selected format (one of FORMATS)
9191
"""
92+
from rich.console import Console
93+
from rich.markup import escape
94+
from rich.style import Style
95+
from rich.theme import Theme
96+
c = Console(theme=Theme(styles={n: Style(**s) for n, s in DOCFORMAT_THEME.items()}))
97+
# inner function for capturing rich object's resulting text
98+
def _capture(obj):
99+
c.begin_capture()
100+
c.print(obj)
101+
return c.end_capture()
102+
# inner function for rendering padding and/or syntax
103+
def _beautify(txt, raw=False):
104+
r = txt
105+
if syntax:
106+
from rich.syntax import Syntax
107+
r = _capture(Syntax(r, syntax, padding=pad))
108+
elif pad:
109+
from rich.padding import Padding
110+
r = _capture(Padding(escape(r) if raw else r, pad=pad))
111+
return r
112+
# start processing input text
92113
format = format or DOCFORMAT
93114
__check(format=format)
94115
if format is None:
95-
return text
116+
return _beautify(text, raw=True)
96117
# collect whitespaces in argument lines and line indentations
97118
ARG_REGEX = re.compile(r"((?P<h>\-{1,2})[a-z][a-z0-9_]*(?:\s+[A-Z][A-Z_]*)?"
98119
r"(?:\,\s+(?P=h)\-[a-z][a-z0-9_]*(?:\s+[A-Z][A-Z_]*)?)?)(\s+)(.*)$")
@@ -159,17 +180,12 @@ def txt_terminal_render(text, format=None, debug=False, alignleft=True):
159180
for link in re.findall("(<(.*?)>)", md):
160181
md = md.replace(link[0], "[{0}]({1}{0})".format(link[1], ["", "mailto:"][is_email(link[1])]))
161182
# import only when required to render Markdown in the terminal
162-
from rich.console import Console
163-
from rich.markdown import Heading, Markdown
164-
from rich.style import Style
165-
from rich.theme import Theme
183+
from rich.markdown import Markdown
166184
if alignleft:
185+
from rich.markdown import Heading
167186
from tinyscript import code
168187
code.replace(Heading.__rich_console__, "text.justify = \"center\"", "")
169-
c = Console(theme=Theme(styles={n: Style(**s) for n, s in DOCFORMAT_THEME.items()}))
170-
c.begin_capture()
171-
c.print(Markdown(md))
172-
return c.end_capture()
188+
return _beautify(_capture(Markdown(md)))
173189

174190

175191
def _txt_list(text, format=None, ordered=False):

src/tinyscript/parser.py

+3-6
Original file line numberDiff line numberDiff line change
@@ -177,25 +177,21 @@ def __proxy_to_real_parser(value):
177177
help=gt("remote interacting port"))
178178
if noarg and noargs_action == "interact":
179179
tokens[1:] = [opt.option_strings[-1]]
180-
set_interact_items(glob)
181180
# notification feature, for displaying notifications during the execution
182181
if add['notify']:
183182
opt = i.add_argument("-n", "--notify", action="store_true", suffix="mode", help=gt("notify mode"))
184183
if noarg and noargs_action == "notify":
185184
tokens[1:] = [opt.option_strings[-1]]
186-
set_notify_items(glob)
187185
# progress mode feature, for displaying a progress bar during the execution
188186
if add['progress']:
189187
opt = i.add_argument("-p", "--progress", action="store_true", suffix="mode", help=gt("progress mode"))
190188
if noarg and noargs_action == "progress":
191189
tokens[1:] = [opt.option_strings[-1]]
192-
set_progress_items(glob)
193190
# stepping mode feature, for stepping within the tool during its execution, especially useful for debugging
194191
if add['step']:
195192
opt = i.add_argument("--step", action="store_true", last=True, suffix="mode", help=gt("stepping mode"))
196193
if noarg and noargs_action == "step":
197194
tokens[1:] = [opt.option_strings[-1]]
198-
set_step_items(glob)
199195
# timing mode feature, for measuring time along the execution of the tool
200196
if add['time']:
201197
b = p.add_argument_group("timing arguments")
@@ -272,9 +268,10 @@ def __proxy_to_real_parser(value):
272268
getattr(a, a._collisions.get("relative", "relative"), False),
273269
getattr(a, a._collisions.get("logfile", "logfile"), None),
274270
getattr(a, a._collisions.get("syslog", "syslog"), None))
275-
# 5) configure features that need it (even if not enabled)
271+
# 5) configure features (with glob['args'] computed)
272+
for k in ['interact', 'notify', 'progress', 'step', 'time']:
273+
globals()[f'set_{k}_items'](glob)
276274
set_hotkeys(glob)
277-
set_time_items(glob)
278275
# 6) display a banner if relevant
279276
bf = glob.get('BANNER_FONT', BANNER_FONT)
280277
if add_banner or isinstance(bf, str):

src/tinyscript/preimports/ftools.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ def __init__(self, wrapped, assignents=functools.WRAPPER_ASSIGNMENTS):
1313
self.__wrapped__ = wrapped
1414
for attr in assignents:
1515
setattr(self, attr, getattr(wrapped, attr))
16-
super().__init__()
16+
super().__init__(wrapped)
1717

1818
def __repr__(self):
1919
return repr(self.__wrapped__)

tests/test_helpers_path.py

+9-7
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from tempfile import gettempdir
77
from unittest import TestCase
88

9+
from tinyscript import b
910
from tinyscript.helpers.path import *
1011

1112
from utils import *
@@ -14,7 +15,7 @@
1415
class TestHelpersPath(TestCase):
1516
@classmethod
1617
def setUpClass(cls):
17-
global FILE, FILE2, MODULE, NOTEX, PATH, SPATH, TEST, TPATH1, TPATH2
18+
global FILE, FILE2, MODULE, NOTEX, PATH, SPATH, TEST, TPATH1, TPATH2, TXT
1819
TEST = "test_dir"
1920
PATH = Path(TEST, expand=True, create=True)
2021
SPATH = PATH.joinpath("test")
@@ -36,6 +37,7 @@ def setUpClass(cls):
3637
FILE2 = PATH.joinpath("test2.txt")
3738
SPATH.joinpath("test.txt").touch()
3839
NOTEX = Path("DOES_NOT_EXIST")
40+
TXT = "this is a\n test"
3941

4042
@classmethod
4143
def tearDownClass(cls):
@@ -60,11 +62,11 @@ def test_file_extensions(self):
6062
self.assertIsNone(FILE.remove())
6163
self.assertFalse(FILE.exists())
6264
self.assertIsNone(FILE.touch())
63-
self.assertEqual(FILE.write_text("this is a test"), 14)
64-
self.assertEqual(list(FILE.read_lines()), [b"this is a test"])
65-
self.assertEqual(list(FILE.read_lines(reverse=True)), [b"this is a test"])
66-
self.assertEqual(list(FILE.read_lines(encoding="utf-8")), ["this is a test"])
67-
self.assertEqual(list(FILE.read_lines(encoding="utf-8", reverse=True)), ["this is a test"])
65+
self.assertEqual(FILE.write_text(TXT), 15)
66+
self.assertEqual(list(FILE.read_lines()), list(map(b, TXT.split("\n"))))
67+
self.assertEqual(list(FILE.read_lines(reverse=True)), list(map(b, TXT.split("\n")))[::-1])
68+
self.assertEqual(list(FILE.read_lines(encoding="utf-8")), TXT.split("\n"))
69+
self.assertEqual(list(FILE.read_lines(encoding="utf-8", reverse=True)), TXT.split("\n")[::-1])
6870
self.assertEqual(FILE.choice(), FILE)
6971
self.assertEqual(FILE.generate(), FILE)
7072
self.assertRaises(TypeError, FILE.append_text, 0)
@@ -81,7 +83,7 @@ def test_folder_extensions(self):
8183
self.assertEqual(str(PATH), str(Path(TEST).absolute()))
8284
self.assertEqual(Path(TEST).child, Path("."))
8385
self.assertEqual(SPATH.size, 4096)
84-
self.assertEqual(PATH.size, [4096 + 4096 + 14, 8213][WINDOWS]) # PATH + SPATH + FILE
86+
self.assertEqual(PATH.size, [4096 + 4096 + 15, 8213][WINDOWS]) # PATH + SPATH + FILE
8587
self.assertTrue(PATH.choice(".txt", ".py", ".other").is_samepath(FILE))
8688
self.assertIsInstance(PATH.generate(), Path)
8789
self.assertEqual(list(PATH.iterfiles()), [FILE.absolute()])

tests/test_helpers_text.py

+3
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,9 @@ def test_text_rendering(self):
132132
self.assertRaises(ValueError, txt2url, TXT)
133133
for help, fmt in zip([HTML, MD, RST, TEXTILE], ["html", "md", "rst", "textile"]):
134134
self.assertIsNotNone(txt_terminal_render(help, fmt))
135+
self.assertIsNotNone(txt_terminal_render(TXT, pad=(2,2)))
136+
self.assertIsNotNone(txt_terminal_render("test:\n string: ok\n", pad=(2,2), syntax="yaml"))
137+
self.assertIsNotNone(txt_terminal_render("test:\n string: ok\n", format="rst", pad=(2,2), syntax="yaml"))
135138

136139
def test_text_utils(self):
137140
self.assertEqual(list(hexdump(URL)), ["00000000: 6874 7470 733a 2f2f 6a6f 686e 3a64 6f65 https://john:doe",

tests/test_preimports_ftools.py

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# -*- coding: UTF-8 -*-
2+
"""Preimports code manipulation assets' tests.
3+
4+
"""
5+
from tinyscript.preimports import functools
6+
7+
from utils import *
8+
9+
10+
class TestPreimportsCode(TestCase):
11+
def test_class_wrapper(self):
12+
# source: https://stackoverflow.com/questions/6394511/python-functools-wraps-equivalent-for-classes
13+
@functools.wraps_cls
14+
class Memoized:
15+
def __init__(self, func):
16+
super().__init__()
17+
self.__func, self.__cache = func, {}
18+
def __call__(self, *args):
19+
try:
20+
return self.__cache[args]
21+
except KeyError:
22+
self.__cache[args] = v = self.__func(*args)
23+
return v
24+
except TypeError:
25+
return self.__func(*args)
26+
def __get__(self, obj, objtype):
27+
return functools.partial(self.__call__, obj)
28+
29+
@Memoized
30+
def fibonacci(n):
31+
"""fibonacci docstring"""
32+
if n in (0, 1):
33+
return n
34+
return fibonacci(n-1) + fibonacci(n-2)
35+
36+
self.assertEqual(fibonacci.__doc__, "fibonacci docstring")
37+
self.assertIn("wrapper.<locals>.fibonacci", repr(fibonacci))
38+

0 commit comments

Comments
 (0)