diff --git a/useful_programs/README.md b/useful_programs/README.md new file mode 100644 index 0000000..6807156 --- /dev/null +++ b/useful_programs/README.md @@ -0,0 +1,17 @@ +# Useful Programs + +Small collection of handy CLI utilities. + +Files: +- `cli_calculator.py` — safe arithmetic evaluator +- `file_organizer.py` — move files into folders by extension +- `json_pretty.py` — pretty-print JSON from file or stdin +- `simple_http_server.py` — serve current directory over HTTP +- `smoke_test.py` — quick checks to verify the scripts run + +Usage examples: + +python cli_calculator.py "(2+3)*4" +python file_organizer.py /path/to/folder +cat file.json | python json_pretty.py +python simple_http_server.py 8080 diff --git a/useful_programs/cli_calculator.py b/useful_programs/cli_calculator.py new file mode 100644 index 0000000..bda0d68 --- /dev/null +++ b/useful_programs/cli_calculator.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +"""Simple CLI calculator: supports + - * / and parentheses via eval with safety.""" +import ast +import operator as op +import sys + +ALLOWED_OPERATORS = { + ast.Add: op.add, + ast.Sub: op.sub, + ast.Mult: op.mul, + ast.Div: op.truediv, + ast.Pow: op.pow, + ast.USub: op.neg, +} + + +def eval_expr(expr: str): + """Safely evaluate a math expression using AST.""" + def _eval(node): + if isinstance(node, ast.Num): + return node.n + if isinstance(node, ast.UnaryOp) and type(node.op) in ALLOWED_OPERATORS: + return ALLOWED_OPERATORS[type(node.op)](_eval(node.operand)) + if isinstance(node, ast.BinOp) and type(node.op) in ALLOWED_OPERATORS: + return ALLOWED_OPERATORS[type(node.op)](_eval(node.left), _eval(node.right)) + raise ValueError(f"Unsupported expression: {ast.dump(node)}") + + parsed = ast.parse(expr, mode="eval") + return _eval(parsed.body) + + +def main(): + if len(sys.argv) > 1: + expr = " ".join(sys.argv[1:]) + else: + expr = input("Enter expression: ") + try: + result = eval_expr(expr) + print(result) + except Exception as e: + print("Error:", e) + + +if __name__ == "__main__": + main() diff --git a/useful_programs/file_organizer.py b/useful_programs/file_organizer.py new file mode 100644 index 0000000..e6d1c3d --- /dev/null +++ b/useful_programs/file_organizer.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +"""Organize files in a directory into subfolders by extension. + +Usage: python file_organizer.py [target_dir] +""" +import os +import shutil +import sys + + +def organize(target_dir: str): + if not os.path.isdir(target_dir): + raise ValueError(f"Not a directory: {target_dir}") + for entry in os.listdir(target_dir): + full = os.path.join(target_dir, entry) + if os.path.isfile(full): + _, ext = os.path.splitext(entry) + ext = ext[1:].lower() or "no_ext" + dest_dir = os.path.join(target_dir, ext) + os.makedirs(dest_dir, exist_ok=True) + shutil.move(full, os.path.join(dest_dir, entry)) + + +def main(): + target = sys.argv[1] if len(sys.argv) > 1 else os.getcwd() + organize(target) + print(f"Organized files in {target}") + + +if __name__ == "__main__": + main() diff --git a/useful_programs/json_pretty.py b/useful_programs/json_pretty.py new file mode 100644 index 0000000..724e219 --- /dev/null +++ b/useful_programs/json_pretty.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +"""Read JSON from a file or stdin and pretty-print it.""" +import json +import sys + + +def main(): + if len(sys.argv) > 1: + path = sys.argv[1] + with open(path, "r", encoding="utf-8") as f: + data = json.load(f) + else: + data = json.load(sys.stdin) + print(json.dumps(data, indent=2, ensure_ascii=False)) + + +if __name__ == "__main__": + main() diff --git a/useful_programs/simple_http_server.py b/useful_programs/simple_http_server.py new file mode 100644 index 0000000..dfef105 --- /dev/null +++ b/useful_programs/simple_http_server.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +"""Start a simple HTTP server serving the current directory. + +Usage: python simple_http_server.py [port] +""" +import http.server +import socketserver +import sys + + +def main(): + port = int(sys.argv[1]) if len(sys.argv) > 1 else 8000 + handler = http.server.SimpleHTTPRequestHandler + with socketserver.TCPServer(("", port), handler) as httpd: + print(f"Serving HTTP on 0.0.0.0:{port} (Ctrl-C to stop)") + try: + httpd.serve_forever() + except KeyboardInterrupt: + print("\nServer stopped") + + +if __name__ == "__main__": + main() diff --git a/useful_programs/smoke_test.py b/useful_programs/smoke_test.py new file mode 100644 index 0000000..e080611 --- /dev/null +++ b/useful_programs/smoke_test.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +"""Run quick smoke tests for the useful_programs utilities.""" +import subprocess +import sys +import json +from pathlib import Path + + +ROOT = Path(__file__).parent + + +def run(cmd, input_text=None): + p = subprocess.run([sys.executable, *cmd], input=input_text, text=True, capture_output=True) + return p.returncode, p.stdout, p.stderr + + +def test_calc(): + code, out, err = run([str(ROOT / "cli_calculator.py"), "(2+3)*4"]) + assert code == 0 and out.strip() == "20" + + +def test_json(): + js = '{"a":1, "b":[1,2]}' + code, out, err = run([str(ROOT / "json_pretty.py")], input_text=js) + assert code == 0 + data = json.loads(out) + assert data["a"] == 1 + + +def test_file_organizer(tmp_dir: Path): + d = tmp_dir / "test_org" + d.mkdir(exist_ok=True) + f = d / "sample.txt" + f.write_text("hello") + code, out, err = run([str(ROOT / "file_organizer.py"), str(d)]) + assert code == 0 + assert (d / "txt" / "sample.txt").exists() + + +def main(): + print("Running smoke tests...") + test_calc() + print("calc ok") + test_json() + print("json ok") + # file organizer needs a temp dir; create one in /tmp + from tempfile import TemporaryDirectory + from pathlib import Path + with TemporaryDirectory() as td: + test_file_organizer(Path(td)) + print("file organizer ok") + print("All smoke tests passed") + + +if __name__ == "__main__": + main()