From 4e09dd4b68880b907b5643b5359be78e6e6525bf Mon Sep 17 00:00:00 2001 From: George Zhang Date: Sun, 29 Nov 2020 18:52:00 -0500 Subject: [PATCH 1/3] Refactor prompt_entry into specialized functions prompt_entry -> prompt_value, print_error, parse_entry, prompt_and_parse_entry parse_entries -> prompt_and_parse_entries --- form.py | 4 +-- process.py | 98 +++++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 73 insertions(+), 29 deletions(-) diff --git a/form.py b/form.py index 2e405c6..51fc4f7 100644 --- a/form.py +++ b/form.py @@ -7,7 +7,7 @@ from dataclasses import dataclass from string import ascii_letters, digits -from process import prompt_entry, parse_entries, format_entries +from process import prompt_and_parse_entries, format_entries @dataclass class EntryInfo: @@ -373,7 +373,7 @@ def command_line_process(target): config = open_config(file) print(f"Form URL: {config.url}") - messages = parse_entries(config.entries, on_prompt=prompt_entry) + messages = prompt_and_parse_entries(config.entries) # Use default prompts data = format_entries(config.entries, messages) print(f"Form data: {data}") diff --git a/process.py b/process.py index 17a5781..6d2e378 100644 --- a/process.py +++ b/process.py @@ -1,7 +1,7 @@ # process.py # -# This module holds functions specifically for parsing config values and for -# returning formatted data dictionaries. +# This module holds functions for processing config files. This includes +# prompts, value parsers, and data formatters. from datetime import date, time, datetime @@ -17,27 +17,27 @@ "extra": "[Extra Data]", } -def prompt_entry(entry): +def prompt_value(entry): """ Prompt for a value to the passed entry. """ - assert entry.prompt - while True: - value = input(f"{entry.title}: {PROMPTS[entry.type]} ").strip() - if not value: - if entry.required and not entry.value: - print(f"Value for entry '{entry.title}' is required") - continue - print(f"Using default value: {entry.value}") - value = entry.value - try: - return parse_value(value, entry.type) - except Exception as e: - if not entry.required and not value: - # If provided value isn't empty, it could be a mistake. Only - # skip when it is purposefully left empty. - return "" - print(type(e).__name__, *e.args) + value = input(f"{entry.title}: {PROMPTS[entry.type]} ").strip() + if value: + return value + + if not (entry.required and not entry.value): + # Don't print this if it's required and there's no default + print(f"Using default value: {entry.value}") + return entry.value + +def print_error(error, entry, value): + """ + Print the error and its reason. + """ + if entry.required and not value: + print(f"Value for entry '{entry.title}' is required") + else: + print(f"{type(error).__name__}: {', '.join(error.args)}") # - Parsers @@ -90,22 +90,66 @@ def parse_value(value, type): """ return PARSERS[type](value) -def parse_entries(entries, *, on_prompt=prompt_entry): +def parse_entry(entry, value=None): + """ + Return the parsed value. + + Parse and return the message. If value is None, entry.value will be used. + If there's an error but the entry is optional and the value is empty, the + empty value will be returned. If the value isn't empty, the user could have + tried to enter a parsable value. + """ + if value is None: + value = entry.value + if value: + return parse_value(value, entry.type) + + if not entry.required: + # If provided value isn't empty, it could be a mistake. Only + # skip when it is purposefully left empty. + return "" + raise ValueError(f"Value for entry '{entry.title}' is required") + +def prompt_and_parse_entry( + entry, *, + on_prompt=prompt_value, + on_error=print_error, +): + """ + Prompt and return the parsed value. + + Prompt for an entry value and return the message. `on_prompt(entry)` will + be called to get a value. `on_error(exc, entry, value)` will be called if + parsing fails. This will loop until `parse_entry(entry, value)` returns + (without erroring). + """ + while True: + value = on_prompt(entry) # Not in try-except to prevent infinite loop + try: + return parse_entry(entry, value) + except Exception as exc: + on_error(exc, entry, value) + +def prompt_and_parse_entries( + entries, *, + on_prompt=prompt_value, + on_error=print_error, +): """ Return a list of parsed messages. Parse the entries to create a list of messages. If the entry needs a - prompt, on_prompt is called with the entry. It should return a message or - raise an error. The result should be passed to `format_entries`. + prompt, `prompt_and_parse_entry(entry, **kwargs)` is used. Otherwise, + `parse_entry(entry)` is used. The result should be passed to + `format_entries`. """ messages = [] for entry in entries: if entry.prompt: - messages.append(on_prompt(entry)) - elif entry.required and not entry.value: - raise ValueError(f"Value for entry '{entry.title}' is required") + kwargs = dict(on_prompt=on_prompt, on_error=on_error) + messages.append(prompt_and_parse_entry(entry, **kwargs)) else: - messages.append(parse_value(entry.value, entry.type)) + messages.append(parse_entry(entry)) return messages From 7152693adf5d6b4b4a04cd4bdbfc7afa83bbbea7 Mon Sep 17 00:00:00 2001 From: George Zhang Date: Sun, 29 Nov 2020 21:22:02 -0500 Subject: [PATCH 2/3] Make functions consistent Added parse_entries and format_entry Renamed prompt_value -> prompt_entry --- process.py | 41 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/process.py b/process.py index 6d2e378..382e8b9 100644 --- a/process.py +++ b/process.py @@ -7,6 +7,7 @@ # - Prompts +# entry -> value PROMPTS = { "words": "[Text]", @@ -17,7 +18,7 @@ "extra": "[Extra Data]", } -def prompt_value(entry): +def prompt_entry(entry): """ Prompt for a value to the passed entry. """ @@ -41,6 +42,7 @@ def print_error(error, entry, value): # - Parsers +# value -> message # Parsing functions (one str argument) def parse_normal(value): @@ -70,6 +72,7 @@ def parse_time(value): time(int(hour), int(minute)) # Check if time is real return [hour, minute] +# General function (takes a `type` argument) PARSERS = { "words": parse_normal, "choice": parse_normal, @@ -90,6 +93,7 @@ def parse_value(value, type): """ return PARSERS[type](value) +# Entry functions (uses entries) def parse_entry(entry, value=None): """ Return the parsed value. @@ -110,9 +114,28 @@ def parse_entry(entry, value=None): return "" raise ValueError(f"Value for entry '{entry.title}' is required") +def parse_entries(entries, values=None): + """ + Return the parsed values. + + Parse and return the messages using parsed_entry. If values is a list, pass + it as the second argument (value). + """ + if values is None: + return list(map(parse_entry, entries)) + else: + messages = [] + for entry, value in zip(entries, values): + messages.append(parse_entry(entry, value)) + return messages + +# - Prompt & Parse +# If you want to just parse entries without prompting, use parse_entries. These +# functions take an on_prompt and on_error for use in the command line. + def prompt_and_parse_entry( entry, *, - on_prompt=prompt_value, + on_prompt=prompt_entry, on_error=print_error, ): """ @@ -132,7 +155,7 @@ def prompt_and_parse_entry( def prompt_and_parse_entries( entries, *, - on_prompt=prompt_value, + on_prompt=prompt_entry, on_error=print_error, ): """ @@ -154,6 +177,7 @@ def prompt_and_parse_entries( # - Formatters +# key, message -> data # Specialized functions (key, message -> dict[str, str]) def format_normal(key, message): @@ -195,6 +219,15 @@ def format_message(key, type, message): """ return FORMATS[type](key, message) +# Entry functions (uses entries) +def format_entry(entry, message): + """ + Return a dictionary to be POSTed to the form. + + The rules for format_message apply here as well. + """ + return format_message(entry.key, entry.type, message) + def format_entries(entries, messages): """ Return a dictionary to be POSTed to the form. @@ -204,5 +237,5 @@ def format_entries(entries, messages): """ data = {} for entry, message in zip(entries, messages): - data |= format_message(entry.key, entry.type, message) + data |= format_entry(entry, message) return data From 4578b9ac240bcc4816d2ec62121ccadc888b6c4a Mon Sep 17 00:00:00 2001 From: George Zhang Date: Fri, 4 Dec 2020 22:05:17 -0500 Subject: [PATCH 3/3] Improve prompt_entry - Have prompt_entry take a prompts argument - Improve messages for when noting is entered --- process.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/process.py b/process.py index af3219b..a47a417 100644 --- a/process.py +++ b/process.py @@ -17,17 +17,25 @@ "extra": "[Extra Data]", } -def prompt_entry(entry): +def prompt_entry(entry, *, prompts=PROMPTS): """ Prompt for a value to the passed entry. + + Print the entry's title and the appropriate hint (from the prompts dict). + If the user typed nothing, notify user to the default value (or that it + will be using an empty value). However, this message won't be printed if + there's no default value for a required question. """ - value = input(f"{entry.title}: {PROMPTS[entry.type]} ").strip() + value = input(f"{entry.title}: {prompts[entry.type]} ").strip() if value: return value - if not (entry.required and not entry.value): - # Don't print this if it's required and there's no default + if entry.value: + # Print this if there is a default value print(f"Using default value: {entry.value}") + elif not entry.required: + # Print this if there's no default but its optional + print("Using empty value") return entry.value def print_error(error, entry, value):