Skip to content

Commit

Permalink
Initial Commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Andy Zhang authored May 3, 2023
0 parents commit de7e104
Show file tree
Hide file tree
Showing 17 changed files with 424 additions and 0 deletions.
19 changes: 19 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Copyright (c) 2023 NWSOFT

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.
6 changes: 6 additions & 0 deletions NWSh/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import sys

if not sys.stdin.isatty():
print("Error: The console is not a TTY.", file=sys.stderr)
sys.stderr.flush()
sys.exit(1)
44 changes: 44 additions & 0 deletions NWSh/arguments.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from typing import Dict, Union

from prompt_toolkit import HTML

from NWSh.printing import print_error
from NWSh.subsystem import Subsystem


class Arguments(Subsystem):
def __init__(self, name: str = "(none)", preferences=None):
super().__init__(name, preferences)
self.arguments: Dict[str, Union[str, int, float]] = {}

def print_welcome(self):
return # Do not print welcome message, as this is not a "normal" subsystem

def ask_argument(self, argument_name: str, argument_description: str, argument_type: str = "str"):
# language=HTML
self.prompt = HTML(
f"<subsystem_name>{self.name}</subsystem_name>"
f"<number>({argument_name})</number>"
f"<prompt>></prompt> "
)
self.arguments[argument_name] = self.session.prompt(self.prompt, rprompt=argument_description, style=self.style)

if argument_type == "int":
try:
self.arguments[argument_name] = int(self.arguments[argument_name])
except ValueError:
print_error(self.name, f"Argument {argument_name} must be an integer")
self.ask_argument(argument_name, argument_description, argument_type)
elif argument_type == "float":
try:
self.arguments[argument_name] = float(self.arguments[argument_name])
except ValueError:
print_error(self.name, f"Argument {argument_name} must be a float")
self.ask_argument(argument_name, argument_description, argument_type)

def get_argument(self, argument_name: str):
try:
return self.arguments[argument_name]
except KeyError:
print_error(self.name, f"Argument {argument_name} not found")
return None
17 changes: 17 additions & 0 deletions NWSh/commands.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from typing import Callable

from NWSh.printing import print_warning
from NWSh.subsystem import Subsystem
from NWSh.system import System


def register_command_system(system: System, command: str, func: Callable):
if command in system.commands:
print_warning(system.name, f"Command {command} already registered")
system.commands[command] = func


def register_command_subsystem(subsystem: Subsystem, command: str, func: Callable):
if command in subsystem.commands:
print_warning(subsystem.name, f"Command {command} already registered")
subsystem.commands[command] = func
5 changes: 5 additions & 0 deletions NWSh/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import platform

WINDOWS = platform.system() == "Windows"
OSX = platform.system() == "Darwin"
LINUX = platform.system() == "Linux"
16 changes: 16 additions & 0 deletions NWSh/paths.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import os
from pathlib import Path

from NWSh.constants import OSX, WINDOWS

# Path to the user preferences folder
if WINDOWS:
USER_PREFS_DIR = Path(os.getenv("APPDATA")) / "NWSOFT" / "NWSh"
elif OSX:
USER_PREFS_DIR = Path.home() / "Library" / "Preferences" / "NWSOFT" / "NWSh"
else:
USER_PREFS_DIR = Path.home() / ".NWSOFT" / "NWSh"

# Path to the user preferences file
USER_PREFS_FILE = USER_PREFS_DIR / "settings.json"
DEFAULT_PREFS_FILE = Path(__file__).parent / "resources" / "settings.json"
42 changes: 42 additions & 0 deletions NWSh/printing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from prompt_toolkit import HTML, print_formatted_text
from prompt_toolkit.styles import Style

from NWSh.settings import Settings


def print_error(subsystem: str, error: str):
settings = Settings()
using_style = settings.get("styles")[settings.get("using")]
style = Style.from_dict(using_style)
# language=HTML
print_formatted_text(
HTML(f"<subsystem_name>{subsystem}</subsystem_name>|<error>ERR : {error}</error>"), style=style
)


def print_warning(subsystem: str, warning: str):
settings = Settings()
using_style = settings.get("styles")[settings.get("using")]
style = Style.from_dict(using_style)
# language=HTML
print_formatted_text(
HTML(f"<subsystem_name>{subsystem}</subsystem_name>|<warning>WARN: {warning}</warning>"), style=style
)


def print_info(subsystem: str, info: str):
settings = Settings()
using_style = settings.get("styles")[settings.get("using")]
style = Style.from_dict(using_style)
# language=HTML
print_formatted_text(HTML(f"<subsystem_name>{subsystem}</subsystem_name>|<info>INFO: {info}</info>"), style=style)


def print_result(subsystem: str, result: str):
settings = Settings()
using_style = settings.get("styles")[settings.get("using")]
style = Style.from_dict(using_style)
# language=HTML
print_formatted_text(
HTML(f"<subsystem_name>{subsystem}</subsystem_name>|<result>=> {result}</result>"), style=style
)
38 changes: 38 additions & 0 deletions NWSh/resources/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Default user preferences
{
"styles": {
"default": {
// Plain text
"": "",
// Header
"title": "#37AFAE",
"description": "#356161",
// Prompt
"subsystem_name": "#368A8A",
"system_name": "#57B4B4",
"number": "#37AFAE",
"prompt": "#ffffff",
// Warnings/Errors
"warning": "#FFA500",
"error": "#FF0000",
"result": "#4188C6"
},
"green": {
// Plain text
"": "",
// Header
"title": "#77BC41",
"description": "#537B2B",
// Prompt
"subsystem_name": "#669D35",
"system_name": "#8ABF4F",
"number": "#77BC41",
"prompt": "#ffffff",
// Warnings/Errors
"warning": "#FFA500",
"error": "#FF0000",
"result": "#4188C6"
}
},
"using": "default"
}
30 changes: 30 additions & 0 deletions NWSh/settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import json5rw as json

from NWSh.paths import DEFAULT_PREFS_FILE, USER_PREFS_FILE


class Settings:
def __init__(self):
self.settings = {}
self.load()

def load(self):
try:
with DEFAULT_PREFS_FILE.open() as f:
self.settings = json.load(f)
with USER_PREFS_FILE.open() as f:
self.settings |= json.load(f)
except FileNotFoundError:
self.save()

def save(self):
USER_PREFS_FILE.parent.mkdir(parents=True, exist_ok=True)
with USER_PREFS_FILE.open("w") as f:
json.dump(self.settings, f, indent=4, sort_keys=True)

def get(self, key, default=None):
return self.settings.get(key, default)

def set(self, key, value):
self.settings[key] = value
self.save()
94 changes: 94 additions & 0 deletions NWSh/subsystem.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import sys

from prompt_toolkit import HTML, print_formatted_text, PromptSession
from prompt_toolkit.output.color_depth import ColorDepth as Depth
from prompt_toolkit.styles import Style

from NWSh.printing import print_error
from NWSh.settings import Settings

DEFAULT_PREFERENCES = {
"name" : "(none)",
"version" : "0.1",
"description": # language=HTML
"<i>No description</i>",
"author" : "Anonymous",
"license" : "MIT",
}


class Subsystem:
commands = {
"exit": lambda: sys.exit(0)
}

def __init__(self, name: str = "(none)", preferences=None):
if preferences is None:
preferences = {}
self.preferences = DEFAULT_PREFERENCES | preferences
self.name = name
self.prompt = f"{name}> "
self.session = PromptSession()
self.settings = Settings()
self.style = None
self.number = 0
self.get_style()

# language=HTML
self.ctrl_c_msg = HTML(
f"<subsystem_name>{self.name}</subsystem_name>: Press <b>Ctrl</b>+<b>D</b> (EOF) to exit."
)

self.print_welcome()

def print_welcome(self):
# language=HTML
print_formatted_text(
HTML(
f"""\
<title><b>{self.preferences["name"]}</b> version {self.preferences["version"]}
By {self.preferences["author"]}, under the {self.preferences["license"]} license
</title>\
<description>\
{self.preferences["description"]}
</description>\
""",
),
style=self.style,
)

def get_style(self):
self.style = Style.from_dict(
self.settings.get("styles")
[self.settings.get("using")]
)

def command_not_found(self):
print_error(self.name, "Command not found")

def execute_command(self, name):
try:
command_function = self.commands[name]
except KeyError:
command_function = self.command_not_found
command_function()

def ask_command(self):
self.prompt = HTML(
# language=HTML
f"<subsystem_name>({self.name})</subsystem_name> "
f"<number>[{self.number}]</number>"
f"<prompt>></prompt> "
)
self.number += 1
try:
result = self.session.prompt(self.prompt, style=self.style, color_depth=Depth.TRUE_COLOR)
except KeyboardInterrupt:
print_formatted_text(self.ctrl_c_msg, style=self.style)
return
except EOFError:
sys.exit(0)

self.execute_command(result)


43 changes: 43 additions & 0 deletions NWSh/system.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import sys

from prompt_toolkit import HTML, print_formatted_text, PromptSession
from prompt_toolkit.output import ColorDepth as Depth

from NWSh.settings import Settings
from NWSh.subsystem import Subsystem


class System(Subsystem):
commands = {}

def __init__(self, name: str = "(none)", preferences=None):
super().__init__(name, preferences)
self.prompt = f"{name}> "
self.session = PromptSession()
self.settings = Settings()
self.style = None
self.number = 0
self.get_style()

# language=HTML
self.ctrl_c_msg = HTML(
f"<system_name>{self.name}</system_name>: Press <b>Ctrl</b>+<b>D</b> (EOF) to exit."
)

def ask_command(self):
# language=HTML
self.prompt = HTML(
f"<system_name>{self.name}</system_name> "
f"<number>[{self.number}]</number>"
f"<prompt>></prompt> "
)
self.number += 1
try:
result = self.session.prompt(self.prompt, style=self.style, color_depth=Depth.TRUE_COLOR)
except KeyboardInterrupt:
print_formatted_text(self.ctrl_c_msg, style=self.style)
return
except EOFError:
sys.exit(0)

self.execute_command(result)
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# NWShDev
This package provides a set of tools to facilitate the development of the [NWSh](https://github.com/NWSOFT-ORG/NWSh) shell.

See the documents in the `docs` directory for more information.
11 changes: 11 additions & 0 deletions docs/Arguments.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# API Documentation - Arguments
# `module NWSh.arguments` - Ask for arguments
> Provides a set of functions to ask for arguments.
## `class NWSh.arguments.Arguments`
> A class to ask for arguments.
#### `def NWSh.arguments.Arguments.ask_argument(name, description, type)`
> Asks for arguments, with a name, a description and a type.\
Note: The type can be `string`, `int`, `float`. Conversion is done automatically.
#### `def NWSh.arguments.Arguments.get_argument(name)`
> Returns the value of the argument with the given name.
> If the argument is not found, returns `None` and prints an error message.
8 changes: 8 additions & 0 deletions docs/Commands.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# API Documentation - Commands
## `module NWSh.commands` - Create commands
> Provides a set of functions to create commands.
### `def NWSh.commands.register_command_subsystem(subsystem, command, func)`
> Registers a command in a subsystem.
> Arguments are not supported yet
### `def NWSh.commands.register_command_system(system, command, func)`
> Same as `NWSh.commands.register_command_subsystem`, but for the `System` class.
Loading

0 comments on commit de7e104

Please sign in to comment.