Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions useful_programs/README.md
Original file line number Diff line number Diff line change
@@ -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
45 changes: 45 additions & 0 deletions useful_programs/cli_calculator.py
Original file line number Diff line number Diff line change
@@ -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()
31 changes: 31 additions & 0 deletions useful_programs/file_organizer.py
Original file line number Diff line number Diff line change
@@ -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()
18 changes: 18 additions & 0 deletions useful_programs/json_pretty.py
Original file line number Diff line number Diff line change
@@ -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()
23 changes: 23 additions & 0 deletions useful_programs/simple_http_server.py
Original file line number Diff line number Diff line change
@@ -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()
56 changes: 56 additions & 0 deletions useful_programs/smoke_test.py
Original file line number Diff line number Diff line change
@@ -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()