Compare commits

..

18 Commits
v1.0 ... master

Author SHA1 Message Date
39f55c1b37 Fix for logging 2022-10-02 01:53:32 +03:00
8cd4e59918 Add asyncio support 2022-10-01 13:41:08 +03:00
069a7d9356 Minor fixes 2022-10-01 13:40:54 +03:00
santaspeen
56c4212391 del sonar files 2022-02-27 21:07:16 +03:00
santaspeen
156fba7a88 Add preview 2022-02-27 21:05:46 +03:00
santaspeen
3892657558 Ignore Sonar 2022-02-27 18:51:00 +03:00
santaspeen
12408f841b Update README 2022-02-24 16:07:35 +03:00
santaspeen
3533c7ebbf Update README 2022-02-24 16:02:11 +03:00
SantaSpeen
88138b6cb5 Update README 2022-02-23 00:46:09 +03:00
SantaSpeen
575879459d Update README 2022-02-23 00:41:32 +03:00
SantaSpeen
e63bd01cbf Update README 2022-02-22 23:45:24 +03:00
SantaSpeen
f81172a008 1.1 2022-02-22 23:44:10 +03:00
SantaSpeen
c333742b8d 1.1 2022-02-22 23:43:56 +03:00
SantaSpeen
a95bb2abab 1.1 2022-02-22 22:22:23 +03:00
santaspeen
494d375a4b 1.0 2022-02-20 23:59:09 +03:00
santaspeen
c25da23d4c 1.0 2022-02-20 23:58:13 +03:00
santaspeen
81ed4640a0 1.0 2022-02-20 23:51:39 +03:00
santaspeen
b9d20c002f 1.0 2022-02-20 23:45:36 +03:00
19 changed files with 488 additions and 382 deletions

8
.gitignore vendored
View File

@ -127,3 +127,11 @@ dmypy.json
# Pyre type checker
.pyre/
# Ignore .idea
.idea/
# ignore Sonar
codecov
.scannerwork/

8
.idea/.gitignore generated vendored
View File

@ -1,8 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/scr" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -1,45 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<Languages>
<language minSize="56" name="Python" />
</Languages>
</inspection_tool>
<inspection_tool class="PyArgumentEqualDefaultInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PyAugmentAssignmentInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PyBehaveInspection" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PyClassicStyleClassInspection" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PyMandatoryEncodingInspection" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PyMissingTypeHintsInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="PyPep8Inspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="ignoredErrors">
<list>
<option value="E501" />
<option value="E266" />
</list>
</option>
</inspection_tool>
<inspection_tool class="PyShadowingBuiltinsInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="ignoredNames">
<list>
<option value="Console" />
</list>
</option>
</inspection_tool>
<inspection_tool class="PyUnresolvedReferencesInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredIdentifiers">
<list>
<option value="core.Console.Console.__create_help_message" />
<option value="core.Console.Console.__exit" />
<option value="core.Console.Console.__alias" />
<option value="core.Console.Console.__create_message" />
<option value="core.Console.Console.__builtins_print" />
<option value="core.Console.Console.__prompt_in" />
<option value="core.Console.Console.__not_found" />
<option value="console.__lshift__" />
</list>
</option>
</inspection_tool>
</profile>
</component>

View File

@ -1,6 +0,0 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

4
.idea/misc.xml generated
View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml generated
View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/CLI-in-Python.iml" filepath="$PROJECT_DIR$/.idea/CLI-in-Python.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated
View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@ -1,6 +1,13 @@
# CLI in Python
<p align="center">
<img src="https://img.shields.io/github/license/SantaSpeen/CLI-in-Python?style=for-the-badge" alt="license" title="license: MIT">
<img src="https://img.shields.io/github/issues/SantaSpeen/CLI-in-Python?style=for-the-badge" alt="issues">
<img src="./assets/magic_logo.svg" alt="magic">
<br/>
<img src="./assets/preview.png" alt="preview">
</p>
#### Версия для русских: [здесь](./README_RU.md)
##### Версия для русских: [здесь](./README_RU.md)
## Command-line interface for programs on Python3
@ -8,16 +15,29 @@
```python
from console import Console
import platform
import getpass
cli = Console(prompt_in=">", prompt_out="]:")
def cli_echo(x: str):
def cli_echo(argv: list):
""" Help message here """
message = "Echo message: " + x
message = f"argv: {argv}"
return message
cli.add("help", cli_echo) # Add commands
def cli_uname():
""" Print uname information. """
uname = platform.uname()
user = getpass.getuser()
return f"{user}@{uname.node} -> {uname.system} {uname.release} ({uname.version})"
# Add commands
cli.add("help", cli_echo, echo=True) # With echo
cli.add("uname", cli_uname) # Without echo
cli.run()
```
@ -39,7 +59,7 @@ cli.write("cli.write")
# ]: cli.write
```
* With logging output usage
* With `logging` output usage
```python
from console import Console
@ -62,7 +82,7 @@ logging.info("Info log")
# ]: 2022-02-20 23:22:49,731 - root - INFO - Info log
```
* with `print()` and `console.log` output usage
* With `print()` and `console.log` output usage
```python
from console import Console
@ -90,6 +110,9 @@ console << "<< log"
# ]: << log
```
If you use IDE, you mast update `builtins.pyi` with `scr/builtins_fix.pyi`. <br/>
Copy all from `builtins_fix.pyi` and insert into `builtins.pyi` at line `131` below `class type(object)`.
## Links
* [Author's Telegram](https://t.me/SantaSpeen "SantaSpeen"): https://t.me/SantaSpeen
@ -97,3 +120,4 @@ console << "<< log"
Used in:
* [Python-CLI-Game-Engine](https://github.com/SantaSpeen/Python-CLI-Game-Engine)
* [CLI-Remote-in-Python](https://github.com/SantaSpeen/CLI-Remote-in-Python)

View File

@ -1,4 +1,11 @@
# CLI in Python
<p align="center">
<img src="https://img.shields.io/github/license/SantaSpeen/CLI-in-Python?style=for-the-badge" alt="license" title="license: MIT">
<img src="https://img.shields.io/github/issues/SantaSpeen/CLI-in-Python?style=for-the-badge" alt="issues">
<img src="./assets/magic_logo.svg" alt="magic">
<br/>
<img src="./assets/preview.png" alt="preview">
</p>
## Консольная оболочка для программ на Python3
@ -6,16 +13,29 @@
```python
from console import Console
import platform
import getpass
cli = Console(prompt_in=">", prompt_out="]:")
def cli_echo(x: str):
def cli_echo(argv: list):
""" Help message here """
message = "Echo message: " + x
message = f"argv: {argv}"
return message
cli.add("help", cli_echo) # Add commands
def cli_uname():
""" Print uname information. """
uname = platform.uname()
user = getpass.getuser()
return f"{user}@{uname.node} -> {uname.system} {uname.release} ({uname.version})"
# Add commands
cli.add("help", cli_echo, echo=True) # With echo
cli.add("uname", cli_uname) # Without echo
cli.run()
```
@ -37,7 +57,7 @@ cli.write("cli.write")
# ]: cli.write
```
* Использование вывода с logging
* Использование вывода с `logging`
```python
from console import Console
@ -60,7 +80,7 @@ logging.info("Info log")
# ]: 2022-02-20 23:22:49,731 - root - INFO - Info log
```
* Использование вывода с`print()` и `console.log`
* Использование вывода с `print()` и `console.log`
```python
from console import Console
@ -88,10 +108,14 @@ console << "<< log"
# ]: << log
```
Если вы используете IDE, можно обновить `builtins.pyi`, что бы она не ругалась на отсутствие `console.*` используя `scr/builtins_fix.pyi`. <br/>
Скопируйте всё из `builtins_fix.pyi` и вставте в `builtins.pyi` на `131` линию, перед `class type(object)`.
## Ссылки
* [Мой Telegram](https://t.me/SantaSpeen "SantaSpeen"): https://t.me/SantaSpeen
Используемые в поектах:
Используемые в проектах:
* [Python-CLI-Game-Engine](https://github.com/SantaSpeen/Python-CLI-Game-Engine)
* [CLI-Remote-in-Python](https://github.com/SantaSpeen/CLI-Remote-in-Python)

40
assets/magic_logo.svg Normal file
View File

@ -0,0 +1,40 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="180.5" height="28" role="img" aria-label="MADE WITH: MAGIC">
<title>MADE WITH: MAGIC</title>
<g shape-rendering="crispEdges">
<rect width="100.25" height="28" fill="#555"/>
<rect x="100.25" width="80.25" height="28" fill="#fe7d37"/>
</g>
<g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="100">
<svg x="3"
y="4"
id="Capa_1"
height="20"
viewBox="0 0 512 512"
width="20"
xmlns="http://www.w3.org/2000/svg">
<g>
<path d="m395.82 182.616-188.72 188.72-12.91 1.72-9.35 20.54-34.31 34.31-11.01-.73-11.25 22.99-56.48 56.48c-2.93 2.93-6.77 4.39-10.61 4.39s-7.68-1.46-10.61-4.39l-22.62-22.62h-.01l-22.62-22.63c-5.86-5.86-5.86-15.36 0-21.22l77.63-77.63 16.6-7.03 5.66-15.23 34.31-34.31 14.84-4.92 7.42-17.34 167.57-167.57 33.24 33.24z" fill="#f66"/>
<path d="m395.82 116.146v66.47l-188.72 188.72-12.91 1.72-9.35 20.54-34.31 34.31-11.01-.73-11.25 22.99-56.48 56.48c-2.93 2.93-6.77 4.39-10.61 4.39s-7.68-1.46-10.61-4.39l-22.62-22.62 334.64-334.64z" fill="#e62e6b"/>
<path d="m506.61 209.006-69.14-69.13 43.05-88.38c2.8-5.75 1.65-12.65-2.88-17.17-4.52-4.53-11.42-5.68-17.17-2.88l-88.38 43.05-69.13-69.14c-4.35-4.35-10.92-5.6-16.56-3.16-5.65 2.45-9.23 8.09-9.04 14.24l2.86 90.45-85.37 57.83c-4.91 3.32-7.4 9.22-6.36 15.04 1.04 5.83 5.4 10.51 11.15 11.94l96.62 24.01 24.01 96.62c1.43 5.75 6.11 10.11 11.94 11.15.87.16 1.75.23 2.62.23 4.92 0 9.6-2.42 12.42-6.59l57.83-85.37 90.45 2.86c6.14.19 11.79-3.39 14.24-9.04 2.44-5.64 1.19-12.21-3.16-16.56z" fill="#fabe2c"/>
<path d="m296.26 215.706 24.01 96.62c1.43 5.75 6.11 10.11 11.94 11.15.87.16 1.75.23 2.62.23 4.92 0 9.6-2.42 12.42-6.59l57.83-85.37 90.45 2.86c6.14.19 11.79-3.39 14.24-9.04 2.44-5.64 1.19-12.21-3.16-16.56l-69.14-69.13 43.05-88.38c2.8-5.75 1.65-12.65-2.88-17.17z" fill="#fd9025"/>
<path d="m465 416.966c-25.92 0-47 21.08-47 47s21.08 47 47 47 47-21.08 47-47-21.08-47-47-47z" fill="#fabe2c"/>
<path d="m104 28.966h-13v-13c0-8.284-6.716-15-15-15s-15 6.716-15 15v13h-13c-8.284 0-15 6.716-15 15s6.716 15 15 15h13v13c0 8.284 6.716 15 15 15s15-6.716 15-15v-13h13c8.284 0 15-6.716 15-15s-6.716-15-15-15z" fill="#fed843"/>
<path d="m207.1 371.336-22.26 22.26-45.32-87.62 22.26-22.26z" fill="#fed843"/>
<path d="m184.84 393.596 22.26-22.26-22.66-43.81-22.265 22.265z" fill="#fabe2c"/>
<path d="m150.53 427.906-22.26 22.26-45.32-87.62 22.26-22.26z" fill="#fed843"/>
<path d="m128.27 450.166 22.26-22.26-22.655-43.815-22.26 22.26z" fill="#fabe2c"/>
<circle cx="15" cy="119.969" fill="#5ed8d3" r="15"/>
<circle cx="128" cy="199.969" fill="#d599ed" r="15"/>
<circle cx="192" cy="63.964" fill="#f66" r="15"/>
<circle cx="328" cy="415.967" fill="#31bebe" r="15"/>
<circle cx="440" cy="327.967" fill="#ad77e3" r="14.999"/>
</g>
</svg>
<text transform="scale(.1)" x="600.25" y="175" textLength="590.5" fill="#fff">
MAGIC BY
</text>
<text transform="scale(.1)" x="1400.75" y="175" textLength="700.5" fill="#fff" font-weight="bold">
SantaSpeen
</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
assets/preview.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

View File

@ -1,228 +0,0 @@
# -*- coding: utf-8 -*-
# Developed by Ahegao Devs
# Written by: SantaSpeen
# Version 1.0
# Licence: MIT
# (c) ahegao.ovh 2022
import builtins
import logging
import sys
import traceback
from typing import AnyStr
class ConsoleIO:
@staticmethod
def write(s: AnyStr):
sys.stdout.write(s)
@staticmethod
def write_err(s: AnyStr):
sys.stderr.write(s)
@staticmethod
def read():
return sys.stdin.readline().strip()
# noinspection PyUnusedLocal, PyShadowingBuiltins, PyUnresolvedReferences
class Console:
def __init__(self,
prompt_in: str = ">",
prompt_out: str = "]:",
not_found: str = "Command \"%s\" not found in alias.",
file: str or None = ConsoleIO,
debug: bool = False) -> None:
"""
def __init__(self,
prompt_in: str = ">",
prompt_out: str = "]:",
not_found: str = "Command \"%s\" not found in alias.") -> None:
:param prompt_in:
:param prompt_out:
:param not_found:
"""
self.__prompt_in = prompt_in
self.__prompt_out = prompt_out
self.__not_found = not_found + "\n"
self.__is_debug = debug
self.__print = print
self.__file = file
self.__alias = {
"help": self.__create_help_message,
}
self.is_run = False
self.get_IO = ConsoleIO
def __debug(self, *x):
if self.__is_debug:
x = list(x)
x.insert(0, "\r CONSOLE DEBUG:")
self.__print(*x)
def __getitem__(self, item):
print(item)
@staticmethod
def __get_max_len(arg) -> int:
arg = list(arg)
i = 0
for a in arg:
l = len(a)
if l > i:
i = l
return i
# noinspection PyStringFormat
def __create_help_message(self, x) -> AnyStr:
""" Print help message and alias of console commands"""
self.__debug("creating help message")
max_len = self.__get_max_len(self.__alias.keys())
if max_len < 7:
max_len = 7
message = f"%{max_len}s : Help message\n" % "Command"
for k, v in self.__alias.items():
doc = v.__doc__
if doc is None:
doc = " No help message found"
message += f" %{max_len}s :%s\n" % (k, doc)
return message
def __create_message(self, text, r="\r"):
self.__debug(f"create message to output, is_run: {self.is_run}")
text += "\n"
if self.is_run:
text += "\r" + self.__prompt_in + " "
return r + self.__prompt_out + " " + text
@property
def alias(self) -> dict:
"""
def alias(self) -> dict:
:return: dict of alias
"""
return self.__alias.copy()
def add(self, key: str, func) -> dict:
"""
def add(self, key: str, func) -> dict:
:param key:
:param func:
:return:
"""
key = key.format(" ", "-")
if not isinstance(key, str):
raise TypeError("key must be string")
self.__debug(f"added user command: key={key}; func={func}")
self.__alias.update({key: func})
return self.__alias.copy()
def write(self, s: AnyStr, r="\r"):
self.__file.write(self.__create_message(s, r))
def log(self, s: AnyStr, r='\r') -> None:
self.write(s, r)
def __lshift__(self, s: AnyStr) -> None:
self.write(s)
def __builtins_print(self,
*values: object,
sep: str or None = " ",
end: str or None = None,
file: str or None = None,
flush: bool = False,
loading: bool = False) -> None:
self.__debug(f"Used __builtins_print; is_run: {self.is_run}")
val = list(values)
if len(val) > 0:
val.insert(0, "\r" + self.__prompt_out)
if not loading:
if self.is_run:
val.append("\r\n" + self.__prompt_in + " ")
end = "" if end is None else end
else:
if end is None:
end = "\n"
self.__print(*tuple(val), sep=sep, end=end, file=file, flush=flush)
def logger_hook(self) -> None:
self.__debug("used logger_hook")
def emit(cls, record):
try:
msg = cls.format(record)
ConsoleIO.write(self.__create_message(msg))
cls.flush()
except RecursionError:
raise
except Exception:
cls.handleError(record)
logging.StreamHandler.emit = emit
def builtins_hook(self) -> None:
"""
def builtins_hook(self) -> None:
:return: None
"""
self.__debug("used builtins_hook")
builtins.Console = Console
builtins.console = self
builtins.print = self.__builtins_print
def run(self) -> None:
"""
def run(self) -> None:
:return: None
"""
self.is_run = True
self.run_while(True)
def run_while(self, whl) -> None:
"""
def run_while(self, whl) -> None:
:param whl: run while what?
:return: None
"""
self.__debug(f"run while {whl}")
while whl:
try:
ConsoleIO.write("\r" + self.__prompt_in + " ")
cmd_in = ConsoleIO.read()
cmd = cmd_in.split(" ")[0]
if cmd == "":
pass
else:
command = self.__alias.get(cmd)
if command:
x = cmd_in[len(cmd) + 1:]
output = command(x)
if isinstance(output, str):
self.log(output)
else:
self.log(self.__not_found % cmd)
except Exception as e:
if e == KeyboardInterrupt:
raise e
ConsoleIO.write_err("\rDuring the execution of the command, an error occurred:\n\n" +
str(traceback.format_exc()) +
"\nType Enter to continue.")

View File

@ -1,46 +0,0 @@
# -*- coding: utf-8 -*-
# Developed by Ahegao Devs
# Written by: SantaSpeen
# Licence: MIT
# (c) ahegao.ovh 2022
from _typeshed import SupportsWrite
from builtins import function
from typing import AnyStr
class ConsoleIO:
@staticmethod
def write(s: AnyStr): ...
@staticmethod
def write_err(s: AnyStr): ...
@staticmethod
def read(): ...
class Console(object):
def __init__(self,
prompt_in: str = ">",
prompt_out: str = "]:",
not_found: str = "Command \"%s\" not found in alias.",
file: SupportsWrite[str] or None = Console,
debug: bool = False) -> None:
self.get_IO: ConsoleIO = ConsoleIO
self.is_run: bool = False
def __getitem__(self, item): ...
@property
def alias(self) -> dict: ...
def add(self, key: str, func: function) -> dict: ...
def log(self, s: AnyStr, r='\r') -> None: ...
def write(self, s: AnyStr, r='\r') -> None: ...
def logger_hook(self) -> None: ...
def builtins_hook(self) -> None: ...
def run(self) -> None: ...
def run_while(self, whl) -> None:...

308
src/console/Console.py Normal file
View File

@ -0,0 +1,308 @@
# -*- coding: utf-8 -*-
# Developed by Ahegao Devs
# Written by: SantaSpeen
# Version 2.1
# Licence: MIT
# (c) ahegao.su 2022
import builtins
import logging
import sys
import traceback
import asyncio
from builtins import RecursionError
from typing import AnyStr
try:
from aioconsole import ainput
except ImportError:
ainput = None
class ConsoleIO:
@staticmethod
def write(s: AnyStr):
sys.stdout.write(s)
@staticmethod
def write_err(s: AnyStr):
sys.stderr.write(s)
@staticmethod
def read():
return sys.stdin.readline().strip()
# noinspection PyUnusedLocal, PyShadowingBuiltins, PyUnresolvedReferences
class Console:
def __init__(self,
prompt_in=">",
prompt_out="]:",
not_found="Command \"%s\" not found in alias.",
file=ConsoleIO,
debug=False,
async_loop=None) -> None:
"""
def __init__(self,
prompt_in: str = ">",
prompt_out: str = "]:",
not_found: str = 'Command "%s" not found in alias.'
) -> None:
:param prompt_in:
:param prompt_out:
:param not_found:
"""
self.__prompt_in = prompt_in
self.__prompt_out = prompt_out
self.__not_found = not_found + "\n"
self.__is_debug = debug
self.__print = print
self.__file = file
self.__alias = dict()
self.__man = dict()
self.add("man", self.__create_man_message, True, "man [COMMAND]")
self.add("help", self.__create_help_message, True, "List of all available commands\n"
"--raw : View raw")
self.is_run = False
self.async_loop = async_loop
self.get_IO = ConsoleIO
def __debug(self, *x):
if self.__is_debug:
x = list(x)
x.insert(0, "\r CONSOLE DEBUG:")
self.__print(*x)
def __getitem__(self, item):
print(item)
@staticmethod
def __get_max_len(arg) -> int:
i = 0
arg = list(arg)
for a in arg:
ln = len(str(a))
if ln > i:
i = ln
return i
def __create_man_message(self, argv: list) -> AnyStr:
x = argv[0]
if x in ['']:
return "man [COMMAND]"
man_message = self.__man.get(x)
if man_message:
return man_message
return f'man: command "{x}" not found'
# noinspection PyStringFormat
def __create_help_message(self, argv: list) -> AnyStr:
""" Print help message and alias of console commands"""
self.__debug("creating help message")
raw = False
max_len_v = 0
if "--raw" in argv:
max_len_v = self.__get_max_len(self.__alias.values())
print()
raw = True
message = str()
max_len = self.__get_max_len(self.__alias.keys())
if max_len < 7:
max_len = 7
if not raw:
message += f"%{max_len}s : Help message\n" % "Command"
else:
message += f"%-{max_len - len(self.__prompt_out) - 1}s; %-{max_len_v}s; __doc__\n" % ("Key", "Object")
for k, v in self.__alias.items():
doc = v['f'].__doc__
if raw:
message += f"%-{max_len}s; %-{max_len_v}s; %s\n" % (k, v, doc)
else:
if doc is None:
doc = " No help message found"
message += f" %{max_len}s :%s\n" % (k, doc)
return message
def __create_message(self, text, r="\r"):
self.__debug(f"create message to output, is_run: {self.is_run}")
text += "\n"
if self.is_run:
text += "\r" + self.__prompt_in + " "
return r + self.__prompt_out + " " + text
@property
def alias(self) -> dict:
"""
@property
def alias(self) -> dict:
:return: Copy of commands alias
"""
return self.__alias.copy()
def add(self, key: str, func, argv=False, man="No have manual message") -> dict:
"""
add(key: str, func, echo=False) -> dict
:param key: Command name. This name added to alias of commands
:param func: Function or lambda function to run, when called key
:param argv: If you need echo from command set as True
:param man: Man message
:return: Copy of commands alias
"""
key = key.format(" ", "-")
if not isinstance(key, str):
raise TypeError("key must be string")
self.__debug(f"added user command: key={key}; func={func}; echo={argv}")
self.__alias.update({key: {"f": func, "e": argv}})
self.__man.update({key: f'Manual for command "{key}"\n\n' + man + "\n\n"})
return self.__alias.copy()
def write(self, s: AnyStr, r="\r"):
self.__file.write(self.__create_message(s, r))
def log(self, s: AnyStr, r='\r') -> None:
self.write(s, r)
def __lshift__(self, s: AnyStr) -> None:
self.write(s)
def __builtins_print(self,
*values: object,
sep: str or None = " ",
end: str or None = None,
file: str or None = None,
flush: bool = False,
loading: bool = False) -> None:
self.__debug(f"Used __builtins_print; is_run: {self.is_run}")
val = list(values)
if len(val) > 0:
val.insert(0, "\r" + self.__prompt_out)
if not loading:
if self.is_run:
val.append("\r\n" + self.__prompt_in + " ")
end = "" if end is None else end
else:
if end is None:
end = "\n"
self.__print(*tuple(val), sep=sep, end=end, file=file, flush=flush)
def logger_hook(self) -> None:
"""
def logger_hook(self) -> None:
:return: None
"""
self.__debug("used logger_hook")
def emit(cls, record):
try:
msg = cls.format(record)
if cls.stream.name == "<stderr>":
ConsoleIO.write(self.__create_message(msg))
else:
cls.stream.write(msg + cls.terminator)
cls.flush()
except RecursionError:
raise
except Exception as e:
cls.handleError(record)
logging.StreamHandler.emit = emit
def builtins_hook(self) -> None:
"""
def builtins_hook(self) -> None:
:return: None
"""
self.__debug("used builtins_hook")
builtins.Console = Console
builtins.console = self
builtins.print = self.__builtins_print
def _cli_logic(self, cmd_in):
try:
cmd = cmd_in.split(" ")[0]
if cmd == "":
pass
else:
command_object = self.__alias.get(cmd)
if command_object:
command = command_object['f']
if command_object['e']:
argv = cmd_in[len(cmd) + 1:].split(" ")
output = command(argv)
else:
output = command()
self.log(str(output))
else:
self.log(self.__not_found % cmd)
except Exception as e:
ConsoleIO.write_err("\rDuring the execution of the command, an error occurred:\n\n" +
str(traceback.format_exc()) +
"\nType Enter to continue.")
def run(self) -> None:
"""
def run(self) -> None:
:return: None
"""
self.is_run = True
self.run_while(True)
def run_while(self, whl) -> None:
"""
def run_while(self, whl) -> None:
:param whl: run while what?
:return: None
"""
self.__debug(f"run while {whl}")
while whl:
ConsoleIO.write("\r" + self.__prompt_in + " ")
self._cli_logic(ConsoleIO.read())
async def async_run(self) -> None:
"""
def async_run(self) -> None:
:return: None
"""
self.is_run = True
await self.async_run_while(True)
async def async_run_while(self, whl) -> None:
"""
async def async_run_while(self, whl) -> None:
:param whl: run while what?
:return: None
"""
if ainput is None:
print("Install aioconsole for async! pip install aioconsole")
exit(1)
if self.async_loop is None:
self.async_loop = asyncio.get_event_loop()
self.__debug(f"async run while {whl}")
reader = asyncio.StreamReader()
while whl:
ConsoleIO.write("\r" + self.__prompt_in + " ")
res = await ainput()
self._cli_logic(res)

51
src/console/Console.pyi Normal file
View File

@ -0,0 +1,51 @@
# -*- coding: utf-8 -*-
# Developed by Ahegao Devs
# Written by: SantaSpeen
# Licence: MIT
# (c) ahegao.ovh 2022
from _typeshed import SupportsWrite
from asyncio import AbstractEventLoop
from typing import AnyStr, NoReturn
class ConsoleIO:
@staticmethod
def write(s: AnyStr): ...
@staticmethod
def write_err(s: AnyStr): ...
@staticmethod
def read(): ...
class Console(object):
def __init__(self,
prompt_in: str = ">",
prompt_out: str = "]:",
not_found: str = "Command \"%s\" not found in alias.",
file: SupportsWrite[str] or None = Console,
debug: bool = False,
async_loop: AbstractEventLoop = None) -> NoReturn:
self.get_IO: ConsoleIO = ConsoleIO
self.is_run: bool = False
def __getitem__(self, item): ...
@property
def alias(self) -> dict: ...
def add(self, key: str, func, argv: bool = False, man: str = "No have manual message") -> dict:...
def log(self, s: AnyStr, r='\r') -> NoReturn: ...
def __lshift__(self, s: AnyStr) -> NoReturn:
self.write(s)
def write(self, s: AnyStr, r='\r') -> NoReturn: ...
def logger_hook(self) -> NoReturn: ...
def builtins_hook(self) -> NoReturn: ...
def run(self) -> NoReturn: ...
def run_while(self, whl) -> NoReturn:...
async def async_run(self) -> NoReturn: ...
async def async_run_while(self, whl) -> NoReturn: ...

View File

@ -1,5 +1,7 @@
import getpass
import logging
import os
import platform
from console import Console, ConsoleIO
@ -57,34 +59,44 @@ def builtins_preview():
ConsoleIO.write("\n\n") # Or console.get_IO.write("\n\n")
def cli_echo(x: str):
def cli_echo(argv: list):
""" Help message here """
message = "Echo message: " + x
message = f"argv: {argv}"
return message
def cli_error(x: str):
def cli_error():
""" Print error message """
raise Exception("Test error message")
def cli_exit(x: str):
def cli_exit():
""" Kill process """
pid = os.getpid()
print(f"\r$ kill {pid}")
os.system(f"kill {pid}")
def cli_mode():
def cli_uname():
""" Print uname information """
uname = platform.uname()
user = getpass.getuser()
return f"{user}@{uname.node} -> {uname.system} {uname.release} ({uname.version})"
def cli_mode():
ConsoleIO.write("\rtype help\n")
cli.add("echo", cli_echo)
cli.add("echo", cli_echo, argv=True)
cli.add("error", cli_error)
cli.add("exit", cli_exit)
cli.add("uname", cli_uname)
cli.run()
@ -94,6 +106,6 @@ def cli_mode():
if __name__ == '__main__':
cli_print()
# logger_preview()
# builtins_preview()
# cli_mode()
logger_preview()
builtins_preview()
cli_mode()