#!/usr/bin/env python3 """Calctool — terminal calculator with expression evaluation, unit conversion, and more.""" import argparse, sys, math, operator, re def color(text, code): return f"\033[{code}m{text}\033[0m" if sys.stdout.isatty() else text SAFE_OPS = { "+": operator.add, "-": operator.sub, "*": operator.mul, "/": operator.truediv, "//": operator.floordiv, "%": operator.mod, "**": operator.pow, "abs": abs, "round": round, "int": int, "float": float, "sqrt": math.sqrt, "sin": math.sin, "cos": math.cos, "tan": math.tan, "log": math.log, "log10": math.log10, "exp": math.exp, "ceil": math.ceil, "floor": math.floor, "pi": math.pi, "e": math.e, "tau": math.tau, "deg": math.degrees, "rad": math.radians, "factorial": math.factorial, } def safe_eval(expr): expr = expr.replace("^", "**") try: result = eval(expr, {"__builtins__": {}}, dict(SAFE_OPS)) return result except Exception as e: return f"Error: {e}" TEMPERATURE_UNITS = { "c": ("Celsius", lambda x: x), "f": ("Fahrenheit", lambda x: x), "k": ("Kelvin", lambda x: x), } LENGTH_UNITS = { "m": ("meters", 1), "km": ("km", 0.001), "cm": ("cm", 100), "mm": ("mm", 1000), "mi": ("miles", 0.000621371), "yd": ("yards", 1.09361), "ft": ("feet", 3.28084), "in": ("inches", 39.3701), } WEIGHT_UNITS = { "kg": ("kg", 1), "g": ("g", 1000), "mg": ("mg", 1_000_000), "lb": ("lbs", 2.20462), "oz": ("oz", 35.274), } AREA_UNITS = { "m2": ("m²", 1), "km2": ("km²", 0.000001), "ha": ("hectares", 0.0001), "acre": ("acres", 0.000247105), "ft2": ("ft²", 10.7639), } def convert_temp(value, from_unit, to_unit): # Convert to Celsius first if from_unit == "f": celsius = (value - 32) * 5 / 9 elif from_unit == "k": celsius = value - 273.15 else: celsius = value # Convert from Celsius to target if to_unit == "f": return celsius * 9 / 5 + 32 elif to_unit == "k": return celsius + 273.15 else: return celsius def cmd_convert(args): try: value = float(args.value) except ValueError: print(f"Error: Invalid number '{args.value}'", file=sys.stderr) sys.exit(1) if args.type in ("temp", "temperature"): result = convert_temp(value, args.from_unit, args.to_unit) print(f" {value}°{args.from_unit.upper()} = {color(f'{result:.2f}°{args.to_unit.upper()}', '32')}") elif args.type in ("len", "length"): unit_map = LENGTH_UNITS if args.from_unit in unit_map and args.to_unit in unit_map: base_value = value / unit_map[args.from_unit][1] result = base_value * unit_map[args.to_unit][1] print(f" {value} {unit_map[args.from_unit][0]} = {color(f'{result:.4f}', '32')} {unit_map[args.to_unit][0]}") else: print("Error: Unknown length unit", file=sys.stderr) elif args.type in ("weight", "mass"): unit_map = WEIGHT_UNITS if args.from_unit in unit_map and args.to_unit in unit_map: base_value = value / unit_map[args.from_unit][1] result = base_value * unit_map[args.to_unit][1] print(f" {value} {unit_map[args.from_unit][0]} = {color(f'{result:.4f}', '32')} {unit_map[args.to_unit][0]}") else: print("Error: Unknown weight unit", file=sys.stderr) elif args.type in ("area",): unit_map = AREA_UNITS if args.from_unit in unit_map and args.to_unit in unit_map: base_value = value / unit_map[args.from_unit][1] result = base_value * unit_map[args.to_unit][1] print(f" {value} {unit_map[args.from_unit][0]} = {color(f'{result:.4f}', '32')} {unit_map[args.to_unit][0]}") else: print("Error: Unknown area unit", file=sys.stderr) def cmd_calc(args): expr = " ".join(args.expression) result = safe_eval(expr) if isinstance(result, str) and result.startswith("Error"): print(f" {color(result, '31')}", file=sys.stderr) sys.exit(1) if isinstance(result, float): if result == int(result): print(f" {expr} = {color(str(int(result)), '36;1')}") else: print(f" {expr} = {color(f'{result:.6f}'.rstrip('0').rstrip('.'), '36;1')}") else: print(f" {expr} = {color(str(result), '36;1')}") def main(): parser = argparse.ArgumentParser( description="Calctool — terminal calculator and converter", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=( "Examples:\n" " calctool calc '2 + 2'\n" " calctool calc 'sqrt(144) + pi'\n" " calctool convert temp 100 c f\n" " calctool convert length 10 m ft\n" " calctool convert weight 5 kg lb\n" ), ) parser.add_argument("--version", action="version", version="calctool 1.0.0") sub = parser.add_subparsers(dest="command", required=True) c = sub.add_parser("calc", help="Evaluate mathematical expression") c.add_argument("expression", nargs="+", help="Expression to evaluate") conv = sub.add_parser("convert", help="Convert between units") conv.add_argument("type", choices=["temp", "temperature", "len", "length", "weight", "mass", "area"], help="Conversion type") conv.add_argument("value", help="Numeric value") conv.add_argument("from_unit", help="Source unit") conv.add_argument("to_unit", help="Target unit") args = parser.parse_args() if args.command == "calc": cmd_calc(args) elif args.command == "convert": cmd_convert(args) if __name__ == "__main__": main()