def parse_old_menu(menu_data): """ Take an old-style menuData dictionary and return a CursesMenu :param dict menu_data: :return: A new CursesMenu :rtype: CursesMenu """ menu_title = menu_data['title'] menu = CursesMenu(menu_title) for item in menu_data["options"]: item_type = item["type"] item_title = item["title"] if item_type == menuItem.COMMAND: item_command = item["command"] menu.append_item(CommandItem(item_title, item_command, menu)) elif item_type == menuItem.FUNCTION: item_function = item["function"] menu.append_item(FunctionItem(item_title, item_function, menu)) elif item_type == menuItem.EXITMENU: menu.append_item(ExitItem(item_title, menu)) elif item_type == menuItem.NUMBER: menu.append_item(SelectionItem(item_title, menu)) elif item_type == menuItem.MENU: new_menu = parse_old_menu(item) menu.append_item(SubmenuItem(item_title, menu, new_menu)) return menu
def test_action(self): root_menu = CursesMenu("root_menu", "test_action") submenu1 = CursesMenu("submenu1", "test_action") submenu2 = CursesMenu("submenu2", "test_action") submenu_item_1 = SubmenuItem("submenu_item_1", submenu1, menu=root_menu) submenu_item_2 = SubmenuItem("submenu_item_2", submenu2, menu=root_menu) root_menu.append_item(submenu_item_1) root_menu.append_item(submenu_item_2) root_menu.start() root_menu.wait_for_start(timeout=10) self.assertIs(CursesMenu.currently_active_menu, root_menu) submenu_item_1.action() submenu1.wait_for_start(timeout=10) self.assertIs(CursesMenu.currently_active_menu, submenu1) CursesMenu.currently_active_menu.exit() submenu1.join(timeout=10) self.assertIs(CursesMenu.currently_active_menu, root_menu) submenu_item_2.action() submenu2.wait_for_start(timeout=10) self.assertIs(CursesMenu.currently_active_menu, submenu2)
def submenu_logging(menu_obj): submenu = CursesMenu("Logging Configuration") q1_item = FunctionItem("Do you want to turn enable logging?", input, ['yes']) q2_item = FunctionItem( "If yes, do you want logging to stdout or a log file?", input, ['FILE']) submenu.append_item(q1_item) submenu.append_item(q2_item) submenu_item = SubmenuItem("Show a submenu", submenu, menu=menu_obj) return submenu_item
def create_menu(list_dict_stories, type_new): title = 'Pynews - {} stories'.format(type_new.capitalize()) menu = CursesMenu(title, 'Select the new and press enter') msg = 'This new does not have an URL' for story in list_dict_stories: title = clean_title(story['title']) if 'url' in story: item = FunctionItem(title, url_open, args=[story['url']]) else: item = FunctionItem(title, lambda x: print(x), args=[msg]) menu.append_item(item) return menu
def submenu_backup(menu_obj): submenu = CursesMenu("Access Key Backup") q1_item = FunctionItem( "Do you want to retain a copy of newly created access keys?", input, ["yes"]) q2_item = FunctionItem( "Enter the directory where a backup copy of the access keys should be stored", input, ["~/Backup"]) submenu.append_item(q1_item) submenu.append_item(q2_item) submenu_item = SubmenuItem("Show a submenu", submenu, menu=menu_obj) return submenu_item
def generate_menu(tasks): """ Create menu for process selection """ menu = CursesMenu("Select something to kill him!") # a CommandItem runs a console command for pid, name in tasks.items(): label = "{:<8} - {}".format('[' + str(pid) + ']', name[:40]) command = "kill {}".format(pid) menu.append_item(cmitems.CommandItem(label, command)) # show the menu and allow the user to interact menu.show()
def generate_menu(menu=None, menu_cfg=None, parent_title=None): if not menu_cfg: menu_cfg = c.get_menu() if not menu: title = get_string(menu_cfg, 'title') subtitle = get_string(menu_cfg, 'subtitle', params=parent_title) menu = CursesMenu(title, subtitle) options = menu_cfg['options'] if type(options) == str and get_string(menu_cfg, 'type') == 'submenu': options_list = c.get_config()[options] for option in options_list: if 'on_item_select' in menu_cfg: title = get_string(menu_cfg, 'title') subtitle = get_string(menu_cfg['on_item_select'], 'subtitle', params=option) submenu = CursesMenu(title, subtitle) option_menu = generate_menu(menu_cfg=menu_cfg['on_item_select'], parent_title=option) item = SubmenuItem(option, option_menu, menu=submenu) else: item = FunctionItem(option, getattr(c.get_action_module(), menu_cfg['action']), [option]) # TODO allow for customisation of module name menu.append_item(item) else: for option in menu_cfg['options']: cmd_type = get_string(option, 'type') title = get_string(option, 'title') action = get_string(option, 'action') subtitle = get_string(option, 'subtitle') if cmd_type == 'function': item = FunctionItem(title, getattr(actions, action)) elif cmd_type == 'submenu': submenu = CursesMenu(title, subtitle) item = SubmenuItem(title, submenu, menu=menu) generate_menu(submenu, option, title) else: item = MenuItem(title) menu.append_item(item) return menu
class Menu: def __init__(self, recipes): self.recipes = recipes self.title = 'PyVegan - List of Recipes' self.menu = CursesMenu(self.title, 'Select one and press enter') self.error_msg = 'This search isn\'t a valid one' def build(self): for recipe in self.recipes: if recipe.link: item = FunctionItem(recipe.title, url_open, args=[recipe.link]) else: item = FunctionItem(recipe.title, lambda x: print(x), args=[self.error_msg]) self.menu.append_item(item) def show(self): self.menu.show()
def main(): """ Builds main menu, branches to submenus """ # parent menu menu = CursesMenu("Local Configuration Menu", "keyup Project") try: submenu_backup = submenu_backup(menu) submenu_logging = submenu_logging(menu) # assemble main menu menu.append_item(submenu_backup) menu.append_item(submenu_logging) menu.show() user_selection = menu.selected_option except Exception as e: print('Unknown Exception: %s. Exit' % str(e)) return False return True
def display_commands(self, user=None): """[displats all commands for user] """ if user: menu = CursesMenu("Spotify Collobrative Playlist", "User: "******"Spotify Collobrative Playlist", "User", show_exit_option=True) sl1 = SelectionItem("Choose Active Playlist", 1) sl2 = SelectionItem("Add new song to playlist", 2) sl3 = SelectionItem("List Playlists", 3) sl4 = SelectionItem("Print Songs in Playlist", 4) sl5 = SelectionItem("Create Playlist", 5) sl6 = SelectionItem("Exit", 6) menu.append_item(sl1) menu.append_item(sl2) menu.append_item(sl3) menu.append_item(sl4) menu.append_item(sl5) menu.append_item(sl6) menu.start() menu.join() return menu.returned_value
def run(self): menu = CursesMenu("Openstack Toolset", "Restore OpenStack Virtual Machine Block Device from SAN - By Real World") options = ['Restore State'] for idx, item in enumerate(options): menu.append_item(SelectionItem(item, idx)) submenu = CursesMenu("Contact the Author.") contact = SubmenuItem("Author: Karl Kloppenborg", submenu=submenu) contact2 = SubmenuItem("Email: [email protected]", submenu=submenu) submenu.append_item(contact) submenu.append_item(contact2) sub = SubmenuItem("Help!", submenu, menu=menu) menu.append_item(sub) menu.show() return menu.selected_option
def create_menu(self) -> None: # Create the menu self.menu = CursesMenu("PyMeterReader Configuration Wizard", "Choose item to configure") function_item = FunctionItem("Volkszähler Gateway", self.input_gw, ["Enter URL: "], should_exit=True) self.menu.append_item(function_item) for meter in self.meters: meter_menu = CursesMenu( f"Connect channels for meter {meter.meter_id} at {meter.meter_address}", "By channel") for channel in meter.channels: map_menu = CursesMenu( f"Choose uuid for {channel.channel_name}") for choice in self.gateway_channels: map_menu.append_item( FunctionItem(f"{choice.uuid}: {choice.title}", self.__assign, [meter, channel, choice.uuid, '30m'], should_exit=True)) map_menu.append_item( FunctionItem("Enter private UUID", self.__assign, [meter, channel, None, '30m'], should_exit=True)) meter_menu.append_item( SubmenuItem( f"{channel.channel_name}: {channel.value} {channel.unit}", map_menu, self.menu)) submenu_item = SubmenuItem(f"Meter {meter.meter_id}", meter_menu, self.menu) self.menu.append_item(submenu_item) view_item = FunctionItem("View current mapping", self.__view_mapping) self.menu.append_item(view_item) save_item = FunctionItem("Save current mapping", self.__safe_mapping) self.menu.append_item(save_item) register_service = FunctionItem( "Register PymeterReader as systemd service.", self.__register_service) self.menu.append_item(register_service) reset_item = FunctionItem("Reset all mappings", self.__clear) self.menu.append_item(reset_item) self.menu.show()
# Create the menu menu = CursesMenu("Title", "Subtitle") # Create some items # MenuItem is the base class for all items, it doesn't do anything when selected menu_item = MenuItem("Menu Item") # A FunctionItem runs a Python function when selected function_item = FunctionItem("Call a Python function", input, ["Enter an input"]) # A CommandItem runs a console command command_item = CommandItem("Run a console command", "touch hello.txt") # A SelectionMenu constructs a menu from a list of strings selection_menu = SelectionMenu(["item1", "item2", "item3"]) # A SubmenuItem lets you add a menu (the selection_menu above, for example) # as a submenu of another menu submenu_item = SubmenuItem("Submenu item", selection_menu, menu) # Once we're done creating them, we just add the items to the menu menu.append_item(menu_item) menu.append_item(function_item) menu.append_item(command_item) menu.append_item(submenu_item) # Finally, we call show to show the menu and allow the user to interact menu.show()
class Wizard: def __init__(self): self.url = "http://localhost/middleware.php" self.gateway = VolkszaehlerGateway(self.url) self.gateway_channels = self.gateway.get_channels() self.menu = None print("Detecting meters...") self.meters = detect() self.channel_config = {} self.restart_ui = True while self.restart_ui: self.restart_ui = False self.create_menu() def input_gw(self, text): def is_valid_url(): return re.match(r"^https?://[/\w\d.]+.php$", self.url) self.restart_ui = True self.menu.clear_screen() self.url = "invalid" while self.url and not is_valid_url(): self.url = input(text) if not self.url: self.menu.stdscr.addstr( 3, 0, "Defaulting to http://localhost/middleware.php") self.url = "http://localhost/middleware.php" self.menu.stdscr.getkey() elif not is_valid_url(): self.menu.stdscr.addstr( 3, 0, "Entered url is not valid." " It must start with 'http://' or 'https://' and end with '.php'" ) self.menu.stdscr.getkey() self.gateway = VolkszaehlerGateway(self.url) self.gateway_channels = self.gateway.get_channels() if self.gateway_channels: self.menu.stdscr.addstr( 3, 0, f"Found {len(self.gateway_channels)} public channels at gateway '{self.url}'." ) else: self.menu.stdscr.addstr( 3, 0, f"Unable to find any public channels at '{self.url}'.") self.menu.stdscr.getkey() def create_menu(self): # Create the menu self.menu = CursesMenu("PyMeterReader Configuration Wizard", "Choose item to configure") function_item = FunctionItem("Volkszähler Gateway", self.input_gw, ["Enter URL: "], should_exit=True) self.menu.append_item(function_item) for meter in self.meters: meter_menu = CursesMenu( f"Connect channels for meter {meter.identifier} at {meter.tty}", "By channel") for channel, value in meter.channels.items(): map_menu = CursesMenu(f"Choose uuid for {channel}") for choice in self.gateway_channels: map_menu.append_item( FunctionItem(f"{choice['uuid']}: {choice['title']}", self.__assign, [meter, channel, choice['uuid'], '30m'], should_exit=True)) map_menu.append_item( FunctionItem("Enter private UUID", self.__assign, [meter, channel, None, '30m'], should_exit=True)) meter_menu.append_item( SubmenuItem(f"{channel}: {value[0]} {value[1]}", map_menu, self.menu)) submenu_item = SubmenuItem(f"Meter {meter.identifier}", meter_menu, self.menu) self.menu.append_item(submenu_item) view_item = FunctionItem("View current mapping", self.__view_mapping) self.menu.append_item(view_item) save_item = FunctionItem("Save current mapping", self.__safe_mapping) self.menu.append_item(save_item) register_service = FunctionItem( "Register PymeterReader as systemd service.", self.__register_service) self.menu.append_item(register_service) reset_item = FunctionItem("Reset all mappings", self.__clear) self.menu.append_item(reset_item) self.menu.show() def __register_service(self): self.menu.clear_screen() self.menu.stdscr.addstr(0, 0, "Installing service...") run( 'sudo systemctl stop pymeterreader', # pylint: disable=subprocess-run-check universal_newlines=True, shell=True) target_service_file = "/etc/systemd/system/pymeterreader.service" service_str = SERVICE_TEMPLATE.format( 'pymeterreader -c /etc/pymeterreader.yaml') try: with open(target_service_file, 'w') as target_file: target_file.write(service_str) run( 'systemctl daemon-reload', # pylint: disable=subprocess-run-check universal_newlines=True, shell=True) if not exists('/etc/pymeterreader.yaml'): self.menu.stdscr.addstr( 1, 0, "Copy example configuration file to '/etc/pymeterreader.yaml'" ) with open('example_configuration.yaml', 'r') as file: example_config = file.read() with open('/etc/pymeterreader.yaml', 'w') as file: file.write(example_config) self.menu.stdscr.addstr( 2, 0, "Registered pymeterreader as servicee.\n" "Enable with 'sudo systemctl enable pymeterreader'\n." "IMPORTANT: Create configuration file '/etc/pymeterreader.yaml'" ) except OSError as err: if isinstance(err, PermissionError): self.menu.stdscr.addstr( 4, 0, "Cannot write service file to /etc/systemd/system. " "Run as root (sudo) to solve this.") self.menu.stdscr.addstr(6, 0, "(press any key)") self.menu.stdscr.getkey() def __clear(self): """ Remove channel mappings """ self.channel_config.clear() def __safe_mapping(self): """ Save yaml to system """ self.menu.clear_screen() result = generate_yaml(self.channel_config, self.url) try: with open('/etc/pymeterreader.yaml', 'w') as config_file: config_file.write(result) self.menu.stdscr.addstr(0, 0, "Saved to /etc/pymeterreader.yaml") except PermissionError: self.menu.stdscr.addstr( 0, 0, "Insufficient permissions: cannot write to /etc/pymeterreader.yaml" ) self.menu.stdscr.addstr(1, 0, "(press any key)") self.menu.stdscr.getkey() def __view_mapping(self): self.menu.clear_screen() self.menu.stdscr.addstr(0, 0, "Mapped channels:") row = 2 for meter in self.channel_config.values(): for channel, content in meter['channels'].items(): self.menu.stdscr.addstr( row, 2, f"{channel} at {meter.get('id')} mapped to UUID {content.get('uuid')}" ) row += 1 self.menu.stdscr.addstr(row, 0, "(press any key)") self.menu.stdscr.getkey() def __assign(self, meter: Device, channel, uuid: str, interval: str): if uuid is None: self.menu.clear_screen() uuid = input("Enter private UUID: ") if meter.identifier not in self.channel_config: self.channel_config[meter.identifier] = { 'channels': {}, 'id': meter.identifier, 'protocol': meter.protocol } self.channel_config[meter.identifier]['channels'][channel] = { 'uuid': uuid, 'interval': interval }
menu.show() user_selection = menu.selected_option except Exception as e: print('Unknown Exception: %s. Exit' % str(e)) return False return True if __name__ == '__main__': menu = CursesMenu("Local Configuration Menu", "xlines Project") # backup submenu = CursesMenu("Exclusions", 'Do you want to add a new file type exclusion?') q1_item1 = FunctionItem("Yes", input, ["yes"]) q1_item2 = FunctionItem("No", print('Exit'), ["no"]) submenu.append_item(q1_item1) submenu.append_item(q1_item2) submenu_exclusions = SubmenuItem("Configure Exclusion List", submenu, menu=menu) menu.append_item(submenu_exclusions) if q1_item1 == "yes": display_exclusions() submenu = CursesMenu( "Access Key Backup", 'Enter the directory where a backup copy of the access keys should be stored' ) q2_item1 = FunctionItem("~/Backup/", input, ["~/Backup"]) submenu.append(q2_item1) submenu.show()
class Editor(object): """A console-based menu system for creating and editing module descripting JSON files """ def __init__(self, edit, jsonfile, output_dir='output/'): """Constructor Either reconstructs a module object from JSON, or create a new based on user input """ self.jsonfile = jsonfile self.output_dir = output_dir self.recently_saved = False if edit: # Load the specified JSON file try: json = json_parser(jsonfile) self.settings = Settings(jsonfile, json['settings']) self.bus = Bus(json['bus']) self.mod = Module(json['module'], self.bus, self.settings) self.recently_saved = True except Exception as e: print('An unresolvable error has occurred:') print(str(e)) print('Exiting...') exit() else: bus_dic = OrderedDict() bus_dic['type'] = get_list_choice('Choose a bus type: ', Bus.supported_bus) bus_dic['addr_width'] = 32 bus_dic['data_width'] = 32 bus_dic['reset'] = 'async' self.bus = Bus(bus_dic) # Get name, addr_width, data_width and description mod = OrderedDict() mod['name'] = get_identifier('Enter a module name: ') '''! @todo Add int check''' mod['addr_width'] = 32 mod['data_width'] = 32 mod['description'] = input('Enter a description for the module: ') mod['register'] = [] self.settings = Settings(None, None) self.mod = Module(mod, self.bus, self.settings) def show_menu(self): self.menu = CursesMenu('bust - Module Editor', self.set_subtitle()) self.menu.append_item(FunctionItem('Edit name', self.edit_name)) self.menu.append_item( FunctionItem('List registers', self.list_registers)) self.menu.append_item( FunctionItem('Add new register', self.add_register)) self.menu.append_item( FunctionItem('Remove register', self.remove_register)) self.menu.append_item( FunctionItem('Update addresses', self.update_addresses)) self.menu.append_item(FunctionItem('Save JSON', self.save_JSON)) self.menu.show() def update_menu(self): self.menu.subtitle = self.set_subtitle() def edit_name(self): print('Change the module name from current: ' + self.mod.name) self.mod.name = get_identifier('Enter a new name: ') self.recently_saved = False self.update_menu() def return_registers(self): while True: clear_screen() if len(self.mod.registers) < 1: print('No registers created at this point...') cont() return else: table = PrettyTable() table.field_names = [ '#', 'Name', 'Mode', 'Address', 'Type', 'Length', 'Reset', 'Description' ] for i, reg in enumerate(self.mod.registers): table.add_row([ i, reg.name, reg.mode, hex(reg.address), reg.sig_type, reg.length, reg.reset, add_line_breaks(reg.description, 25) ]) return table def list_registers(self): table = self.return_registers() if table is None: return print(table) print( '\nEnter the register number for register details, or q to quit...' ) while True: choice = input('Choice: ') if self.valid_register_input(choice): break else: print(choice + ' is not a valid choice') if choice == 'q': return else: clear_screen() self.print_register(int(choice), table) cont() def print_register(self, reg_num, table): reg = self.mod.registers[reg_num] print(table.get_string(start=reg_num, end=(reg_num + 1))) if len(reg.fields) > 0: print('\nFields:') table_fields = PrettyTable() table_fields.field_names = [ '#', 'Name', 'Type', 'Position', 'Length', 'Reset', 'Description' ] for i, field in enumerate(reg.fields): table_fields.add_row([ i, field.name, field.sig_type, field.get_pos_str(), field.length, field.reset, add_line_breaks(field.description, 25) ]) print(table_fields) def add_register(self): """Adds a register to the module object Get user input to create a register that may or may not consists of individual fields """ reg = OrderedDict() reg_names = [regs.name for regs in self.mod.registers] print('Input register information: ') try: reg['name'] = get_identifier('Name: ', reg_names) reg['description'] = input('Description: ') reg['mode'] = get_list_choice("Choose register mode: ", Register.supported_modes, 'lower', 0) fields = [] width_consumed = 0 while True: field_dic = OrderedDict() add_fields = input('Do you want to add a field? (Y/n): ') field_names = [field['name'] for field in fields] if add_fields.upper() == 'N': break elif add_fields.upper() == 'Y' or add_fields == '': field_dic['name'] = get_identifier('Field name: ', field_names) field_dic['type'] = get_list_choice( 'Field type: ', Field.supported_types) if field_dic['type'] == 'slv': max_width = self.mod.data_width - width_consumed field_dic['length'] = get_int( 'Field length: ', 10, 1, max_width, "The minimum width of a field is 1!", "The maximum width of this field cannot extend" + " the module data width minus the width already" + " consumed by other fields: " + str(max_width)) width_consumed += field_dic['length'] else: width_consumed += 1 field_dic['length'] = 1 max_reset = 2**field_dic['length'] - 1 field_dic['reset'] = hex( get_int( 'Field reset in hex (default=0x0): ', 16, 0x0, max_reset, "The minimum reset value is 0x0", "The maximum reset value is based on the field width, " + "and is " + str(hex(max_reset)), 0x0)) field_dic['description'] = input('Field description: ') fields.append(field_dic) # Check if all available data bits are use if width_consumed == self.mod.data_width: print( "All available bits (" + str(self.mod.data_width) + ") is consumed.\n" + "No more fields can be added to this register.\n") break elif width_consumed > self.mod.data_width: raise RuntimeError( "More bits used by fields than available...") else: print(add_fields + ' is not a valid choice...') if len(fields) > 0: reg['type'] = 'fields' reg['fields'] = fields else: reg['type'] = get_list_choice('Register type: ', Register.supported_types, None, 0) # Make sure reg length is set to help calculate max reset later # Registers of field type will get an auto reset based on the field resets if reg['type'] == 'default': reg['length'] = self.mod.data_width elif reg['type'] == 'sl': reg['length'] = 1 elif reg['type'] == 'slv': while True: try: reg['length'] = int(input('Length: ')) break except Exception: print('That is not a valid length...') if input('Auto-assign address? (Y/n): ').upper() == 'N': # Make sure the address is not out of range and that it is free max_address = 2**self.mod.addr_width - 1 while True: reg['address'] = get_int( "Address in hex: ", 16, 0x0, max_address, "The minimum address is 0x0", "The maximum address is based on the module address width, " + "and is " + str(hex(max_address)), 0x0) # Perform an extra check of address range, although redundant if self.mod.is_address_out_of_range(reg['address']): print("Address is out of range...") continue # Check if the address has not been taken if not self.mod.is_address_free(reg['address']): print( "The chosen address is already assigned to another register..." ) continue # Check whether the address is not byte-addressed, and give the user a choice to continue if not self.mod.is_address_byte_based(reg['address']): choice = input( "The selected address is not byte-based. Continue? (y/N): " ) if not choice.upper() == 'Y': continue break if reg['type'] != 'fields': max_reset = 2**reg['length'] - 1 reg['reset'] = hex( get_int( 'Register reset in hex (default=0x0): ', 16, 0x0, max_reset, "The minimum reset value is 0x0", "The maximum reset value is based on the register width, " + "and is " + str(hex(max_reset)), 0x0)) table = PrettyTable() table.field_names = [ '#', 'Name', 'Mode', 'Address', 'Type', 'Length', 'Reset', 'Description' ] # Table values based on what values exists table_name = reg['name'] table_mode = reg['mode'] if 'address' in reg: table_address = reg['address'] else: table_address = 'auto' table_type = reg['type'] if reg['type'] == 'fields': table_length = 'auto' elif 'length' in reg: table_length = reg['length'] else: table_length = self.mod.bus.data_width if reg['type'] == 'fields': table_reset = 'auto' elif 'reset' in reg: table_reset = reg['reset'] else: table_reset = 'auto' table_description = add_line_breaks(reg['description'], 25) table.add_row([ len(self.mod.registers), table_name, table_mode, table_address, table_type, table_length, table_reset, table_description ]) print(table) if 'fields' in reg: print('\nFields:') table_fields = PrettyTable() table_fields.field_names = [ '#', 'Name', 'Type', 'Length', 'Reset', 'Description' ] for i, field in enumerate(reg['fields']): table_fields.add_row([ i, field['name'], field['type'], field['length'], field['reset'], field['description'] ]) print(table_fields) if input('Confirm creation of register? (Y/n): ').upper() != 'N': self.mod.add_register(reg) self.recently_saved = False self.update_menu() else: return except KeyboardInterrupt: print('\nAdding register aborted!') cont() except Exception as e: print('\nAdding register failed!') print(str(e)) cont() def remove_register(self): table = self.return_registers() if table is None: return print(table) print('\nEnter the register number for removal, or q to quit...') while True: choice = input('Choice: ') if self.valid_register_input(choice): break else: print(choice + ' is not a valid choice') if choice == 'q': return else: clear_screen() self.print_register(int(choice), table) if input('Are you sure you want to delete this register? (y/N): ' ).upper() == 'Y': del self.mod.registers[int(choice)] self.recently_saved = False self.update_menu() def update_addresses(self): self.mod.update_addresses() print("Addresses are updated..") self.recently_saved = False self.update_menu() cont() def save_JSON(self): print('Saving ' + self.jsonfile + ' ...') # Get JSON with addresses json = self.mod.return_JSON(True) try: write_string_to_file(json, self.jsonfile, ".") except Exception as e: print('Saving failed...') print(e) cont() return self.recently_saved = True cont() self.update_menu() def set_subtitle(self): if self.recently_saved: s = ' - SAVED' else: s = ' - NOT SAVED' string = self.mod.name string += ' / ' + str(self.mod.addr_width) string += ' / ' + str(self.mod.data_width) string += ' / ' + str(hex(self.mod.baseaddr)) string += s return string def valid_register_input(self, s): """ Returns boolean determining if a choice of register is valid The input is first checked against the quit-character, and then checked if it matches any valid indexes of the mod.registers list. """ if s.upper() == 'Q': return True elif is_int(s): index = int(s) for i, reg in enumerate(self.mod.registers): if index == i: return True return False
class Wizard: CONFIG_FILE_NAME = "pymeterreader.yaml" POSIX_CONFIG_PATH = Path("/etc") / CONFIG_FILE_NAME def __init__(self) -> None: logging.basicConfig(level=logging.INFO) self.url = "http://localhost/middleware.php" self.gateway = VolkszaehlerGateway(self.url) self.gateway_channels = self.gateway.get_channels() self.menu: CursesMenu print("Detecting meters...") self.meters = detect() self.channel_config: tp.Dict[str, tp.Dict[str, tp.Union[str, tp.Dict]]] = {} self.restart_ui = True while self.restart_ui: self.restart_ui = False self.create_menu() def input_gw(self, text) -> None: def is_valid_url(): return re.match(r"^https?://[/\w\d.]+.php$", self.url) self.restart_ui = True self.menu.clear_screen() self.url = "invalid" while self.url and not is_valid_url(): self.url = input(text) if not self.url: self.menu.stdscr.addstr( 3, 0, "Defaulting to http://localhost/middleware.php") self.url = "http://localhost/middleware.php" self.menu.stdscr.getkey() elif not is_valid_url(): self.menu.stdscr.addstr( 3, 0, "Entered url is not valid." " It must start with 'http://' or 'https://' and end with '.php'" ) self.menu.stdscr.getkey() self.gateway = VolkszaehlerGateway(self.url) self.gateway_channels = self.gateway.get_channels() if self.gateway_channels: self.menu.stdscr.addstr( 3, 0, f"Found {len(self.gateway_channels)} public channels at gateway '{self.url}'." ) else: self.menu.stdscr.addstr( 3, 0, f"Unable to find any public channels at '{self.url}'.") self.menu.stdscr.getkey() def create_menu(self) -> None: # Create the menu self.menu = CursesMenu("PyMeterReader Configuration Wizard", "Choose item to configure") function_item = FunctionItem("Volkszähler Gateway", self.input_gw, ["Enter URL: "], should_exit=True) self.menu.append_item(function_item) for meter in self.meters: meter_menu = CursesMenu( f"Connect channels for meter {meter.meter_id} at {meter.meter_address}", "By channel") for channel in meter.channels: map_menu = CursesMenu( f"Choose uuid for {channel.channel_name}") for choice in self.gateway_channels: map_menu.append_item( FunctionItem(f"{choice.uuid}: {choice.title}", self.__assign, [meter, channel, choice.uuid, '30m'], should_exit=True)) map_menu.append_item( FunctionItem("Enter private UUID", self.__assign, [meter, channel, None, '30m'], should_exit=True)) meter_menu.append_item( SubmenuItem( f"{channel.channel_name}: {channel.value} {channel.unit}", map_menu, self.menu)) submenu_item = SubmenuItem(f"Meter {meter.meter_id}", meter_menu, self.menu) self.menu.append_item(submenu_item) view_item = FunctionItem("View current mapping", self.__view_mapping) self.menu.append_item(view_item) save_item = FunctionItem("Save current mapping", self.__safe_mapping) self.menu.append_item(save_item) register_service = FunctionItem( "Register PymeterReader as systemd service.", self.__register_service) self.menu.append_item(register_service) reset_item = FunctionItem("Reset all mappings", self.__clear) self.menu.append_item(reset_item) self.menu.show() def __register_service(self) -> None: self.menu.clear_screen() if platform.system() != "Linux": self.menu.stdscr.addstr( 0, 0, "Systemd Service registration is only supported on Linux!") self.menu.stdscr.addstr(1, 0, "(press any key)") self.menu.stdscr.getkey() return self.menu.stdscr.addstr(0, 0, "Installing service...") run( 'sudo systemctl stop pymeterreader', # pylint: disable=subprocess-run-check universal_newlines=True, shell=True) target_service_file = "/etc/systemd/system/pymeterreader.service" service_str = SERVICE_TEMPLATE.format( f'pymeterreader -c {self.POSIX_CONFIG_PATH.absolute()}') try: with open(target_service_file, 'w') as target_file: target_file.write(service_str) run( 'systemctl daemon-reload', # pylint: disable=subprocess-run-check universal_newlines=True, shell=True) if not exists(self.POSIX_CONFIG_PATH): self.menu.stdscr.addstr( 1, 0, f"Copy example configuration file to '{self.POSIX_CONFIG_PATH.absolute()}'" ) with open('example_configuration.yaml', 'r') as file: example_config = file.read() with open(self.POSIX_CONFIG_PATH, 'w') as file: file.write(example_config) self.menu.stdscr.addstr( 2, 0, "Registered pymeterreader as service.\n" "Enable with 'sudo systemctl enable pymeterreader'\n." f"IMPORTANT: Create configuration file '{self.POSIX_CONFIG_PATH.absolute()}'" ) except FileNotFoundError as err: self.menu.stdscr.addstr(4, 0, f"Could not access file: {err}!") except PermissionError: self.menu.stdscr.addstr( 4, 0, "Cannot write service file to /etc/systemd/system. " "Run as root (sudo) to solve this.") self.menu.stdscr.addstr(6, 0, "(press any key)") self.menu.stdscr.getkey() def __clear(self) -> None: """ Remove channel mappings """ self.channel_config.clear() def __safe_mapping(self) -> None: """ Save yaml to system """ self.menu.clear_screen() result = generate_yaml(self.channel_config, self.url) try: if platform.system() in ["Linux", "Darwin"]: config_path = self.POSIX_CONFIG_PATH else: config_path = Path(".") / "pymeterreader.yaml" with open(config_path, "w") as config_file: config_file.write(result) self.menu.stdscr.addstr(0, 0, f"Saved to {config_path.absolute()}") except PermissionError: self.menu.stdscr.addstr( 0, 0, f"Insufficient permissions: cannot write to {config_path.absolute()}!" ) except FileNotFoundError: self.menu.stdscr.addstr( 0, 0, f"Could not access path: {config_path.absolute()}!") self.menu.stdscr.addstr(1, 0, "(press any key)") self.menu.stdscr.getkey() def __view_mapping(self) -> None: self.menu.clear_screen() self.menu.stdscr.addstr(0, 0, "Mapped channels:") row = 2 for meter in self.channel_config.values(): for channel, content in meter['channels'].items(): self.menu.stdscr.addstr( row, 2, f"{channel} at {meter.get('id')} mapped to UUID {content.get('uuid')}" ) row += 1 self.menu.stdscr.addstr(row, 0, "(press any key)") self.menu.stdscr.getkey() def __assign(self, meter: Device, channel: ChannelValue, uuid: tp.Optional[str], interval: str) -> None: if uuid is None: self.menu.clear_screen() uuid = input("Enter private UUID: ") if meter.meter_id not in self.channel_config: self.channel_config[meter.meter_id] = { 'channels': {}, 'protocol': meter.protocol, 'meter_address': meter.meter_address, 'meter_id': meter.meter_id } self.channel_config[meter.meter_id]['channels'][ channel.channel_name] = { 'uuid': uuid, 'interval': interval }
from cursesmenu import CursesMenu from cursesmenu.items import * from scriptku import menucurse #Top Level Menu toplv_menu = CursesMenu("Menu Raspberry Legend! - written by Pratama", "pilih satu") #Youtube Menu yt_menu = CursesMenu("Utility utk download youtube video", "pilih salah satu") toplv_yt_item = SubmenuItem("Youtube Video Downloader", yt_menu, menu=toplv_menu) yt1 = FunctionItem("Download single youtube video", menucurse.yt1) yt_edit_links = FunctionItem("Edit list download", menucurse.yt_edit_links) yt_bulk = FunctionItem("Bulk download youtube videos", menucurse.yt_bulk) #Bulk rename menu bulk_rename_item = FunctionItem("Bulk rename - WARNING(DANGEROUS TOOLS, USE AT YOUR OWN RISK)", menucurse.bulk_rename) #Top Level Menu Append toplv_menu.append_item(toplv_yt_item) toplv_menu.append_item(bulk_rename_item) #Youtube Menu Append yt_menu.append_item(yt1) yt_menu.append_item(yt_edit_links) yt_menu.append_item(yt_bulk) #Show the Top Level Menu toplv_menu.show()
from modules import SSH as ssh from modules import SshX as ssh_X from modules import Xfce as xfce # Створення основного меню menu = CursesMenu("Ubuntu universal script", platform.version()) # Створення основних пунктів _menu_items = [ CommandItem("Update paсkages", "sudo apt update"), CommandItem("Upgrade paсkages", "sudo apt upgrade"), CommandItem("Autoremove paсkages", "sudo apt autoremove"), CommandItem("Remove other kernel`s", "bash bash/remove_kernel.sh"), CommandItem( "Cleaning pickings removed paсkages", "sudo dpkg -l | awk '/^rc/ {print $2}' | xargs sudo dpkg --purge"), # Відображення підменю SubmenuItem("Install soft", soft.menu, menu), SubmenuItem("Remove software", remove.menu, menu), SubmenuItem("Xfce soft", xfce.menu, menu), SubmenuItem("SSH Connect", ssh.menu, menu), SubmenuItem("SSH connect with X-window support", ssh_X.menu, menu) ] # Додавання пунктів до основного меню for item in _menu_items: menu.append_item(item) # Показ меню menu.show()
class TestSampleMenu(BaseTestCase): def setUp(self): super(TestSampleMenu, self).setUp() self.menu = CursesMenu("self.menu", "TestSampleMenu") self.item1 = MenuItem("self.item1", self.menu) self.item2 = MenuItem("self.item2", self.menu) self.menu.append_item(self.item1) self.menu.append_item(self.item2) self.menu.start() self.menu.wait_for_start(timeout=10) def tearDown(self): super(TestSampleMenu, self).tearDown() self.menu.exit() self.menu.join(timeout=10) def test_go_down(self): self.menu.go_down() self.assertEqual(self.menu.current_option, 1) self.assertIs(self.menu.current_item, self.item2) self.menu.go_down() self.assertEqual(self.menu.current_option, 2) self.assertEqual(self.menu.current_item, self.menu.exit_item) self.menu.go_down() self.assertEqual(self.menu.current_option, 0) self.assertIs(self.menu.current_item, self.item1) def test_go_up(self): self.menu.go_up() self.assertEqual(self.menu.current_option, 2) self.assertIs(self.menu.current_item, self.menu.exit_item) self.menu.go_up() self.assertEqual(self.menu.current_option, 1) self.assertEqual(self.menu.current_item, self.item2) self.menu.go_up() self.assertEqual(self.menu.current_option, 0) self.assertIs(self.menu.current_item, self.item1) def test_go_to(self): self.menu.go_to(1) self.assertEqual(self.menu.current_option, 1) self.assertEqual(self.menu.current_item, self.item2) def test_select(self): self.menu.select() self.assertEqual(self.menu.selected_option, 0) self.assertIs(self.menu.selected_item, self.item1) self.menu.go_down() self.menu.select() self.assertEqual(self.menu.selected_option, 1) self.assertIs(self.menu.selected_item, self.item2) self.menu.go_down() self.menu.select() self.assertEqual(self.menu.selected_option, 2) self.assertIs(self.menu.selected_item, self.menu.exit_item) self.menu.join(timeout=10) self.assertFalse(self.menu.is_alive()) def test_exit(self): self.menu.exit() self.menu.join(timeout=10) self.assertFalse(self.menu.is_alive())
class TestSampleMenu(BaseTestCase): def setUp(self): super(TestSampleMenu, self).setUp() self.menu = CursesMenu("self.menu", "TestSampleMenu") self.item1 = MenuItem("self.item1", self.menu) self.item2 = MenuItem("self.item2", self.menu) self.menu.append_item(self.item1) self.menu.append_item(self.item2) self.menu.start() self.menu.wait_for_start(10) def tearDown(self): super(TestSampleMenu, self).tearDown() self.menu.exit() self.menu.join() def test_go_down(self): self.menu.go_down() self.assertEqual(self.menu.current_option, 1) self.assertIs(self.menu.current_item, self.item2) self.menu.go_down() self.assertEqual(self.menu.current_option, 2) self.assertEqual(self.menu.current_item, self.menu.exit_item) self.menu.go_down() self.assertEqual(self.menu.current_option, 0) self.assertIs(self.menu.current_item, self.item1) def test_go_up(self): self.menu.go_up() self.assertEqual(self.menu.current_option, 2) self.assertIs(self.menu.current_item, self.menu.exit_item) self.menu.go_up() self.assertEqual(self.menu.current_option, 1) self.assertEqual(self.menu.current_item, self.item2) self.menu.go_up() self.assertEqual(self.menu.current_option, 0) self.assertIs(self.menu.current_item, self.item1) def test_go_to(self): self.menu.go_to(1) self.assertEqual(self.menu.current_option, 1) self.assertEqual(self.menu.current_item, self.item2) def test_select(self): self.menu.select() self.assertEqual(self.menu.selected_option, 0) self.assertIs(self.menu.selected_item, self.item1) self.menu.go_down() self.menu.select() self.assertEqual(self.menu.selected_option, 1) self.assertIs(self.menu.selected_item, self.item2) self.menu.go_down() self.menu.select() self.assertEqual(self.menu.selected_option, 2) self.assertIs(self.menu.selected_item, self.menu.exit_item) self.menu.join(timeout=5) self.assertFalse(self.menu.is_alive()) def test_exit(self): self.menu.exit() self.menu.join(timeout=5) self.assertFalse(self.menu.is_alive())
#!/usr/bin/env python3 from cursesmenu import CursesMenu from cursesmenu.items import FunctionItem dtitle = "default title" menu = CursesMenu(dtitle) def myFunc(): menu.title = "there's a new title" # myFunc, the'()' were not allowed here, got TypeError str function_item = FunctionItem("change title", myFunc) menu.append_item(function_item) menu.show()