141 lines
3.9 KiB
Python
Executable File
141 lines
3.9 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
calc - Quick calculator and unit converter
|
|
|
|
Math and conversions from the command line.
|
|
"""
|
|
|
|
import sys
|
|
import math
|
|
from datetime import datetime, timedelta
|
|
|
|
# Common conversions
|
|
CONVERSIONS = {
|
|
# Length
|
|
'km_to_mi': lambda x: x * 0.621371,
|
|
'mi_to_km': lambda x: x * 1.60934,
|
|
'm_to_ft': lambda x: x * 3.28084,
|
|
'ft_to_m': lambda x: x * 0.3048,
|
|
'in_to_cm': lambda x: x * 2.54,
|
|
'cm_to_in': lambda x: x / 2.54,
|
|
|
|
# Weight
|
|
'kg_to_lb': lambda x: x * 2.20462,
|
|
'lb_to_kg': lambda x: x / 2.20462,
|
|
'oz_to_g': lambda x: x * 28.3495,
|
|
'g_to_oz': lambda x: x / 28.3495,
|
|
|
|
# Temperature
|
|
'c_to_f': lambda x: x * 9/5 + 32,
|
|
'f_to_c': lambda x: (x - 32) * 5/9,
|
|
|
|
# Volume
|
|
'l_to_gal': lambda x: x * 0.264172,
|
|
'gal_to_l': lambda x: x * 3.78541,
|
|
|
|
# Data
|
|
'mb_to_gb': lambda x: x / 1024,
|
|
'gb_to_mb': lambda x: x * 1024,
|
|
'gb_to_tb': lambda x: x / 1024,
|
|
'tb_to_gb': lambda x: x * 1024,
|
|
}
|
|
|
|
def calculate(expr: str):
|
|
"""Evaluate a math expression."""
|
|
# Add math functions to namespace
|
|
namespace = {
|
|
'sqrt': math.sqrt,
|
|
'sin': math.sin,
|
|
'cos': math.cos,
|
|
'tan': math.tan,
|
|
'log': math.log,
|
|
'log10': math.log10,
|
|
'exp': math.exp,
|
|
'pi': math.pi,
|
|
'e': math.e,
|
|
'abs': abs,
|
|
'pow': pow,
|
|
'round': round,
|
|
}
|
|
|
|
try:
|
|
result = eval(expr, {"__builtins__": {}}, namespace)
|
|
print(f"= {result}")
|
|
except Exception as e:
|
|
print(f"Error: {e}")
|
|
|
|
def convert(value: float, conversion: str):
|
|
"""Convert between units."""
|
|
if conversion in CONVERSIONS:
|
|
result = CONVERSIONS[conversion](value)
|
|
units = conversion.split('_to_')
|
|
print(f"{value} {units[0]} = {result:.4f} {units[1]}")
|
|
else:
|
|
print(f"Unknown conversion: {conversion}")
|
|
print("Available:", ', '.join(CONVERSIONS.keys()))
|
|
|
|
def time_until(target: str):
|
|
"""Calculate time until a date."""
|
|
try:
|
|
if len(target) == 10: # YYYY-MM-DD
|
|
target_date = datetime.strptime(target, "%Y-%m-%d")
|
|
else:
|
|
target_date = datetime.strptime(target, "%Y-%m-%d %H:%M")
|
|
|
|
diff = target_date - datetime.now()
|
|
|
|
if diff.total_seconds() < 0:
|
|
print("That date has passed")
|
|
return
|
|
|
|
days = diff.days
|
|
hours, rem = divmod(diff.seconds, 3600)
|
|
minutes = rem // 60
|
|
|
|
print(f"Time until {target}:")
|
|
print(f" {days} days, {hours} hours, {minutes} minutes")
|
|
except:
|
|
print("Date format: YYYY-MM-DD or YYYY-MM-DD HH:MM")
|
|
|
|
def percentage(part: float, whole: float):
|
|
"""Calculate percentage."""
|
|
pct = (part / whole) * 100
|
|
print(f"{part} / {whole} = {pct:.2f}%")
|
|
|
|
def main():
|
|
if len(sys.argv) < 2:
|
|
print("Usage:")
|
|
print(" calc <expression> - Math calculation")
|
|
print(" calc <value> <conversion> - Unit conversion")
|
|
print(" calc until <YYYY-MM-DD> - Time until date")
|
|
print(" calc pct <part> <whole> - Percentage")
|
|
print("")
|
|
print("Examples:")
|
|
print(" calc '2 + 2'")
|
|
print(" calc 'sqrt(16) + pi'")
|
|
print(" calc 100 km_to_mi")
|
|
print(" calc 72 f_to_c")
|
|
print(" calc until 2026-12-31")
|
|
print("")
|
|
print("Conversions:", ', '.join(sorted(CONVERSIONS.keys())))
|
|
return
|
|
|
|
cmd = sys.argv[1]
|
|
|
|
if cmd == 'until' and len(sys.argv) > 2:
|
|
time_until(' '.join(sys.argv[2:]))
|
|
|
|
elif cmd == 'pct' and len(sys.argv) > 3:
|
|
percentage(float(sys.argv[2]), float(sys.argv[3]))
|
|
|
|
elif len(sys.argv) == 3 and sys.argv[2] in CONVERSIONS:
|
|
convert(float(sys.argv[1]), sys.argv[2])
|
|
|
|
else:
|
|
# Treat as expression
|
|
expr = ' '.join(sys.argv[1:])
|
|
calculate(expr)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|