Coverage for kryptic_cypher/app.py: 85%
93 statements
« prev ^ index » next coverage.py v7.14.2, created at 2026-06-20 18:52 +0000
« prev ^ index » next coverage.py v7.14.2, created at 2026-06-20 18:52 +0000
1"""
2Module that contains the click entrypoint for our cli interface.
4This is currently only for encoding and decoding data using the encode and decode commands.
5"""
7import base64
8from io import BytesIO
9from logging import basicConfig
10import os
11import click
12from kryptic_cypher.cypher.base import CypherResult
13from .cypher import Cypher, CypherWithKey, register_all_cyphers, registered_cyphers
16@click.group()
17@click.pass_context
18def main(ctx: click.Context):
19 """
20 main group that represents the top-level: ***zombie-nomnom***
22 This will be used to decorate sub-commands for zombie-nomnom.
24 ***Example Usage:***
25 ```python
26 @main.command("sub-command")
27 def sub_command():
28 # do actual meaningful work.
29 pass
30 ```
31 """
32 basicConfig(level=os.environ.get("LOG_LEVEL", "INFO").upper())
33 register_all_cyphers()
36def resolve_cypher(
37 cypher: str,
38 text: str,
39 input: str,
40 key: str,
41) -> Cypher | CypherWithKey:
42 if not text and not input:
43 raise click.ClickException("You must specify either -t or -i")
45 cypher_instance = registered_cyphers.get(cypher, None)
47 if not cypher_instance:
48 raise click.ClickException(
49 f"Invalid cypher: {cypher}, {', '.join(registered_cyphers.keys())}"
50 )
52 if isinstance(cypher_instance, CypherWithKey):
53 if not key:
54 raise click.ClickException("You must specify -k")
55 result = cypher_instance.validate_key(key)
56 if not result.success:
57 raise click.ClickException("\n".join(result.messages))
59 return cypher_instance
62def process_output(
63 output: str | None,
64 result: CypherResult,
65):
66 if not result.success:
67 raise click.ClickException(result.error)
69 if output:
70 flags = "w" if isinstance(result.new_text, str) else "wb"
71 with open(output, flags) as f:
72 f.write(result.new_text)
73 else:
74 if isinstance(result.new_text, str):
75 click.echo(result.new_text)
76 else:
77 encoded_text = BytesIO(result.new_text)
78 full_value = encoded_text.read()
79 binary_string = base64.b64encode(full_value).decode("utf-8")
80 click.echo(binary_string)
83def _run_cypher(
84 action: str,
85 cypher: str,
86 text: str,
87 input: str,
88 key: str,
89 output: str | None,
90 binary: bool,
91):
92 """Shared runner for encode/decode click commands.
94 action must be either 'encode' or 'decode'.
95 """
96 action = action.lower()
97 if action not in ("encode", "decode"):
98 raise click.ClickException("action must be 'encode' or 'decode'")
100 cypher_instance = resolve_cypher(cypher, text, input, key)
102 if input:
103 with open(input, "rb" if binary else "r") as f:
104 text = f.read()
106 # Resolve the bound method (`encode` or `decode`) and call it.
107 method = getattr(cypher_instance, action)
108 if isinstance(cypher_instance, CypherWithKey):
109 result = method(text, key)
110 else:
111 result = method(text)
113 process_output(output, result)
116@main.command("encode")
117@click.option(
118 "-c",
119 "--cypher",
120 help="The cypher to use",
121 required=True,
122)
123@click.option("-t", "--text", help="The text to encode", required=False)
124@click.option("-k", "--key", help="The input file to read text from", required=False)
125@click.option("-i", "--input", help="The input file to read text from", required=False)
126@click.option(
127 "-o",
128 "--output",
129 help="The output file to write text to",
130 required=False,
131 type=click.Path(writable=True, dir_okay=False),
132)
133@click.option(
134 "-b",
135 "--binary",
136 help="The output file to write text to",
137 required=False,
138 is_flag=True,
139)
140def encode(
141 cypher: str,
142 text: str,
143 input: str,
144 binary: bool,
145 key: str,
146 output: str | None,
147):
148 """
149 CLI command to encode text using a cypher in our system that will check to make sure the usage is valid i.e. input is given and key is valid if key is required.
150 """
151 _run_cypher(
152 "encode",
153 cypher=cypher,
154 text=text,
155 input=input,
156 key=key,
157 output=output,
158 binary=binary,
159 )
162@main.command("decode")
163@click.option(
164 "-c",
165 "--cypher",
166 help="The cypher to use",
167 required=True,
168)
169@click.option("-t", "--text", help="The text to encode", required=False)
170@click.option("-i", "--input", help="The input file to read text from", required=False)
171@click.option("-k", "--key", help="The input file to read text from", required=False)
172@click.option(
173 "-o",
174 "--output",
175 help="The output file to write text to",
176 required=False,
177 type=click.Path(writable=True, dir_okay=False),
178)
179@click.option(
180 "-b",
181 "--binary",
182 help="The output file to write text to",
183 required=False,
184 is_flag=True,
185)
186def decode(
187 cypher: str,
188 text: str,
189 input: str,
190 key: str,
191 output: str,
192 binary: bool,
193):
194 """
195 CLI command to decode text using a cypher in our system that will check to make sure the usage is valid i.e. input is given and key is valid if key is required.
196 """
197 _run_cypher(
198 "decode",
199 cypher=cypher,
200 text=text,
201 input=input,
202 key=key,
203 output=output,
204 binary=binary,
205 )
208@main.command("list")
209def list_cyphers():
210 for cypher in sorted(registered_cyphers.keys()):
211 click.echo(
212 f"{cypher}: {'keyless' if isinstance(registered_cyphers[cypher], Cypher) else 'keyed'}"
213 )
216@main.command("cypher")
217@click.argument("cypher")
218@click.argument("action", type=click.Choice(["encode", "decode"]))
219@click.argument("text")
220@click.option(
221 "-k",
222 "--key",
223 help="The key to use for the cypher",
224 required=False,
225)
226@click.option(
227 "--binary",
228 "-b",
229 is_flag=True,
230 help="Whether or not input is binary...",
231)
232@click.option(
233 "-o",
234 "--output",
235 help="The output file to write text to",
236 required=False,
237 default=None,
238)
239def cypher(
240 cypher: str,
241 action: str,
242 text: str,
243 key: str,
244 binary: bool,
245 output: str | None,
246):
247 if os.path.exists(text):
248 _run_cypher(action, cypher, None, text, key, output, binary)
249 else:
250 _run_cypher(action, cypher, text, None, key, output, binary)
253try:
254 from kryptic_cypher.bot import run
256 @main.command("bot")
257 @click.option(
258 "--env-file",
259 type=click.Path(
260 exists=True,
261 file_okay=True,
262 dir_okay=False,
263 ),
264 )
265 def run_bot(env_file: str = None):
266 click.echo("Executing Discord Bot...")
267 run(env_file=env_file)
269except ImportError:
270 pass