|
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 |
210 | 2 |
|
211 | 3 | if __name__ == "__main__":
|
212 | 4 | main()
|
0 commit comments