From 4deffc5b1bc54ea04c3c9ecb08a6860f5514f6ba Mon Sep 17 00:00:00 2001 From: Thomas Cherry Date: Fri, 23 Feb 2024 07:38:47 -0500 Subject: [PATCH 1/7] adding lots of colors, network, and other functions --- .gitignore | 3 +- example.py | 62 +++++++++++-- run.sh | 16 +++- scripts/show.py | 20 ++++ setup.py | 2 +- shellwrap/color.py | 172 ++++++++++++++++++++++++++++++++--- shellwrap/datetools.py | 19 +++- shellwrap/file.py | 35 +++++-- shellwrap/net.py | 24 +++++ shellwrap/unix.py | 20 ++-- shellwrap/util.py | 12 +++ test/__init__.py | 0 test/shellwrap/__init__.py | 0 test/shellwrap/test_color.py | 53 +++++++++++ test/shellwrap/test_time.py | 43 +++++++++ test/shellwrap/test_util.py | 21 +++++ 16 files changed, 454 insertions(+), 48 deletions(-) create mode 100644 scripts/show.py create mode 100644 shellwrap/net.py create mode 100644 shellwrap/util.py create mode 100644 test/__init__.py create mode 100644 test/shellwrap/__init__.py create mode 100644 test/shellwrap/test_color.py create mode 100644 test/shellwrap/test_time.py create mode 100644 test/shellwrap/test_util.py diff --git a/.gitignore b/.gitignore index d29f5d5..cbe462a 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ lib #application -.python-*-history \ No newline at end of file +.python-*-history +__pycache__ diff --git a/example.py b/example.py index 20c9ebf..5e275b7 100644 --- a/example.py +++ b/example.py @@ -1,7 +1,8 @@ from shellwrap import color +from shellwrap import file from shellwrap import interactive from shellwrap import unix -from shellwrap import file +from shellwrap import net import argparse # ###################################### @@ -57,6 +58,37 @@ def process_actions(action=None, env:dict=None): some_task(action, env) return True +@color.print_red +@color.print_green +def log_this(item): + return item + +@color.bold +@color.green +def color_this(foo): + return foo + +@color.bold +@color.black_green +@color.underline +def blueit(text): + return text + +@color.green +@color.underline +def headline(text): + return text + +@unix.str_to_json +@unix.wrap_call +@unix.wrap_curl +def curl_test(url, what, proj): + return [url + what + proj, "-H", "Client-Id: test"] + +@net.str_to_json +def other_test(url): + return net.read(url)["text"] + # ###################################### #mark - Main @@ -76,15 +108,33 @@ def main(): if args.user: interactive.user_commands(handler=process_actions, environment=env, g=globals()) - + print(headline("Unix tests")) presult = unix.pipe(['echo', 'one', 'two', 'three'], ['wc', '-m']) - print(presult) - + print(f"preselt={presult}") print(unix.ccurl('-H', 'Header: Value', 'https://github.com/jceaser/shellwrap.git')) - color.cprint(color.tcode.red, "ending", env) + print(headline("\nUnix Decorator tests")) + print(curl_test('http://thomascherry.name/', + '/cgi-bin/go.cgi', + '?user=thomas&name=main&group=public')) + + #color.cprint(color.tcode.red, "ending", env) + + print(headline("\nFile tests")) + print(file.read('.editorconfig')) + + print(headline("\nDecorator tests")) + print("Normal: %s" % log_this('hi, this is the decorator')) + print(color_this("some text")) + print(blueit("Make this blue and bold.")) + + print(headline("\nURL tests")) + + url = 'http://thomascherry.name/cgi-bin/go.cgi?user=thomas&name=main&group=public' + print(net.read(url)["text"]) - print(file.read_file('.editorconfig')) + print(other_test(url)) + #print(net.rread(url).text) if __name__ == "__main__": main() diff --git a/run.sh b/run.sh index 9ccf6aa..be57c82 100755 --- a/run.sh +++ b/run.sh @@ -1,5 +1,10 @@ #!/bin/bash +run_init() +{ + pip3 install build +} + run_clean() { rm -rf build dist shellwrap.egg-info @@ -7,17 +12,19 @@ run_clean() run_build() { - python3 setup.py sdist bdist_wheel + #python3 setup.py sdist bdist_wheel + python3 -m build # the new way } run_install() { - pip3 install shellwrap-0.0.1-py3-none-any.whl + pip3 install dist/shellwrap-0.0.2-py3-none-any.whl } run_test() { - python3 -m unittest discover -s ./ -p '*test.py' + #python3 -m unittest discover -s ./test -p 'test*.py' + python3 -m unittest discover } run_lint() @@ -31,12 +38,13 @@ run_lint() } # Process the command line arguments -while getopts "hcbitlu" opt +while getopts "hcbIitlu" opt do case ${opt} in h) run_help ;; c) run_clean ;; b) run_build ;; + I) run_init ;; i) run_install ;; t) run_test ;; l) run_lint ;; diff --git a/scripts/show.py b/scripts/show.py new file mode 100644 index 0000000..a7398ec --- /dev/null +++ b/scripts/show.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 + +for i in range(30, 37 + 1): + print("\033[%dm%d\t\t\033[%dm%d" % (i, i, i + 60, i + 60)) + +print("\033[39m\\033[49m - Reset color") +print("\\033[2K - Clear Line") +print("\\033[;H or \\033[;f - Put the cursor at line L and column C.") +print("\\033[A - Move the cursor up N lines") +print("\\033[B - Move the cursor down N lines") +print("\\033[C - Move the cursor forward N columns") +print("\\033[D - Move the cursor backward N columns\n") +print("\\033[2J - Clear the screen, move to (0,0)") +print("\\033[K - Erase to end of line") +print("\\033[s - Save cursor position") +print("\\033[u - Restore cursor position\n") +print("\\033[4m - Underline on") +print("\\033[24m - Underline off\n") +print("\\033[1m - Bold on") +print("\\033[21m - Bold off") diff --git a/setup.py b/setup.py index a249599..92e8cb7 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ from setuptools import setup, find_packages -VERSION = '0.0.1' +VERSION = '0.0.3' DESCRIPTION = 'Scripting Tools' LONG_DESCRIPTION = 'A set of tools to make it easy to write unix scripts' diff --git a/shellwrap/color.py b/shellwrap/color.py index a4bbb8d..81e245f 100755 --- a/shellwrap/color.py +++ b/shellwrap/color.py @@ -8,18 +8,11 @@ # template modified 2021-09-02 -""" template script for future scripts """ +""" Terminal codes for drawing colors and styles on text """ #mark - Imports from enum import Enum #Creating Enums -#import argparse #command line parsing -#import code #interactive shell -#import os #file handling -#import re #filter in man() -#import readline #interactive shell -#import subprocess #calling unix commands -#import sys #exiting # ############################################################################## #mark - Utility functions - Leave these alone @@ -50,15 +43,80 @@ def __init__(self, data): def __getattr__ (self, attr): """ Allow items to be access with dot notation. """ - return self.get(attr, '\033[0m') - -tcode = TerminalCode({'red':'\033[0;31m', - 'green': '\033[0;32m', - 'yellow': '\033[0;33m', - 'blue': '\033[0;34m', - 'white': '\033[0;37m', + color_code = self.get(attr, '\033[0m') + if color_code.startswith('\033'): + return color_code + return f'\033[0;{color_code}m' + + def full(self, fg, bg): + fgc = self.get(fg, "0") + bgc = self.get(bg, "0") + return f'\033[{fgc};{bgc}m' + def raw(self, code): + return self.get(code, '0') + +#https://stackoverflow.com/questions/4842424/list-of-ansi-color-escape-sequences + +"""Colors for the following schemas: +* RGB - Red, Green, Blue +* RYG - Red, Yellow, Green +* CMYK - Cyan, Magenta, Yellow, Black +""" +tcode = TerminalCode({'none': '\033[0m', 'bold': '\033[1m', + 'faint': '\033[2m', + 'italix': '\033[3m', #not well supported 'underline': '\033[4m', + 'slow': '\033[5m', + 'fast': '\033[6m', #not well supported + 'inverse': '\033[7m', + 'hide': '\033[8m', + + 'off_bold': '\033[21m', + 'off_faint': '\033[22m', + 'off_italix': '\033[23m', #not well supported + 'off_underline': '\033[24m', + 'off_slow': '\033[25m', + 'off_fast': '\033[26m', #not well supported + 'off_inverse': '\033[27m', + 'off_hide': '\033[28m', + + 'black': '30', + 'red':'31', + 'green': '32', + 'yellow': '33', + 'blue': '34', + 'magenta': '35', + 'cyan': '36', + 'white': '37', + + 'backblack':'40', + 'backred':'41', + 'backgreen': '42', + 'backyellow': '43', + 'backblue': '44', + 'backmagenta': '45', + 'backcyan': '46', + 'backwhite': '47', + + 'bright_black' : '90', + 'bright_red' : '91', + 'bright_green' : '92', + 'bright_yellow': '93', + 'bright_blue' : '94', + 'bright_magenta' : '95', + 'bright_cyan' : '96', + 'bright_white' : '97', + + 'back_bright_black': '100', + 'back_bright_red': '101', + 'back_bright_green': '102', + 'back_bright_yellow': '103', + 'back_bright_blue': '104', + 'back_bright_magenta': '105', + 'back_bright_cyan': '106', + 'back_bright_white': '107', + #'nc': '\033[0m', # No Color # now define duplicates VMode.FATAL: '->red', @@ -98,6 +156,60 @@ def cprint(color:str, content:str, environment:dict=None, verbose:VMode=VMode.NO else: print ("{}".format(content)) +def encoder(color, content): + return "{}{}{}".format(color, content, tcode.nc) + +# decorators + +def black(foo): + return lambda c : encoder(tcode.black, foo(c)) + +def red(foo): + return lambda c : encoder(tcode.red, foo(c)) + +def black_green(foo): + color = tcode.full("black", "backgreen") + return lambda c : encoder(color, foo(c)) + +def green(foo): + return lambda c : encoder(tcode.green, foo(c)) + +def blue(foo): + return lambda c : encoder(tcode.blue, foo(c)) + +def bold(foo): + return lambda c : encoder(tcode.bold, foo(c)) + +def underline(foo): + return lambda c : encoder(tcode.underline, foo(c)) + +def link(link, text): + return '\033]8;;{}\a{}\033]8;;\a'.format(link, text) + +def print_red(function): + def inner(*args): + ret = function(*args) + cprint(tcode.red, ret) + return ret + return inner + +def print_green(function): + def inner(*args): + ret = function(*args) + cprint(tcode.green, ret) + return ret + return inner + +def print_blue(function): + def inner(*args): + ret = function(args) + cprint(tcode.blue, ret) + return ret + return inner + +def command(code): + print (code, end='') + def vcprint(verbose:VMode, content:str, environment:dict=None): """ Verbose Color Printing: @@ -110,3 +222,33 @@ def vcprint(verbose:VMode, content:str, environment:dict=None): """ cprint (tcode.get(verbose, tcode.white), content, environment, verbose) +def cmd_clear_screen(): + print('\033[2J', end='') + +def cmd_clear_line(): + print('\033[2K', end='') + +def cmd_clear_end_line(): + print('\033[K', end='') + +def cmd_save_position(): + print('\033[s', end='') + +def cmd_restore_position(): + print('\033[u', end='') + +def cmd_move(num, direction): + direction = direction.upper() + match direction: + case "UP": + direction = "A" + case "DOWN": + direction = "B" + case "RIGHT": + direction = "C" + case "LEFT": + direction = "D" + print('\033[{}{}'.format(num, direction), end='') + +def cmd_move_to(line, column): + print('\033[{};{}H'.format(line, column), end='') diff --git a/shellwrap/datetools.py b/shellwrap/datetools.py index 9b8f733..202624f 100644 --- a/shellwrap/datetools.py +++ b/shellwrap/datetools.py @@ -6,19 +6,32 @@ # template modified 2021-09-02 -""" template script for future scripts """ +""" Functions for getting Date and Time values """ #mark - Imports import datetime +import time # ###################################### #mark date functions +def now_internal(): + """ In testing, this one function can be Mocked to stop time """ + return datetime.datetime.now() + def now(): """ Return a string with the current date and time formated in ISO""" - return datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S") + return now_internal().strftime("%Y-%m-%dT%H:%M:%S") def today(): """ Return a string with the current date formated in ISO""" - return datetime.datetime.now().strftime("%Y-%m-%d") + return now_internal().strftime("%Y-%m-%d") + +def unix(): + """ unix time stamp """ + return int(time.time()) + +def unix_difference(start): + """ different from a unix start time stamp and now """ + return int(time.time()) - start diff --git a/shellwrap/file.py b/shellwrap/file.py index 29d7a4c..4ecb07d 100755 --- a/shellwrap/file.py +++ b/shellwrap/file.py @@ -8,23 +8,28 @@ # template modified 2021-09-02 -""" template script for future scripts """ +""" Functions for managing files, cRUD """ #mark - Imports -#from enum import Enum #Creating Enums -#import argparse #command line parsing -#import code #interactive shell import os #file handling -#import re #filter in man() -#import readline #interactive shell -#import subprocess #calling unix commands -#import sys #exiting # ###################################### #mark File Tools -def read_file(path:str=None): +def exists(path:str=None): + """ + Tests if a file or directory exists + Parameters: + path (string): full path to file or directory to test for + Returns: + True if path exists, false otherwise + """ + path=os.path.realpath(__file__[:-2]+"txt") if path is None else os.path.expanduser(path) + #return os.path.isfile(path) + return os.path.exists(path) + +def read(path:str=None): """ Read and return the contents of a file Parameters: @@ -40,7 +45,7 @@ def read_file(path:str=None): file.close() return text -def write_file(text:str, path:str=None): +def write(text:str, path:str=None): """ Write (creating if need be) file and set it's content Parameters: @@ -51,3 +56,13 @@ def write_file(text:str, path:str=None): with open(path, "w+") as cache: cache.write(text) cache.close() + +def delete(path:str=None): + """ + Delete file and set it's content + Parameters: + path (string): path to file to write + text (string): content for file + """ + path=os.path.realpath(__file__[:-2]+"txt") if path is None else os.path.expanduser(path) + os.remove(path) diff --git a/shellwrap/net.py b/shellwrap/net.py new file mode 100644 index 0000000..82b6b96 --- /dev/null +++ b/shellwrap/net.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 + +""" Functions for making HTTP requests """ + +#mark - Imports + +import json +import urllib.request +#import requests + +def str_to_json(foo): + return lambda *flags : json.loads(foo(*flags)) + +def read(url): + ret = "" + try: + resp = urllib.request.urlopen(url) + ret = resp.read() + except urllib.error.HTTPError as e: + ret = 'HTTPError: {} - {}'.format(e.code, e.reason) + ret = {'status': e.code, + 'headers': e.headers.items(), + 'text': e.read().decode()} + return ret diff --git a/shellwrap/unix.py b/shellwrap/unix.py index 3a510a0..9d66733 100755 --- a/shellwrap/unix.py +++ b/shellwrap/unix.py @@ -8,18 +8,12 @@ # template modified 2021-09-02 -""" template script for future scripts """ +""" Functions to make calling Unix apps easy """ #mark - Imports -#from enum import Enum #Creating Enums -#import argparse #command line parsing -#import code #interactive shell -#import os #file handling -#import re #filter in man() -#import readline #interactive shell +import json import subprocess #calling unix commands -#import sys #exiting # ###################################### #mark calling unix commands @@ -47,6 +41,16 @@ def pipe(*cmd_lists): #print (f"return:{pipe_result.returncode}") return pipe_result.stdout.decode('utf-8') +def wrap_curl(foo): + """Wrap a list of parameters""" + return lambda *flags : curl(*foo(*flags)) + +def wrap_call(foo): + return lambda *flags : call(*foo(*flags)) + +def str_to_json(foo): + return lambda *flags : json.loads(foo(*flags)) + def curl(*flags): """ Build a curl command list which can be passed to pipe() or call() """ cmd = ["curl", "-s", '-A', 'tcherry-script'] diff --git a/shellwrap/util.py b/shellwrap/util.py new file mode 100644 index 0000000..8d46e61 --- /dev/null +++ b/shellwrap/util.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python3 + +""" General functions of no specific category """ + +#mark - Imports + +import json + +#mark - functions + +def str_to_json(foo): + return lambda *flags : json.loads(foo(*flags)) diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/shellwrap/__init__.py b/test/shellwrap/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/shellwrap/test_color.py b/test/shellwrap/test_color.py new file mode 100644 index 0000000..509c281 --- /dev/null +++ b/test/shellwrap/test_color.py @@ -0,0 +1,53 @@ +#from unittest.mock import Mock +#from unittest.mock import patch +import unittest + +#import urllib.error as urlerr + +#import test.util as tutil + +import shellwrap.color as color + +class TestColor(unittest.TestCase): + """Test suit for Search API""" + + def pattern_maker_color(self, color, content): + return '\033[0;{}m{}\033[0m'.format(color, content) + def pattern_maker_style(self, color, content): + return '{}{}\033[0m'.format(color, content) + + # ********************************************************************** + # Tests + + def test_tcode(self): + def are(code_name, iscolor=True): + expected_code = color.tcode[code_name] + expected_esc = expected_code # assume style, not color + if iscolor: + expected_esc = '\x1b[0;{}m'.format(expected_code) + expected_full = '\x1b[{};{}m'.format(expected_code, expected_code) + self.assertEqual(expected_esc, color.tcode.__getattr__(code_name), "[] test") + self.assertEqual(expected_code, color.tcode.raw(code_name), "raw() test") + self.assertEqual(expected_full, color.tcode.full(code_name, code_name), "Full() test") + + are("red") + are("green") + are("backgreen") + are("bright_yellow") + are("underline", False) + are("off_inverse", False) + + def test_red(self): + src = lambda a : a + self.assertEqual("\x1b[0;31mtest\x1b[0m", color.red(src)("test")) + self.assertEqual(self.pattern_maker_color(color.tcode.raw('red'), "test"), + color.red(src)("test")) + + def test_link(self): + self.assertEqual('\x1b]8;;http://example.org\x07Example\x1b]8;;\x07', + color.link("http://example.org", "Example")) + + def test_other(self): + expected = self.pattern_maker_style(color.tcode.raw('hide'), "test") + actual = color.encoder(color.tcode.hide, "test") + self.assertEqual(expected, actual) diff --git a/test/shellwrap/test_time.py b/test/shellwrap/test_time.py new file mode 100644 index 0000000..7bb0698 --- /dev/null +++ b/test/shellwrap/test_time.py @@ -0,0 +1,43 @@ +from unittest.mock import Mock +from unittest.mock import patch +from unittest.mock import MagicMock + +import unittest +import datetime +import shellwrap.datetools as dt + +class TestTime(unittest.TestCase): + """Test suit for date-tools API""" + + def fixed_date_2024_02_14(self): + return 1707930000 + + def fixed_datetime(self): + return datetime.datetime(2024, 2, 14, 12, 0, 0, 0) + + # ********************************************************************** + # Tests + + def test_now(self): + dt.now_internal = MagicMock(return_value=self.fixed_datetime()) + self.assertEqual("2024-02-14T12:00:00", dt.now(), "now is broken") + + def test_today(self): + dt.now_internal = MagicMock(return_value=self.fixed_datetime()) + self.assertEqual("2024-02-14", dt.today(), "today is broken") + + @patch('time.time') + def test_unix(self, mocked_time_time): + mocked_time_time.return_value = self.fixed_date_2024_02_14() + expected = self.fixed_date_2024_02_14() + actual = dt.unix() + self.assertEqual(expected, value) + + @patch('time.time') + def test_unix(self, mocked_time_time): + mocked_time_time.return_value = self.fixed_date_2024_02_14() + + expected = 1000 + start = self.fixed_date_2024_02_14() - expected + actual = dt.unix_difference(start) + self.assertEqual(expected, actual, "date math is wrong") diff --git a/test/shellwrap/test_util.py b/test/shellwrap/test_util.py new file mode 100644 index 0000000..38e5eec --- /dev/null +++ b/test/shellwrap/test_util.py @@ -0,0 +1,21 @@ +#from unittest.mock import Mock +#from unittest.mock import patch +import unittest + +#import urllib.error as urlerr +#import test.util as tutil + +import shellwrap.util as util + +class TestColor(unittest.TestCase): + + def test_json(self): + """ Setup a function to cgall str_to_json on """ + @util.str_to_json + def foo(data): + """This function could load a file and return a JSON string""" + return "{\"Data\": \"%s\"}" % data + + expected = {'Data': 'test'} + actual = foo("test") + self.assertEqual(expected, actual) From 9b38c0c9baa0a5fedbcafe29099910a827b44848 Mon Sep 17 00:00:00 2001 From: Thomas Cherry Date: Fri, 23 Feb 2024 07:39:25 -0500 Subject: [PATCH 2/7] moving file to folder --- example.py => scripts/example.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename example.py => scripts/example.py (100%) diff --git a/example.py b/scripts/example.py similarity index 100% rename from example.py rename to scripts/example.py From f45965a3b523cf6f3cb5e1c65ea8c8424110552f Mon Sep 17 00:00:00 2001 From: Thomas Cherry Date: Fri, 15 Aug 2025 17:05:40 -0400 Subject: [PATCH 3/7] Cleaning up color, simplifing the code --- run.sh | 19 +++- scripts/example.py | 42 ++++--- shellwrap/color.py | 266 +++++++++++++++++++++++++-------------------- shellwrap/file.py | 16 ++- 4 files changed, 210 insertions(+), 133 deletions(-) diff --git a/run.sh b/run.sh index be57c82..5095a75 100755 --- a/run.sh +++ b/run.sh @@ -18,7 +18,7 @@ run_build() run_install() { - pip3 install dist/shellwrap-0.0.2-py3-none-any.whl + pip3 install dist/shellwrap-0.0.3-py3-none-any.whl } run_test() @@ -37,6 +37,21 @@ run_lint() # --ignore-patterns=".*\.md,.*\.sh,.*\.html,pylintrc,LICENSE,build,dist,tags,shellwrap.egg-info" } +run_help() +{ + fmt="%4s %s\\n" + printf "$fmt" flag meaning + printf "$fmt" -h help + printf "$fmt" -c clean + printf "$fmt" -b build + printf "$fmt" -I init + printf "$fmt" -i install + printf "$fmt" -t test + printf "$fmt" -l lint + printf "$fmt" -u uninstall + printf "$fmt" -v "set version" +} + # Process the command line arguments while getopts "hcbIitlu" opt do @@ -51,7 +66,7 @@ do u) pip3 uninstall shellwrap ;; v) set_version $OPTARG ;; - *) help ; exit ;; + *) run_help ; exit ;; esac done diff --git a/scripts/example.py b/scripts/example.py index 5e275b7..036e336 100644 --- a/scripts/example.py +++ b/scripts/example.py @@ -1,5 +1,6 @@ from shellwrap import color from shellwrap import file +from shellwrap import datetools from shellwrap import interactive from shellwrap import unix from shellwrap import net @@ -27,9 +28,9 @@ def initialize_enviornment(args): if args.color_off: environment["color"] = False if args.verbose: - environment["verbose"]=VMode.WARN + environment["verbose"]=1 if args.very_verbose: - environment["verbose"]=VMode.INFO + environment["verbose"]=3 return environment # ###################################### @@ -101,7 +102,10 @@ def main(): world['do_action'] = do_action world['process_actions'] = process_actions - color.cprint(color.tcode.green, "Starting script", env) + print("This will go away") + color.cmd_clear_screen() + + color.cprint(color.tcode.green, "Starting script") if args.interactive: interactive.interactive(env, g=globals()) @@ -109,14 +113,14 @@ def main(): interactive.user_commands(handler=process_actions, environment=env, g=globals()) print(headline("Unix tests")) - presult = unix.pipe(['echo', 'one', 'two', 'three'], ['wc', '-m']) - print(f"preselt={presult}") + result = unix.pipe(['echo', 'one', 'two', 'three'], ['wc', '-m']) + print(f"pipe result={result}.") print(unix.ccurl('-H', 'Header: Value', 'https://github.com/jceaser/shellwrap.git')) - print(headline("\nUnix Decorator tests")) - print(curl_test('http://thomascherry.name/', - '/cgi-bin/go.cgi', - '?user=thomas&name=main&group=public')) + #print(headline("\nUnix Decorator tests")) + #print(curl_test('http://thomascherry.name/', + # '/cgi-bin/go.cgi', + # '?user=thomas&name=main&group=public')) #color.cprint(color.tcode.red, "ending", env) @@ -127,14 +131,24 @@ def main(): print("Normal: %s" % log_this('hi, this is the decorator')) print(color_this("some text")) print(blueit("Make this blue and bold.")) + print(color.link("https://apple.com/", "apple.com")) - print(headline("\nURL tests")) - - url = 'http://thomascherry.name/cgi-bin/go.cgi?user=thomas&name=main&group=public' - print(net.read(url)["text"]) + #print(headline("\nURL tests")) + #url = 'http://thomascherry.name/cgi-bin/go.cgi?user=thomas&name=main&group=public' + #print(net.read(url)["text"]) - print(other_test(url)) + #print(other_test(url)) #print(net.rread(url).text) + print(headline("Colorize tests")) + print(color.colorize(":rocket: This is my :red:red:end: text and this is my :green:green:end: text.")) + + color.cprint([color.tcode.green, color.tcode.underline], "my list text") + + print(' ; '.join(f"{i}={color.emoji[i]}" for i in color.emoji)) + + print(headline("Date tests")) + print(datetools.now()) + if __name__ == "__main__": main() diff --git a/shellwrap/color.py b/shellwrap/color.py index 81e245f..351bf62 100755 --- a/shellwrap/color.py +++ b/shellwrap/color.py @@ -13,19 +13,11 @@ #mark - Imports from enum import Enum #Creating Enums +import re # ############################################################################## #mark - Utility functions - Leave these alone -class VMode(Enum): - """ Verbose Mode Enums, higher values print less""" - FATAL = -8 # always print - ERROR = 0 - NORMAL = 1 - WARN = 2 - INFO = 4 - DEBUG = 8 - class TerminalCode(dict): """ A custom dictionary for storing terminal color codes that allows for value @@ -39,21 +31,21 @@ def __init__(self, data): """ super().__init__() for key, value in data.items(): - self[key] = self[value[2:]] if value.startswith('->') else value + self[key] = value def __getattr__ (self, attr): """ Allow items to be access with dot notation. """ - color_code = self.get(attr, '\033[0m') - if color_code.startswith('\033'): - return color_code - return f'\033[0;{color_code}m' + color_code = self.get(attr.replace('_', '-'), '\033[0m') + return color_code + + def escape(self, code) -> str: + """ Take a TerminalCode and wrap it in terminal escape code. """ + return f'\033[{code}m' def full(self, fg, bg): fgc = self.get(fg, "0") bgc = self.get(bg, "0") return f'\033[{fgc};{bgc}m' - def raw(self, code): - return self.get(code, '0') #https://stackoverflow.com/questions/4842424/list-of-ansi-color-escape-sequences @@ -62,24 +54,34 @@ def raw(self, code): * RYG - Red, Yellow, Green * CMYK - Cyan, Magenta, Yellow, Black """ -tcode = TerminalCode({'none': '\033[0m', - 'bold': '\033[1m', - 'faint': '\033[2m', - 'italix': '\033[3m', #not well supported - 'underline': '\033[4m', - 'slow': '\033[5m', - 'fast': '\033[6m', #not well supported - 'inverse': '\033[7m', - 'hide': '\033[8m', - - 'off_bold': '\033[21m', - 'off_faint': '\033[22m', - 'off_italix': '\033[23m', #not well supported - 'off_underline': '\033[24m', - 'off_slow': '\033[25m', - 'off_fast': '\033[26m', #not well supported - 'off_inverse': '\033[27m', - 'off_hide': '\033[28m', +tcode = TerminalCode({'none': '0', + 'bold': '1', + 'faint': '2', + 'italix': '3', #not well supported + 'underline': '4', + 'slow': '5', + 'fast': '6', #not well supported + 'inverse': '7', + 'hide': '8', + + 'nc': '\033[0m', + 'on-bold': '\033[1m', + 'on-faint': '\033[2m', + 'on-italix': '\033[3m', #not well supported + 'on-underline': '\033[4m', + 'on-slow': '\033[5m', + 'on-fast': '\033[6m', #not well supported + 'on-inverse': '\033[7m', + 'on-hide': '\033[8m', + + 'off-bold': '\033[21m', + 'off-faint': '\033[22m', + 'off-italix': '\033[23m', #not well supported + 'off-underline': '\033[24m', + 'off-slow': '\033[25m', + 'off-fast': '\033[26m', #not well supported + 'off-inverse': '\033[27m', + 'off-hide': '\033[28m', 'black': '30', 'red':'31', @@ -90,48 +92,58 @@ def raw(self, code): 'cyan': '36', 'white': '37', - 'backblack':'40', - 'backred':'41', - 'backgreen': '42', - 'backyellow': '43', - 'backblue': '44', - 'backmagenta': '45', - 'backcyan': '46', - 'backwhite': '47', - - 'bright_black' : '90', - 'bright_red' : '91', - 'bright_green' : '92', - 'bright_yellow': '93', - 'bright_blue' : '94', - 'bright_magenta' : '95', - 'bright_cyan' : '96', - 'bright_white' : '97', - - 'back_bright_black': '100', - 'back_bright_red': '101', - 'back_bright_green': '102', - 'back_bright_yellow': '103', - 'back_bright_blue': '104', - 'back_bright_magenta': '105', - 'back_bright_cyan': '106', - 'back_bright_white': '107', - - #'nc': '\033[0m', # No Color - # now define duplicates - VMode.FATAL: '->red', - VMode.ERROR: '->red', - VMode.NORMAL: '->white', - VMode.WARN: '->yellow', - VMode.INFO: '->blue', - VMode.DEBUG: '->underline' + 'back-black':'40', + 'back-red':'41', + 'back-green': '42', + 'back-yellow': '43', + 'back-blue': '44', + 'back-magenta': '45', + 'back-cyan': '46', + 'back-white': '47', + + 'bright-black' : '90', + 'bright-red' : '91', + 'bright-green' : '92', + 'bright-yellow': '93', + 'bright-blue' : '94', + 'bright-magenta' : '95', + 'bright-cyan' : '96', + 'bright-white' : '97', + + 'back-bright-black': '100', + 'back-bright-red': '101', + 'back-bright-green': '102', + 'back-bright-yellow': '103', + 'back-bright-blue': '104', + 'back-bright-magenta': '105', + 'back-bright-cyan': '106', + 'back-bright-white': '107', + + 'clear-screen': '\033[2J', + 'clear-line': '\033[2K', + 'clear-to-end': '\033[K', + 'position-save': '\033[s', + 'position-restore': '\033[u' }) -def is_verbose(environment, verbose): - """ True if the environment is in verbose mode; Can print in verbose mode """ - return verbose.value <= environment.get("verbose", VMode.WARN).value - -def cprint(color:str, content:str, environment:dict=None, verbose:VMode=VMode.NORMAL): +#emoji: TerminalCode = TerminalCode({':rocket:': '๐Ÿš€'}) +emoji: dict[str,str] = {'none': '', + 'airship':'๐ƒŒ', + 'bomb': '๐Ÿ’ฃ', + 'chequered': '๐Ÿ', + 'degree': 'ยฐ', + 'firecracker': '๐Ÿงจ', + 'flag': '๐Ÿณ๏ธ', + 'pirate': '๐Ÿดโ€โ˜ ๏ธ', + 'platform': '๐™', + 'post': '๐Ÿšฉ', + 'rocket': '๐Ÿš€', + 'sub': '๐ƒ', + 'unicorn': '๐Ÿฆ„', + 'warn': 'โš ๏ธ' +} + +def cprint(color: str | list[str], content:str): """ Color Print, print out text in the requested color, but respect the verbose and color modes of the environment variable. @@ -148,18 +160,53 @@ def cprint(color:str, content:str, environment:dict=None, verbose:VMode=VMode.NO Return: None """ - if environment is None: - environment = {} - if is_verbose(environment, verbose): - if environment.get("color", True): - print ("{}{}{}".format(color, content, tcode.nc)) - else: - print ("{}".format(content)) -def encoder(color, content): - return "{}{}{}".format(color, content, tcode.nc) + if isinstance(color, list): + colors = ";".join(color) + print(f"\033[{colors}m{content}\033[0m") + else: + print ("{}{}{}".format(tcode.escape(color), content, tcode.nc)) + +def encoder(color: str, content: str): + """ + Take a color code and text content, either with or without escape code, and return a printable + escape sequence. + """ + if color.startswith('\033['): + return "{}{}{}".format(color, content, tcode.nc) + else: + return f"\033[{color}m{content}\033[0m" + +def link(link:str, text:str): + """ Take an html link and link text and return a printable escape sequence. """ + return '\033]8;;{}\a{}\033]8;;\a'.format(link, text) + +def colorize(text: str): + """ + Parse the input text and apply color formatting based on the tags. + + Tags should be in the format :color: where color is the name of the color. + The :end tag is used to reset the color. + + Example: + ":red:Hello :green:World:end" will color "Hello" in red and "World" in green. + """ + # Find all color tags in the text + tags = re.findall(r':(\w+):', text) + + # Replace each tag with its corresponding ANSI color code + for tag in tags: + if tag in tcode: + text = text.replace(f':{tag}:', f'\033[{tcode[tag]}m') + elif tag in emoji: + text = text.replace(f':{tag}:', emoji[tag]) + else: + # If the tag is not recognized, leave it as is + pass + return text -# decorators +# ############################################################################## +# decorators - experimental def black(foo): return lambda c : encoder(tcode.black, foo(c)) @@ -183,9 +230,6 @@ def bold(foo): def underline(foo): return lambda c : encoder(tcode.underline, foo(c)) -def link(link, text): - return '\033]8;;{}\a{}\033]8;;\a'.format(link, text) - def print_red(function): def inner(*args): ret = function(*args) @@ -207,48 +251,40 @@ def inner(*args): return ret return inner -def command(code): - print (code, end='') - -def vcprint(verbose:VMode, content:str, environment:dict=None): - """ - Verbose Color Printing: - Decided what color to print out for the user, based on verbose level +# ############################################################################## +# terminal commands - Parameters: - * verbose - level for print context - * content - text to print out - * environment - application settings - """ - cprint (tcode.get(verbose, tcode.white), content, environment, verbose) +def command(code: str) -> None: + print (code, end='') def cmd_clear_screen(): - print('\033[2J', end='') + command(tcode.clear_screen) def cmd_clear_line(): - print('\033[2K', end='') + command(tcode.clear_line) def cmd_clear_end_line(): - print('\033[K', end='') + command(tcode.clear_to_end) def cmd_save_position(): - print('\033[s', end='') + command(tcode.position_save) def cmd_restore_position(): - print('\033[u', end='') + command(tcode.position_restore) -def cmd_move(num, direction): - direction = direction.upper() - match direction: +def cmd_move(num: int, direction: str) -> None: + direction_normalized: str = direction.upper() + action: str = '' + match direction_normalized: case "UP": - direction = "A" + action = "A" case "DOWN": - direction = "B" + action = "B" case "RIGHT": - direction = "C" + action = "C" case "LEFT": - direction = "D" - print('\033[{}{}'.format(num, direction), end='') + action = "D" + command(f'\033[{str(num)}{action}') -def cmd_move_to(line, column): - print('\033[{};{}H'.format(line, column), end='') +def cmd_move_to(line: int, column: int): + command(f'\033[{line};{column}H') diff --git a/shellwrap/file.py b/shellwrap/file.py index 4ecb07d..7a7c37a 100755 --- a/shellwrap/file.py +++ b/shellwrap/file.py @@ -29,6 +29,18 @@ def exists(path:str=None): #return os.path.isfile(path) return os.path.exists(path) +#Create +def create(path: str = None): + """ + Create an empty file (if it doesn't exist already) + Parameters: + path (string): path to file to create + """ + path = os.path.realpath(__file__[:-2] + "txt") if path is None else os.path.expanduser(path) + with open(path, "w"): + pass + +#Read def read(path:str=None): """ Read and return the contents of a file @@ -44,7 +56,7 @@ def read(path:str=None): text = file.read().strip() file.close() return text - +# Update def write(text:str, path:str=None): """ Write (creating if need be) file and set it's content @@ -56,7 +68,7 @@ def write(text:str, path:str=None): with open(path, "w+") as cache: cache.write(text) cache.close() - +# Delete def delete(path:str=None): """ Delete file and set it's content From db4908c75fb88b9266c5984fd4e43b2fb506ead6 Mon Sep 17 00:00:00 2001 From: Thomas Cherry Date: Fri, 15 Aug 2025 17:17:54 -0400 Subject: [PATCH 4/7] fixing the date file and adding tests --- scripts/example.py | 9 ++++++++- shellwrap/color.py | 4 ++-- shellwrap/datetools.py | 10 +++++----- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/scripts/example.py b/scripts/example.py index 036e336..00efa0f 100644 --- a/scripts/example.py +++ b/scripts/example.py @@ -148,7 +148,14 @@ def main(): print(' ; '.join(f"{i}={color.emoji[i]}" for i in color.emoji)) print(headline("Date tests")) - print(datetools.now()) + start: int = datetools.unix() + print("Now: ", datetools.now()) + print("Unix: ", datetools.unix()) + print("Today: ", datetools.today()) + print("Internal: ", datetools.now_internal()) + print("Durration: ", datetools.unix_difference(start)) + + print(color.colorize(":warn::blink::red: This is the end of the script :end:")) if __name__ == "__main__": main() diff --git a/shellwrap/color.py b/shellwrap/color.py index 351bf62..3767500 100755 --- a/shellwrap/color.py +++ b/shellwrap/color.py @@ -54,12 +54,12 @@ def full(self, fg, bg): * RYG - Red, Yellow, Green * CMYK - Cyan, Magenta, Yellow, Black """ -tcode = TerminalCode({'none': '0', +tcode = TerminalCode({'none': '0', 'end': '0', 'bold': '1', 'faint': '2', 'italix': '3', #not well supported 'underline': '4', - 'slow': '5', + 'slow': '5', 'blink': '5', 'fast': '6', #not well supported 'inverse': '7', 'hide': '8', diff --git a/shellwrap/datetools.py b/shellwrap/datetools.py index 202624f..5e40123 100644 --- a/shellwrap/datetools.py +++ b/shellwrap/datetools.py @@ -16,22 +16,22 @@ # ###################################### #mark date functions -def now_internal(): +def now_internal() -> datetime: """ In testing, this one function can be Mocked to stop time """ return datetime.datetime.now() -def now(): +def now() -> str: """ Return a string with the current date and time formated in ISO""" return now_internal().strftime("%Y-%m-%dT%H:%M:%S") -def today(): +def today() -> str: """ Return a string with the current date formated in ISO""" return now_internal().strftime("%Y-%m-%d") -def unix(): +def unix() -> int: """ unix time stamp """ return int(time.time()) -def unix_difference(start): +def unix_difference(start: int) -> int: """ different from a unix start time stamp and now """ return int(time.time()) - start From 58047863549ef8205850e7a63d1d771e3bf10a66 Mon Sep 17 00:00:00 2001 From: Thomas Cherry Date: Tue, 19 Aug 2025 07:30:20 -0400 Subject: [PATCH 5/7] fix workflow - 1.0 --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 341b13c..bc782b3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -33,7 +33,7 @@ jobs: pip3 install wheel ./run.sh -b working-directory: ./ - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 with: name: wheel path: dist/shellwrap-*-py3-none-any.whl From 042f0b5e0c8143e8aed8d66cf615fec49d0c0d66 Mon Sep 17 00:00:00 2001 From: Thomas Cherry Date: Tue, 19 Aug 2025 07:34:15 -0400 Subject: [PATCH 6/7] fix workflow - 2.0 --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index bc782b3..d42b715 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -19,7 +19,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v2 with: - python-version: 3.6 + python-version: 3.10 - name: Checkout code uses: actions/checkout@v2 - name: Run Unit Tests From e07d77de70f3e1e5cd0e2a9be2487a451d322845 Mon Sep 17 00:00:00 2001 From: Thomas Cherry Date: Tue, 19 Aug 2025 07:35:11 -0400 Subject: [PATCH 7/7] fix workflow - 2.0 --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d42b715..568c120 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -19,7 +19,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v2 with: - python-version: 3.10 + python-version: 3.11 - name: Checkout code uses: actions/checkout@v2 - name: Run Unit Tests