Created
September 20, 2025 17:36
-
-
Save thelabcat/83c9e16b5eae020b17a2b93c9ff829f7 to your computer and use it in GitHub Desktop.
Python module demo for kids - Multiple Choice CLI Question Asker
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """Multiple choice CLI input | |
| Present the user with a list of options, and let them choose either by name, or | |
| numerically. | |
| Copyright 2025 Wilbur Jaywright. | |
| This program is free software: you can redistribute it and/or modify it under | |
| the terms of the GNU General Public License as published by the Free Software | |
| Foundation, either version 3 of the License, or (at your option) any later | |
| version. | |
| This program is distributed in the hope that it will be useful, but WITHOUT ANY | |
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A | |
| PARTICULAR PURPOSE. See the GNU General Public License for more details. | |
| You should have received a copy of the GNU General Public License along with | |
| this program. If not, see <https://www.gnu.org/licenses/>. | |
| S.D.G. | |
| """ | |
| # That first line with the #! is a shebang line for *NIX systems. If this | |
| # script is set as an executable, it tells them what to execute it with, | |
| # and where that executor is. In this case, it's the Python 3 interpreter. | |
| # The next thing is a module docstring. It's an explanation of what the | |
| # module's name is, what it does, who wrote it, and what the rules are for | |
| # using or copying it. I also want to give credit to Whom credit is ALWAYS due: | |
| # S.D.G. is a Latin acronym for, 'To God alone be the glory.' | |
| # Our first bit of Python code should usually be any imports we need. For this | |
| # module, we just need one: The Sequence class from the typing module. This | |
| # 'typing' module isn't for your keyboard, but rather it lets us tell the | |
| # programmer what type of Python object should be passed to the argument | |
| # 'options' in our function: Anything we can subscript with an index. | |
| from typing import Sequence | |
| # Now that our imports are done, we can start defining things! | |
| def multiple_choice(title: str, options: Sequence[str]) -> str: | |
| """Allow the user to choose between multiple options | |
| Args: | |
| title (str): The question at hand. | |
| options (Sequence[str]): A subscriptable of option strings. | |
| Returns: | |
| choice (str): The chosen option.""" | |
| # The function can't work if there's no options to choose from! | |
| # In Python, 'assert' means, "Make sure that this first thing is true, and | |
| # raise an error with this second thing as a message if it isn't." | |
| assert len(options) > 0, "Too few options" | |
| # Find the 'biggest' option by length, and then find out how long it is. | |
| # We'll use this when we display the question so that we can make it nice | |
| # and pretty. | |
| option_max_width = len(max(options, key=len)) | |
| # We're going to show a number next to each option as well, so we'd better | |
| # get the visual length of the biggest number as well. That's easy: The | |
| # biggest number is just the total number of options there are. So, we find | |
| # that, turn it into a string, and then get the length of the string. | |
| num_width = len(str(len(options))) | |
| # Make sure we get a valid choice. | |
| # The return statements will exit this loop for us. | |
| while True: | |
| # Display the question and the options, and get an input | |
| print(title) | |
| # i becomes the index of each option as we iterate through them, thanks | |
| # to the enumerate function. | |
| for i, option in enumerate(options): | |
| # This is some fancy formatted string code, so let me explain. | |
| # Everything within those curly brackets is evaluated before it is | |
| # printed. Before the colon is what gets evaluated, and anything | |
| # after the colon is instructions on how to print it. The first | |
| # thing is to print the number of the option. Python indexes start | |
| # at zero, so we add 1. After that, I wanted to format it so all | |
| # the numbers are padded with zeros. You can see where I used | |
| # num_width here. After two dots, I print the option, but I justify | |
| # it to the right with more dots, so that the right-hand side of | |
| # all the options is lined up. | |
| print(f"{i + 1:0{num_width}d}··{option:·>{option_max_width}}") | |
| # Finally, ask the user for some input. | |
| entry = input("Choice: ") | |
| # Option was typed directly | |
| if entry in options: | |
| return entry | |
| # Number was typed | |
| if entry.isnumeric(): | |
| try: | |
| return options[int(entry) - 1] | |
| # The number wasn't a valid option index | |
| except IndexError: | |
| print("Entered number does not match an option.") | |
| # Something was typed but it was invalid | |
| if entry: | |
| print("Invalid entry. Please type a number or the option itself.") | |
| # If this module is run directly, perform a basic test. | |
| if __name__ == "__main__": | |
| print("Multiple choice CLI input library") | |
| print("Performing test...") | |
| # Store some things that never change | |
| QUESTION = "Which of the following is not a dessert?" | |
| OPTIONS = "cookies", "cake", "brownies", "onions" | |
| CORRECT = "onions" | |
| # Make sure the correct answer is an option! | |
| # If we ever change the program, we might forget to test this ourselves | |
| assert CORRECT in OPTIONS, \ | |
| "The test question's correct answer isn't even an option" | |
| # Ask the question | |
| choice = multiple_choice(QUESTION, OPTIONS) | |
| # Respond to the user's answer | |
| print(f"You chose '{choice}'.") | |
| if choice == CORRECT: | |
| print("Correct!") | |
| else: | |
| print(f"But, the correct choice was '{CORRECT}'.") | |
| # Wait for the user to exit the program | |
| input("Press Enter to exit.") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment