You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

120 lines
3.6 KiB

"""Implement Mach5 menus."""
import os
import errno
import yaml
from collections import OrderedDict
import subprocess
def get_data_from(menu_file):
"""Get menu data from a yaml menu definition file."""
menu_file = os.path.expanduser(menu_file)
if not os.path.exists(menu_file):
raise FileNotFoundError(errno.ENOENT,
os.strerror(errno.ENOENT), menu_file)
with open(menu_file, 'r') as yaml_file:
menu_data = yaml.safe_load(yaml_file)
#######################################################################
# WARNING #
# yaml.safe_load does NOT return and OrderedDict #
# so far, dict in Python are ordered, but IT CAN CHANGE #
#######################################################################
if 'entries' not in menu_data:
raise ValueError(f"Malformed file {menu_file}.\
First entry is not a menu")
return menu_data
class Command:
"""A Mach5 command."""
def __init__(self, cmd_data):
"""Construct a Mach5 command object."""
if len(cmd_data['sc']) != 1:
raise ValueError(f"Shortcut must be only one character.\
{cmd_data['sc']} is invalid.")
self._name = cmd_data['name']
self._cmd = cmd_data['cmd']
self._sc = cmd_data['sc']
def show(self):
"""Return string for Command."""
return f"{self._sc} - {self._name}"
def __repr__(self):
"""Return representation string for Command object."""
return f"{self._sc!r} - {self._name!r} - {self._cmd!r}"
def __str__(self):
"""Return string conversion for command."""
return f"{self._sc} - {self._name}"
def execute(self):
"""Execute command."""
print(f"Execute command {self._name}")
subprocess.Popen(os.path.expanduser(self._cmd))
class Menu:
"""A Mach5 menu class."""
def __init__(self, menu_data):
"""Init a menu object instance from a menu data dict."""
self._name = menu_data['name']
self._entries = OrderedDict()
self._sc = menu_data['sc']
for e in menu_data['entries']:
if e['sc'] in self._entries:
raise ValueError(f"Repeated shortcut {e['sc']}")
if 'entries' in e:
self._entries[e['sc']] = Menu(e)
else:
self._entries[e['sc']] = Command(e)
def __len__(self):
"""Return number of items in menu."""
return len(self._entries)
def __iter__(self):
return MenuIter(self)
def from_file(path):
"""Return a Menu instance from menu definition file."""
return Menu(get_data_from(path))
def __repr__(self):
"""Return repr string for menu object."""
return f"{self._sc!r} - {self._name!r}"
def __str__(self):
"""Return string conversion for menu."""
if self._sc:
return f"{self._sc} - {self._name}"
else:
return f"{self._name}"
def get_entries(self):
"""Return entries for menu instance."""
return self._entries
class MenuIter:
"""A helper class to make iterables Menu objects"""
def __init__(self, menu):
"""Return an iterable menu"""
self._entries = menu.get_entries()
self._current = 0
def __iter__(self):
return self
def __next__(self):
if self._current < len(self._entries):
item = self._entries[self._current]
self._current += 1
return item
raise StopIteration