1
\$\begingroup\$

Questions:

  • How could this code be written better?
  • Could this be written in less lines of code if constructed using classes?
  • Also, If the user-created menu items were to be saved to a file for later use, which would be the best approach to use? (pickle? plain text file? etc.)

Problems:

  • How do I dynamically draw the fixed menu items in the settings menu from a list or dict?
  • How to make items selectable by number or a specific character?
  • My main struggle came with dynamically displaying and executing sub-menu items.

Preface:

After about 3 weeks of research, trial and error, playing with different approaches and data structures, I ended up scrapping all my code and resigning myself to writing the following code.

This problem originated as a programming that challenged I created for myself and it now haunts me because I am having to admit defeat by asking you guys for guidance. This drives me crazy since I know there are probably very elegant ways of writing this script, but this was the only way I could make it work and produce the desired outputs.

I have spent hours researching stackoverflow and other sites for similar answers, but none fully addressed my particular design goals, such as dealing with sub-menus and the dynamic nature of this menu system.

I should also mention that I have not fully coded every aspect of this script, but there is enough here to allow the creation of menu items and demonstrate what i'm trying to achieve.


#!/usr/bin/python
import sys, os

menu_actions = {}
user_menu_items = []


def add_userItem(item):
  returnitem = "item " + item + " created..."
  return returnitem

def show_settings():
  os.system('clear')

  print "\nEdit"
  print "Default Settings"
  print "\nBack"
  choice = raw_input(" >>  ")
  if choice.lower() == 'back':
    main_menu()
  else:
    decision(choice)
  return

def quit():
  raise SystemExit()

def s_edit():
  os.system('clear')
  print "\nCreate"
  print "Modify"
  print "\nBack\n"
  choice = raw_input(" >>  ")
  if choice.lower() == 'back':
    show_settings()
  else:
    decision(choice)
  return

def s_default():
  os.system('clear')
  choice = raw_input("(**All user created menus will be erased**)\nAre you sure? (YES / NO)  or Back\n >>  ")
  if choice.lower() == 'back':
    show_settings()
  elif choice.lower() == 'yes':
    print 'all settings have been set to default'
    del user_menu_items[:]  # removing all items from user menu
    show_settings()
  elif choice.lower() == 'no':
    show_settings()
  return

def e_create():
  go = True
  addeditem = []
  global user_menu_items
  while go:      
    badinput = False
    if badinput == True:
      print "That's not a selection. Please try again..."
    else:
      os.system('clear')
      if addeditem != '':
        z = 0
        for i in addeditem:
          print(addeditem[z])
          z += 1
      if not user_menu_items:
        print
      print "\n**Menu Items**"
      z = 0
      for i in user_menu_items:
        print(user_menu_items[z])
        z += 1
      choice = raw_input("\nCreate New Item? (YES / NO)\n >>  ")
    if choice.lower() == 'yes':
      additem = raw_input("\n\nSpecify Name of New Menu Item: ")
      addeditem.append(add_userItem(additem))
      user_menu_items.append(additem)
    else:
      if choice.lower() == 'no':
    go = False
      else: 
    badinput = True
  s_edit()
  return

def e_modify():
  os.system('clear')
  print "Rename"
  print "Delete"
  print "Move Item"
  print "\nBack\n"
  choice = raw_input("Choose an action: ")
  return choice

def m_rename():
  os.system('clear')
  print "\nBack\n"
  choice = raw_input("Enter a new name: ")
  return choice

def m_delete():
  os.system('clear')
  print "\nBack\n"
  choice = raw_input("Select Menu Item to Delete: ")
  return choice

def m_move():
  os.system('clear')
  print "\nBack\n"
  choice = raw_input("Specify new location: ")
  return choice

def goback():
  os.system('clear')
  print "\nBack\n"
  decision('')
  return

def user_menu():
  z = 0
  for i in user_menu_items:
    print(user_menu_items[z])
    z += 1
  return

def main_menu():
  os.system('clear')
  user_menu()
  print "\nSettings"
  print "Quit"
  choice = raw_input(" >>  ")
  decision(choice)
  return

def decision(decision):
  dec = decision.lower()
  if dec == '':
    menu_actions['main menu']()
  else:  
    try:
      menu_actions[dec]()
    except KeyError:
      print "invalid selection, please try again.\n"
      menu_actions['main menu']()
  return

menu_actions = {
  'main': main_menu,
  'settings': show_settings,
  'quit': quit,
  'edit': s_edit,
  'default': s_default,
  'create': e_create,
  'modify': e_modify,
  'rename': m_rename,
  'delete': m_delete,
  'move': m_move,
  'back' : goback
  }

main_menu()

About: As you can see, I have designed a menu creator based on user input, while still providing a fixed menu system for creating and modifying the user-created menus. My desire was to display one tier at a time, making menu items numerically selectable (except for "go back" and "quit"). Below is a pseudo code view of my desired menu tree.

I should also mention that I am fully aware of the fact that the user-created menu items will not have any particular functionality, but I figured I could use this program to create the menus and then code functionality later on -- if desired.

Desired Menu Tree:

  1. Settings

    1. Edit Menu

      1. Create > # Specify Location of Menu Item. > # Specify Name of New Menu Item.
      2. Modify > # Select Menu Item to Modify. > # Pick an Action?
        1. Rename > # Enter a new name
        2. Delete > # Select Menu Item to Delete.
        3. Move Item > # Where?

      x. Go Back

    2. Default Settings (All user created menus will be erased) > #Are you sure? (YES / NO?)

x. Quit > #exit program

\$\endgroup\$
  • \$\begingroup\$ Can you fix the indentation so that your initial code and intent can be undetstood ? Your code doesn't runs with current indentation. Start fixing with line#63 \$\endgroup\$ – Archit Jain Feb 6 '14 at 18:47
  • \$\begingroup\$ All fixed, sorry about that, when I copied the text over and then specified it as code, stackexchange didn't recognize all of my indentions. You should be good to go now. \$\endgroup\$ – holaymolay Feb 7 '14 at 8:10
1
\$\begingroup\$

Disclaimer : your code is not indented properly and I cannot run it. Also, it seems a bit convoluted to me so I haven't really tried to understand it. However, here are a few comments that might help you if you want to improve your code.


Keep it simple

def add_userItem(item):
  returnitem = "item " + item + " created..."
  return returnitem

could be

def add_userItem(item):
  return "item " + item + " created..."

goback could just call main_menu() instead of decision(''). This would make decision() much simpler as it shouldn't be its duty to try to handle special cases (see Separation of Concerns)


You don't need to have menu_actions at the top of your file.


return at the end of the function is not required if you don't plan to return any value.


  choice = raw_input("Choose an action: ")
  return choice

could be

  return raw_input("Choose an action: ")


For Loop

The way Python loops is designed is supposed to help you to write concise and expressive code : what you write should match pretty closely how you think about things : "I want to do this for each item in this container" instead of "I want to have this index and I start at 0 and I increment and I get the element at this index...".

    z = 0
    for i in addeditem:
      print(addeditem[z])
      z += 1

should be written :

    for i in addeditem:
      print(i)

How nice is this ?

Pretty much everytime you initialise a variable at 0 to use it as an index in a loop you are doing it wrong. If you really need to know what is the current index, enumerate is what you are looking for.



Don't repeat yourself

  for i in user_menu_items:
    print(i)

appears in your code twice. You should just call user_menu.


Multiple methods looks quite similar, it might be worth defining a more generic function and then just calling it everywhere


  if choice.lower() == 'back':
    show_settings()
  elif choice.lower() == 'yes':
    print 'all settings have been set to default'
    del user_menu_items[:]  # removing all items from user menu
    show_settings()
  elif choice.lower() == 'no':
    show_settings()

calls lower() and show_settings in 3 different places. It might be easier to do something like :

  choice = raw_input("(**All user created menus will be erased**)\nAre you sure? (YES / NO)  or Back\n >>  ").lower()
  if choice == 'yes':
    print 'all settings have been set to default'
    del user_menu_items[:]  # removing all items from user menu
    show_settings()
  elif choice in ['back','no']:
    show_settings()

If you ever change the label of menu (which most likely happened already - I assume that main menu is its old name), things will fail and you'll have to fix it in different places in decision.

What about doing :

def decision(decision):
    try:
      menu_actions[decision.lower()]()
    except KeyError:
      print "invalid selection, please try again.\n"
      main_menu()

Documentation

You should add comments at least to describe the program and the different main functions.

\$\endgroup\$
  • \$\begingroup\$ This is excellent feedback, thank you Josay. I am new to programming and only recently began being more disciplined with using comments. I will go back and work on it cleaning things up a bit before submitting my edits. Thanks! \$\endgroup\$ – holaymolay Feb 12 '14 at 17:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.