-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathuser_items.py
More file actions
182 lines (141 loc) · 5.53 KB
/
user_items.py
File metadata and controls
182 lines (141 loc) · 5.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
from types import FrameType
from typing import Optional, Any, IO
from os.path import isfile
from math import ceil
import json
from npycli import CLI, Command, CLIError, EmptyEntriesError
from npycli.command import cmd
from npycli.errors import causes
from npycli.kwarg_aliasing import alias_cmd_kwargs
from npycli.parameters import CommandParameter
USER_ITEMS_FILE = 'user-items.gitignore.json'
TAB_WIDTH: int = 4
cli = CLI('user-items')
def open_user_items_file(mode: str) -> IO[Any]:
return open(USER_ITEMS_FILE, mode, encoding='utf-8')
__items__: Optional[dict[str, str]] = None
def items() -> dict[str, str]:
global __items__
if __items__ is not None:
return __items__
if isfile(USER_ITEMS_FILE):
with open_user_items_file('r') as f:
__items__ = json.loads(f.read())
else:
__items__ = {}
return items()
def commit() -> dict[str, str]:
if __items__ is None:
raise RuntimeError('Program error: __items__ should never be None when committing')
with open_user_items_file('w') as f:
f.write(json.dumps(__items__, indent=4))
return __items__
@cli.cmd(names=('add', 'new'), help='Add a new key-value pair.')
@alias_cmd_kwargs({'key': ('k',), 'value': ('v',)})
def add_cmd(key: str, value: str) -> str:
if key in items():
raise KeyError(f'Key {key} already exists.')
items()[key] = value
commit()
return f'{key}:{value}'
@cli.cmd(names=('set', 'change'), help='Change an the existing value of a key.')
@alias_cmd_kwargs({'key': ('k',), 'value': ('v',)})
def set_cmd(key: str, value: str) -> str:
if key not in items():
raise KeyError(f'Key {key} does not exist. Use {cmd(add_cmd).name}.')
items()[key] = value
commit()
return f'{key}:{value}'
@cli.cmd(names=('delete', 'del', 'remove', 'rm'), help='Delete an existing key-value pair.')
@alias_cmd_kwargs({'key': ('k',)})
def delete_cmd(key: str) -> str:
value: str = items().pop(key)
commit()
return f'Deleted:\n{key}:{value}'
@cli.cmd(names=('show', 'print'), help='Show a specific or all existing key-value pair(s).')
@alias_cmd_kwargs({'key': ('k',)})
def show_cmd(key: Optional[str] = None) -> str:
if key is None:
longest_length: int = max(len(s) for s in items().keys())
tab_count: int = ceil(longest_length / TAB_WIDTH)
result: str = ''
for k, v in items().items():
result += f'{k + ':': <{tab_count * TAB_WIDTH}}{v}\n'
return result
else:
return f'{key}:{items()[key]}'
@cli.cmd(
name='help',
help=(
"<...>: Positional or Keyword\n"
"<*...>: Variable Positional\n"
"<**...>: Variable Keyword\n"
"/: Positional / Keyword Only Delimiter\n"
"<: ...>: Type\n"
"<: = ...>: Default Value\n"
"[...]: Boolean Flag (Keyword only, no value needed)\n"
)
)
def help_cmd(
command_name: Optional[str] = None,
parameter_name: Optional[str] = None,
/,
extended: bool = False
) -> str:
if extended:
command_help, parameter_help = Command.extended_command_help, CommandParameter.extended_parameter_help
else:
command_help, parameter_help = Command.basic_command_help, CommandParameter.basic_parameter_help
if command_name is None:
out: str = ""
for i, command in enumerate(cli.commands):
out += f"{command_help(command)}"
if i != len(cli.commands) - 1:
out += "\n"
return out
if (command := cli.get_command(command_name)) is None:
return f"{command_name} is not a command."
if parameter_name is not None:
if (parameter := next(filter(lambda p: parameter_name in p.names, command.parameters)), None) is None: # type: ignore
return f"'{parameter_name}' is not a parameter"
return parameter_help(parameter)
return command_help(command)
@cli.retvals()
def retvals(command: Command, return_value: Optional[Any]) -> Optional[Any]:
if return_value is None:
return
print(f'{command.name}:\n{return_value}')
@cli.errors()
def errors(command: Command, exc: Exception) -> Optional[Any]:
errors_string: str = "\n".join([f"{' ' * TAB_WIDTH}{e.__class__.__name__}: {e}" for e in causes(exc, True)])
print(f'{command.name} error:\n{errors_string}')
def as_prompter() -> None:
print('Program style: as_prompter')
while True:
try:
cli.prompt()
except KeyboardInterrupt:
print("\n^C")
return
except EmptyEntriesError: # If the user entered nothing, just continue
pass
except CLIError as err: # Catch errors that occur between here and execution of command
print(f'{err.__class__.__name__}: {err.args[0]}')
def as_cli_program() -> None:
print('Program style: as_cli_program')
from sys import argv
try:
cli.exec(argv[1:])
except EmptyEntriesError: # If the user entered nothing, just continue
pass
except CLIError as err: # Catch errors that occur between here and execution of command
print(f'{err.__class__.__name__}: {err.args[0]}')
if __name__ == '__main__':
from inspect import currentframe, getframeinfo
print(
f'Comment/uncomment a function below (line {getframeinfo(currentframe() or FrameType()).lineno}) to use to select the style of program.',
end='')
# This function will use the arguments passed into `argv` as arguments.
# as_cli_program()
# This function will use a `while true` loop to prompt the user with text entry.
as_prompter()