Skip to content

Commit 34ad62b

Browse files
Refactor CLI as a submodule with cli.py broken into smaller modules (#87)
1 parent 6f62cf9 commit 34ad62b

15 files changed

+834
-794
lines changed

Diff for: covert/__main__.py

+1-209
Original file line numberDiff line numberDiff line change
@@ -1,212 +1,4 @@
1-
import os
2-
import sys
3-
from typing import NoReturn
4-
5-
import colorama
6-
7-
from covert.cli import main_benchmark, main_dec, main_edit, main_enc, main_id
8-
from covert.clihelp import print_help, print_version
9-
10-
11-
class Args:
12-
13-
def __init__(self):
14-
self.mode = None
15-
self.idname = ""
16-
self.files = []
17-
self.wideopen = None
18-
self.askpass = 0
19-
self.passwords = []
20-
self.recipients = []
21-
self.recipfiles = []
22-
self.outfile = []
23-
self.identities = []
24-
self.padding = "5"
25-
self.armor = None
26-
self.paste = None
27-
self.debug = None
28-
self.delete_entire_idstore = False
29-
self.delete = False
30-
self.secret = False
31-
32-
33-
encargs = dict(
34-
idname='-I --id'.split(),
35-
askpass='-p --passphrase'.split(),
36-
passwords='--password'.split(),
37-
wideopen='--wide-open'.split(),
38-
recipients='-r --recipient'.split(),
39-
recipfiles='-R --keyfile --recipients-file'.split(),
40-
identities='-i --identity'.split(),
41-
outfile='-o --out --output'.split(),
42-
armor='-a --armor'.split(),
43-
paste='-A'.split(),
44-
padding='--pad --padding'.split(),
45-
debug='--debug'.split(),
46-
)
47-
48-
decargs = dict(
49-
idname='-I --id'.split(),
50-
askpass='-p --passphrase'.split(),
51-
passwords='--password'.split(),
52-
identities='-i --identity'.split(),
53-
outfile='-o --out --output'.split(),
54-
paste='-A'.split(),
55-
debug='--debug'.split(),
56-
)
57-
58-
idargs = dict(
59-
askpass='-p --passphrase'.split(),
60-
recipients='-r --recipient'.split(),
61-
recipfiles='-R --keyfile --recipients-file'.split(),
62-
identities='-i --identity'.split(),
63-
secret='-s --secret'.split(),
64-
delete_entire_idstore='--delete-entire-idstore'.split(),
65-
delete='-D --delete'.split(),
66-
debug='--debug'.split(),
67-
)
68-
69-
editargs = dict(debug='--debug'.split(),)
70-
benchargs = dict(debug='--debug'.split(),)
71-
72-
# TODO: Put mode args and help here as well
73-
modes = {
74-
"enc": main_enc,
75-
"dec": main_dec,
76-
"edit": main_edit,
77-
"id": main_id,
78-
"benchmark": main_benchmark,
79-
}
80-
81-
def needhelp(av):
82-
"""Check for -h and --help but not past --"""
83-
for a in av:
84-
if a == '--': return False
85-
if a.lower() in ('-h', '--help'): return True
86-
return False
87-
88-
def subcommand(arg):
89-
if arg in ('enc', 'encrypt', '-e'): return 'enc', encargs
90-
if arg in ('dec', 'decrypt', '-d'): return 'dec', decargs
91-
if arg in ('edit'): return 'edit', editargs
92-
if arg in ('id'): return 'id', idargs
93-
if arg in ('bench', 'benchmark'): return 'benchmark', benchargs
94-
if arg in ('help', ): return 'help', {}
95-
return None, {}
96-
97-
def argparse():
98-
# Custom parsing due to argparse module's limitations
99-
av = sys.argv[1:]
100-
if not av:
101-
print_help()
102-
103-
if any(a.lower() in ('-v', '--version') for a in av):
104-
print_version()
105-
106-
args = Args()
107-
# Separate mode selector from other arguments
108-
if av[0].startswith("-") and len(av[0]) > 2 and not needhelp(av):
109-
av.insert(1, f'-{av[0][2:]}')
110-
av[0] = av[0][:2]
111-
112-
args.mode, ad = subcommand(av[0])
113-
114-
if args.mode == 'help' or needhelp(av):
115-
if args.mode == 'help' and len(av) == 2 and (mode := subcommand(av[1])[0]):
116-
print_help(mode)
117-
print_help(args.mode or "help")
118-
119-
if args.mode is None:
120-
sys.stderr.write(' 💣 Invalid or missing command (enc/dec/edit/id/benchmark/help).\n')
121-
sys.exit(1)
122-
123-
aiter = iter(av[1:])
124-
longargs = [flag[1:] for switches in ad.values() for flag in switches if flag.startswith("--")]
125-
shortargs = [flag[1:] for switches in ad.values() for flag in switches if not flag.startswith("--")]
126-
for a in aiter:
127-
aprint = a
128-
if not a.startswith('-'):
129-
args.files.append(a)
130-
continue
131-
if a == '-':
132-
args.files.append(True)
133-
continue
134-
if a == '--':
135-
args.files += aiter
136-
break
137-
if a.startswith('--'):
138-
a = a.lower()
139-
if not a.startswith('--') and len(a) > 2:
140-
if any(arg not in shortargs for arg in list(a[1:])):
141-
falseargs = [arg for arg in list(a[1:]) if arg not in shortargs]
142-
print_help(args.mode, f' 💣 Unknown argument: covert {args.mode} {a} (failing -{" -".join(falseargs)})')
143-
a = [f'-{shortarg}' for shortarg in list(a[1:]) if shortarg in shortargs]
144-
if isinstance(a, str):
145-
a = [a]
146-
for i, av in enumerate(a):
147-
argvar = next((k for k, v in ad.items() if av in v), None)
148-
if isinstance(av, int):
149-
continue
150-
if argvar is None:
151-
print_help(args.mode, f' 💣 Unknown argument: covert {args.mode} {aprint}')
152-
try:
153-
var = getattr(args, argvar)
154-
if isinstance(var, list):
155-
var.append(next(aiter))
156-
elif isinstance(var, str):
157-
setattr(args, argvar, next(aiter))
158-
elif isinstance(var, int):
159-
setattr(args, argvar, var + 1)
160-
else:
161-
setattr(args, argvar, True)
162-
except StopIteration:
163-
print_help(args.mode, f' 💣 Argument parameter missing: covert {args.mode} {aprint} …')
164-
165-
return args
166-
167-
168-
def main() -> NoReturn:
169-
"""
170-
The main CLI entry point.
171-
172-
Consider calling covert.cli.main* or other modules directly if you use from Python code.
173-
174-
System exit codes:
175-
* 0 The requested function was completed successfully
176-
* 1 CLI argument error
177-
* 2 I/O error (broken pipe, not other types currently)
178-
* 10-99 Normal errors, authentication failures, corrupted data, ... (currently 10 for all)
179-
180-
:raises SystemExit: on normal exit or any expected error, including KeyboardInterrupt
181-
:raises Exception: on unexpected error (report a bug), or on any error with `--debug`
182-
"""
183-
colorama.init()
184-
# CLI argument processing
185-
args = argparse()
186-
if len(args.outfile) > 1:
187-
raise ValueError('Only one output file may be specified')
188-
args.outfile = args.outfile[0] if args.outfile else None
189-
190-
# A quick sanity check, not entirely reliable
191-
if args.outfile in args.files:
192-
raise ValueError('In-place operation is not supported, cannot use the same file as input and output.')
193-
194-
# Run the mode-specific main function
195-
if args.debug:
196-
modes[args.mode](args) # --debug makes us not catch errors
197-
sys.exit(0)
198-
try:
199-
modes[args.mode](args) # Normal run
200-
except ValueError as e:
201-
sys.stderr.write(f"Error: {e}\n")
202-
sys.exit(10)
203-
except BrokenPipeError:
204-
sys.stderr.write('I/O error (broken pipe)\n')
205-
sys.exit(3)
206-
except KeyboardInterrupt:
207-
sys.stderr.write("Interrupted.\n")
208-
sys.exit(2)
209-
sys.exit(0)
1+
from covert.cli.__main__ import main
2102

2113
if __name__ == "__main__":
2124
main()

0 commit comments

Comments
 (0)