Files
Modus/modules/launcher/plugins/calculator.py
T

158 lines
5.3 KiB
Python

"""
Calculator plugin for the launcher.
Evaluates mathematical expressions and converts units.
"""
import math
import re
import subprocess
import time
from typing import List
import utils.icons as icons
from modules.launcher.plugin_base import PluginBase
from modules.launcher.result import Result
from utils.conversion import Conversion
class CalculatorPlugin(PluginBase):
"""
Plugin for calculating mathematical expressions and converting units.
"""
def __init__(self):
super().__init__()
self.display_name = "Calculator"
self.description = "Evaluate mathematical expressions and convert units"
# Safe functions for evaluation
self.safe_functions = {
"abs": abs,
"round": round,
"min": min,
"max": max,
"sum": sum,
"pow": pow,
"sqrt": math.sqrt,
"sin": math.sin,
"cos": math.cos,
"tan": math.tan,
"asin": math.asin,
"acos": math.acos,
"atan": math.atan,
"log": math.log,
"log10": math.log10,
"exp": math.exp,
"pi": math.pi,
"e": math.e,
}
# Initialize conversion utility
self.converter = Conversion()
# Pre-compiled regex patterns
self.expression_pattern = re.compile(r"[\d+\-*/^()=]")
self.number_pattern = re.compile(r"\d")
self.conversion_pattern = re.compile(
r"(\d+(?:\.\d+)?)\s*([a-zA-Z]+)\s*(?:to|in|=)\s*([a-zA-Z]+)"
)
# Cache for conversion results
self._conversion_cache = {}
self._last_cache_cleanup = time.time()
self._cache_cleanup_interval = 300 # 5 minutes
def initialize(self):
"""Initialize the files plugin."""
self.set_triggers(["=", "= "])
def _cleanup_cache(self):
"""Clean up old cache entries."""
current_time = time.time()
if current_time - self._last_cache_cleanup > self._cache_cleanup_interval:
self._conversion_cache.clear()
self._last_cache_cleanup = current_time
def query(self, query: str) -> List[Result]:
"""Process a query and return results."""
if not query:
return []
# Clean up cache periodically
self._cleanup_cache()
# Check if it's a conversion query
conversion_match = self.conversion_pattern.match(query)
if conversion_match:
try:
value, from_unit, to_unit = conversion_match.groups()
value = float(value)
# Check cache first
cache_key = f"{value}_{from_unit}_{to_unit}"
if cache_key in self._conversion_cache:
result = self._conversion_cache[cache_key]
else:
# Use the conversion utility
result = self.converter.convert(value, from_unit, to_unit)
# Cache the result
self._conversion_cache[cache_key] = result
return [
Result(
title=f"{result:.6g} {to_unit}",
subtitle=f"{value} {from_unit} = {result:.6g} {to_unit}",
icon_markup=icons.calculator,
action=lambda r=f"{result:.6g}": self._copy_to_clipboard(r),
relevance=1.0,
plugin_name=self.display_name,
data={"from": (value, from_unit), "to": (result, to_unit)},
)
]
except ValueError as e:
return [
Result(
title="Invalid conversion",
subtitle=str(e),
icon_markup=icons.calculator,
relevance=0.0,
plugin_name=self.display_name,
)
]
# Check if it's a math expression
if self.expression_pattern.search(query):
try:
# Evaluate the expression
result = eval(query, {"__builtins__": {}}, self.safe_functions)
if isinstance(result, (int, float)):
return [
Result(
title=f"{result:.6g}",
subtitle=f"{query} = {result:.6g}",
icon_markup=icons.calculator,
action=lambda r=f"{result:.6g}": self._copy_to_clipboard(r),
relevance=1.0,
plugin_name=self.display_name,
)
]
except Exception:
pass
return []
def _copy_to_clipboard(self, text: str):
"""Copy text to clipboard using cliphist."""
try:
# First copy to clipboard
subprocess.run(["wl-copy"], input=text.encode(), check=True)
# Then store in cliphist
subprocess.run(["cliphist", "store"], input=text.encode(), check=True)
except subprocess.CalledProcessError:
# If cliphist fails, at least we have the text in clipboard
pass
def cleanup(self):
"""Clean up resources."""
self._conversion_cache.clear()