From e6da8596c433f46bc337c7e8a14c7de1b0310e4c Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Thu, 13 Aug 2020 14:19:05 -0400 Subject: Replaced choices_function / choices_method with choices_provider. Replaced completer_function / completer_method with completer. ArgparseCompleter now always passes cmd2.Cmd or CommandSet instance as the self argument to choices_provider and completer functions. Moved basic_complete from utils into cmd2.Cmd class. Moved CompletionError to exceptions.py --- examples/arg_decorators.py | 2 +- examples/argparse_completion.py | 98 ++++++++++--------------- examples/modular_commands/commandset_basic.py | 3 +- examples/modular_commands/commandset_complex.py | 3 +- examples/modular_commands_main.py | 96 +++--------------------- 5 files changed, 53 insertions(+), 149 deletions(-) (limited to 'examples') diff --git a/examples/arg_decorators.py b/examples/arg_decorators.py index a085341d..9ffcd8ce 100755 --- a/examples/arg_decorators.py +++ b/examples/arg_decorators.py @@ -18,7 +18,7 @@ class ArgparsingApp(cmd2.Cmd): help='add comma for thousands separator') fsize_parser.add_argument('-u', '--unit', choices=['MB', 'KB'], help='unit to display size in') fsize_parser.add_argument('file_path', help='path of file', - completer_method=cmd2.Cmd.path_complete) + completer=cmd2.Cmd.path_complete) @cmd2.with_argparser(fsize_parser) def do_fsize(self, args: argparse.Namespace) -> None: diff --git a/examples/argparse_completion.py b/examples/argparse_completion.py index e44533b3..c2cb31f6 100644 --- a/examples/argparse_completion.py +++ b/examples/argparse_completion.py @@ -6,62 +6,19 @@ A simple example demonstrating how to integrate tab completion with argparse-bas import argparse from typing import Dict, List -from cmd2 import Cmd, Cmd2ArgumentParser, CompletionItem, with_argparser -from cmd2.utils import CompletionError, basic_complete +from cmd2 import Cmd, Cmd2ArgumentParser, CompletionError, CompletionItem, with_argparser # Data source for argparse.choices food_item_strs = ['Pizza', 'Ham', 'Ham Sandwich', 'Potato'] -def choices_function() -> List[str]: - """Choices functions are useful when the choice list is dynamically generated (e.g. from data in a database)""" - return ['a', 'dynamic', 'list', 'goes', 'here'] - - -def completer_function(text: str, line: str, begidx: int, endidx: int) -> List[str]: - """ - A tab completion function not dependent on instance data. Since custom tab completion operations commonly - need to modify cmd2's instance variables related to tab completion, it will be rare to need a completer - function. completer_method should be used in those cases. - """ - match_against = ['a', 'dynamic', 'list', 'goes', 'here'] - return basic_complete(text, line, begidx, endidx, match_against) - - -def choices_completion_item() -> List[CompletionItem]: - """Return CompletionItem instead of strings. These give more context to what's being tab completed.""" - items = \ - { - 1: "My item", - 2: "Another item", - 3: "Yet another item" - } - return [CompletionItem(item_id, description) for item_id, description in items.items()] - - -def choices_arg_tokens(arg_tokens: Dict[str, List[str]]) -> List[str]: - """ - If a choices or completer function/method takes a value called arg_tokens, then it will be - passed a dictionary that maps the command line tokens up through the one being completed - to their argparse argument name. All values of the arg_tokens dictionary are lists, even if - a particular argument expects only 1 token. - """ - # Check if choices_function flag has appeared - values = ['choices_function', 'flag'] - if 'choices_function' in arg_tokens: - values.append('is {}'.format(arg_tokens['choices_function'][0])) - else: - values.append('not supplied') - return values - - class ArgparseCompletion(Cmd): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.sport_item_strs = ['Bat', 'Basket', 'Basketball', 'Football', 'Space Ball'] - def choices_method(self) -> List[str]: - """Choices methods are useful when the choice list is based on instance data of your application""" + def choices_provider(self) -> List[str]: + """A choices provider is useful when the choice list is based on instance data of your application""" return self.sport_item_strs def choices_completion_error(self) -> List[str]: @@ -76,6 +33,33 @@ class ArgparseCompletion(Cmd): return self.sport_item_strs raise CompletionError("debug must be true") + # noinspection PyMethodMayBeStatic + def choices_completion_item(self) -> List[CompletionItem]: + """Return CompletionItem instead of strings. These give more context to what's being tab completed.""" + items = \ + { + 1: "My item", + 2: "Another item", + 3: "Yet another item" + } + return [CompletionItem(item_id, description) for item_id, description in items.items()] + + # noinspection PyMethodMayBeStatic + def choices_arg_tokens(self, arg_tokens: Dict[str, List[str]]) -> List[str]: + """ + If a choices or completer function/method takes a value called arg_tokens, then it will be + passed a dictionary that maps the command line tokens up through the one being completed + to their argparse argument name. All values of the arg_tokens dictionary are lists, even if + a particular argument expects only 1 token. + """ + # Check if choices_provider flag has appeared + values = ['choices_provider', 'flag'] + if 'choices_provider' in arg_tokens: + values.append('is {}'.format(arg_tokens['choices_provider'][0])) + else: + values.append('not supplied') + return values + # Parser for example command example_parser = Cmd2ArgumentParser(description="Command demonstrating tab completion with argparse\n" "Notice even the flags of this command tab complete") @@ -85,29 +69,25 @@ class ArgparseCompletion(Cmd): example_parser.add_argument('--choices', choices=food_item_strs, metavar="CHOICE", help="tab complete using choices") - # Tab complete from choices provided by a choices function and choices method - example_parser.add_argument('--choices_function', choices_function=choices_function, - help="tab complete using a choices_function") - example_parser.add_argument('--choices_method', choices_method=choices_method, - help="tab complete using a choices_method") + # Tab complete from choices provided by a choices_provider + example_parser.add_argument('--choices_provider', choices_provider=choices_provider, + help="tab complete using a choices_provider") - # Tab complete using a completer function and completer method - example_parser.add_argument('--completer_function', completer_function=completer_function, - help="tab complete using a completer_function") - example_parser.add_argument('--completer_method', completer_method=Cmd.path_complete, - help="tab complete using a completer_method") + # Tab complete using a completer + example_parser.add_argument('--completer', completer=Cmd.path_complete, + help="tab complete using a completer") # Demonstrate raising a CompletionError while tab completing - example_parser.add_argument('--completion_error', choices_method=choices_completion_error, + example_parser.add_argument('--completion_error', choices_provider=choices_completion_error, help="raise a CompletionError while tab completing if debug is False") # Demonstrate returning CompletionItems instead of strings - example_parser.add_argument('--completion_item', choices_function=choices_completion_item, metavar="ITEM_ID", + example_parser.add_argument('--completion_item', choices_provider=choices_completion_item, metavar="ITEM_ID", descriptive_header="Description", help="demonstrate use of CompletionItems") # Demonstrate use of arg_tokens dictionary - example_parser.add_argument('--arg_tokens', choices_function=choices_arg_tokens, + example_parser.add_argument('--arg_tokens', choices_provider=choices_arg_tokens, help="demonstrate use of arg_tokens dictionary") @with_argparser(example_parser) diff --git a/examples/modular_commands/commandset_basic.py b/examples/modular_commands/commandset_basic.py index 2ceda439..9c94e01e 100644 --- a/examples/modular_commands/commandset_basic.py +++ b/examples/modular_commands/commandset_basic.py @@ -4,8 +4,7 @@ A simple example demonstrating a loadable command set """ from typing import List -from cmd2 import Cmd, CommandSet, Statement, with_category, with_default_category -from cmd2.utils import CompletionError +from cmd2 import Cmd, CommandSet, CompletionError, Statement, with_category, with_default_category @with_default_category('Basic Completion') diff --git a/examples/modular_commands/commandset_complex.py b/examples/modular_commands/commandset_complex.py index 7c6b1300..a4343ae3 100644 --- a/examples/modular_commands/commandset_complex.py +++ b/examples/modular_commands/commandset_complex.py @@ -8,7 +8,6 @@ import argparse from typing import List import cmd2 -from cmd2 import utils @cmd2.with_default_category('Fruits') @@ -42,7 +41,7 @@ class CommandSetA(cmd2.CommandSet): self._cmd.poutput(', '.join(['{}']*len(args)).format(*args)) def complete_durian(self, text: str, line: str, begidx: int, endidx: int) -> List[str]: - return utils.basic_complete(text, line, begidx, endidx, ['stinks', 'smells', 'disgusting']) + return self._cmd.basic_complete(text, line, begidx, endidx, ['stinks', 'smells', 'disgusting']) elderberry_parser = cmd2.Cmd2ArgumentParser('elderberry') elderberry_parser.add_argument('arg1') diff --git a/examples/modular_commands_main.py b/examples/modular_commands_main.py index b698e00f..1b0ec64d 100644 --- a/examples/modular_commands_main.py +++ b/examples/modular_commands_main.py @@ -5,58 +5,13 @@ A complex example demonstrating a variety of methods to load CommandSets using a with examples of how to integrate tab completion with argparse-based commands. """ import argparse -from typing import Dict, Iterable, List, Optional +from typing import Iterable, List, Optional -from cmd2 import Cmd, Cmd2ArgumentParser, CommandSet, CompletionItem, with_argparser -from cmd2.utils import CompletionError, basic_complete from modular_commands.commandset_basic import BasicCompletionCommandSet # noqa: F401 from modular_commands.commandset_complex import CommandSetA # noqa: F401 from modular_commands.commandset_custominit import CustomInitCommandSet # noqa: F401 -# Data source for argparse.choices -food_item_strs = ['Pizza', 'Ham', 'Ham Sandwich', 'Potato'] - - -def choices_function() -> List[str]: - """Choices functions are useful when the choice list is dynamically generated (e.g. from data in a database)""" - return ['a', 'dynamic', 'list', 'goes', 'here'] - - -def completer_function(text: str, line: str, begidx: int, endidx: int) -> List[str]: - """ - A tab completion function not dependent on instance data. Since custom tab completion operations commonly - need to modify cmd2's instance variables related to tab completion, it will be rare to need a completer - function. completer_method should be used in those cases. - """ - match_against = ['a', 'dynamic', 'list', 'goes', 'here'] - return basic_complete(text, line, begidx, endidx, match_against) - - -def choices_completion_item() -> List[CompletionItem]: - """Return CompletionItem instead of strings. These give more context to what's being tab completed.""" - items = \ - { - 1: "My item", - 2: "Another item", - 3: "Yet another item" - } - return [CompletionItem(item_id, description) for item_id, description in items.items()] - - -def choices_arg_tokens(arg_tokens: Dict[str, List[str]]) -> List[str]: - """ - If a choices or completer function/method takes a value called arg_tokens, then it will be - passed a dictionary that maps the command line tokens up through the one being completed - to their argparse argument name. All values of the arg_tokens dictionary are lists, even if - a particular argument expects only 1 token. - """ - # Check if choices_function flag has appeared - values = ['choices_function', 'flag'] - if 'choices_function' in arg_tokens: - values.append('is {}'.format(arg_tokens['choices_function'][0])) - else: - values.append('not supplied') - return values +from cmd2 import Cmd, Cmd2ArgumentParser, CommandSet, with_argparser class WithCommandSets(Cmd): @@ -64,55 +19,26 @@ class WithCommandSets(Cmd): super().__init__(command_sets=command_sets) self.sport_item_strs = ['Bat', 'Basket', 'Basketball', 'Football', 'Space Ball'] - def choices_method(self) -> List[str]: - """Choices methods are useful when the choice list is based on instance data of your application""" + def choices_provider(self) -> List[str]: + """A choices provider is useful when the choice list is based on instance data of your application""" return self.sport_item_strs - def choices_completion_error(self) -> List[str]: - """ - CompletionErrors can be raised if an error occurs while tab completing. - - Example use cases - - Reading a database to retrieve a tab completion data set failed - - A previous command line argument that determines the data set being completed is invalid - """ - if self.debug: - return self.sport_item_strs - raise CompletionError("debug must be true") - # Parser for example command example_parser = Cmd2ArgumentParser(description="Command demonstrating tab completion with argparse\n" "Notice even the flags of this command tab complete") # Tab complete from a list using argparse choices. Set metavar if you don't # want the entire choices list showing in the usage text for this command. - example_parser.add_argument('--choices', choices=food_item_strs, metavar="CHOICE", + example_parser.add_argument('--choices', choices=['some', 'choices', 'here'], metavar="CHOICE", help="tab complete using choices") - # Tab complete from choices provided by a choices function and choices method - example_parser.add_argument('--choices_function', choices_function=choices_function, - help="tab complete using a choices_function") - example_parser.add_argument('--choices_method', choices_method=choices_method, - help="tab complete using a choices_method") - - # Tab complete using a completer function and completer method - example_parser.add_argument('--completer_function', completer_function=completer_function, - help="tab complete using a completer_function") - example_parser.add_argument('--completer_method', completer_method=Cmd.path_complete, - help="tab complete using a completer_method") - - # Demonstrate raising a CompletionError while tab completing - example_parser.add_argument('--completion_error', choices_method=choices_completion_error, - help="raise a CompletionError while tab completing if debug is False") - - # Demonstrate returning CompletionItems instead of strings - example_parser.add_argument('--completion_item', choices_function=choices_completion_item, metavar="ITEM_ID", - descriptive_header="Description", - help="demonstrate use of CompletionItems") + # Tab complete from choices provided by a choices provider + example_parser.add_argument('--choices_provider', choices_provider=choices_provider, + help="tab complete using a choices_provider") - # Demonstrate use of arg_tokens dictionary - example_parser.add_argument('--arg_tokens', choices_function=choices_arg_tokens, - help="demonstrate use of arg_tokens dictionary") + # Tab complete using a completer + example_parser.add_argument('--completer', completer=Cmd.path_complete, + help="tab complete using a completer") @with_argparser(example_parser) def do_example(self, _: argparse.Namespace) -> None: -- cgit v1.2.1 From e8aa84b39872956cf881e845b8464a3807b58a6e Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Tue, 18 Aug 2020 10:26:53 -0400 Subject: Updated async printing example to use thread event instead of a boolean flag --- examples/async_printing.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'examples') diff --git a/examples/async_printing.py b/examples/async_printing.py index a136d8e2..29af2d64 100755 --- a/examples/async_printing.py +++ b/examples/async_printing.py @@ -35,7 +35,7 @@ class AlerterApp(cmd2.Cmd): self.prompt = "(APR)> " # The thread that will asynchronously alert the user of events - self._stop_thread = False + self._stop_event = threading.Event() self._alerter_thread = threading.Thread() self._alert_count = 0 self._next_alert_time = 0 @@ -50,7 +50,7 @@ class AlerterApp(cmd2.Cmd): # Therefore this is the best place to start the alerter thread since there is no risk of it alerting # before the prompt is displayed. You can also start it via a command if its not something that should # be running during the entire application. See do_start_alerts(). - self._stop_thread = False + self._stop_event.clear() self._alerter_thread = threading.Thread(name='alerter', target=self._alerter_thread_func) self._alerter_thread.start() @@ -61,7 +61,7 @@ class AlerterApp(cmd2.Cmd): # After this function returns, cmdloop() releases self.terminal_lock which could make the alerter # thread think the prompt is on screen. Therefore this is the best place to stop the alerter thread. # You can also stop it via a command. See do_stop_alerts(). - self._stop_thread = True + self._stop_event.set() if self._alerter_thread.is_alive(): self._alerter_thread.join() @@ -70,13 +70,13 @@ class AlerterApp(cmd2.Cmd): if self._alerter_thread.is_alive(): print("The alert thread is already started") else: - self._stop_thread = False + self._stop_event.clear() self._alerter_thread = threading.Thread(name='alerter', target=self._alerter_thread_func) self._alerter_thread.start() def do_stop_alerts(self, _): """ Stops the alerter thread """ - self._stop_thread = True + self._stop_event.set() if self._alerter_thread.is_alive(): self._alerter_thread.join() else: @@ -166,7 +166,7 @@ class AlerterApp(cmd2.Cmd): self._alert_count = 0 self._next_alert_time = 0 - while not self._stop_thread: + while not self._stop_event.is_set(): # Always acquire terminal_lock before printing alerts or updating the prompt # To keep the app responsive, do not block on this call if self.terminal_lock.acquire(blocking=False): @@ -191,7 +191,7 @@ class AlerterApp(cmd2.Cmd): # Don't forget to release the lock self.terminal_lock.release() - time.sleep(0.5) + self._stop_event.wait(0.5) if __name__ == '__main__': -- cgit v1.2.1 From 7294ebcfdc01364da0c7b259e3985dff36bb4864 Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Tue, 1 Sep 2020 11:40:57 -0400 Subject: Added read_input() example --- examples/read_input.py | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 examples/read_input.py (limited to 'examples') diff --git a/examples/read_input.py b/examples/read_input.py new file mode 100644 index 00000000..36e92ec3 --- /dev/null +++ b/examples/read_input.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python +# coding=utf-8 +""" +A simple example demonstrating the various ways to call cmd2.Cmd.read_input() for input history and tab completion +""" +from typing import List + +import cmd2 + +EXAMPLE_COMMANDS = "Example Commands" + + +class ReadInputApp(cmd2.Cmd): + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + self.prompt = "\n" + self.prompt + self.custom_history = ['history 1', 'history 2'] + + @cmd2.with_category(EXAMPLE_COMMANDS) + def do_basic(self, _) -> None: + """Call read_input with no history or tab completion""" + print("Tab completion and up-arrow history is off") + try: + self.read_input("> ") + except EOFError: + pass + + @cmd2.with_category(EXAMPLE_COMMANDS) + def do_basic_with_history(self, _) -> None: + """Call read_input with custom history and no tab completion""" + print("Tab completion is off but up-arrow history is populated") + try: + input_str = self.read_input("> ", history=self.custom_history) + self.custom_history.append(input_str) + except EOFError: + pass + + @cmd2.with_category(EXAMPLE_COMMANDS) + def do_commands(self, _) -> None: + """Call read_input the same way cmd2 prompt does to read commands""" + print("Tab completing and up-arrow history configured for commands") + try: + self.read_input("> ", completion_mode=cmd2.CompletionMode.COMMANDS) + except EOFError: + pass + + @cmd2.with_category(EXAMPLE_COMMANDS) + def do_custom_choices(self, _) -> None: + """Call read_input to use custom history and choices""" + print("Tab completing with static choices list and custom history") + try: + input_str = self.read_input("> ", history=self.custom_history, completion_mode=cmd2.CompletionMode.CUSTOM, + choices=['choice_1', 'choice_2', 'choice_3']) + self.custom_history.append(input_str) + except EOFError: + pass + + # noinspection PyMethodMayBeStatic + def choices_provider(self) -> List[str]: + """Example choices provider function""" + return ["provider_1", "provider_2", "provider_3"] + + @cmd2.with_category(EXAMPLE_COMMANDS) + def do_custom_choices_provider(self, _) -> None: + """Call read_input to use custom history and choices provider function""" + print("Tab completing with choices from provider function and custom history") + try: + input_str = self.read_input("> ", history=self.custom_history, completion_mode=cmd2.CompletionMode.CUSTOM, + choices_provider=ReadInputApp.choices_provider) + self.custom_history.append(input_str) + except EOFError: + pass + + @cmd2.with_category(EXAMPLE_COMMANDS) + def do_custom_completer(self, _) -> None: + """all read_input to use custom history and completer function""" + print("Tab completing paths and using custom history") + try: + input_str = self.read_input("> ", history=self.custom_history, completion_mode=cmd2.CompletionMode.CUSTOM, + completer=cmd2.Cmd.path_complete) + self.custom_history.append(input_str) + except EOFError: + pass + + +if __name__ == '__main__': + import sys + app = ReadInputApp() + sys.exit(app.cmdloop()) -- cgit v1.2.1 From e4e05127e144ea45ff63a266025f2135d1d9cd72 Mon Sep 17 00:00:00 2001 From: Kevin Van Brunt Date: Tue, 1 Sep 2020 18:25:36 -0400 Subject: Updated read_input example --- examples/read_input.py | 43 +++++++++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 10 deletions(-) (limited to 'examples') diff --git a/examples/read_input.py b/examples/read_input.py index 36e92ec3..e772a106 100644 --- a/examples/read_input.py +++ b/examples/read_input.py @@ -19,7 +19,7 @@ class ReadInputApp(cmd2.Cmd): @cmd2.with_category(EXAMPLE_COMMANDS) def do_basic(self, _) -> None: """Call read_input with no history or tab completion""" - print("Tab completion and up-arrow history is off") + self.poutput("Tab completion and up-arrow history is off") try: self.read_input("> ") except EOFError: @@ -28,17 +28,18 @@ class ReadInputApp(cmd2.Cmd): @cmd2.with_category(EXAMPLE_COMMANDS) def do_basic_with_history(self, _) -> None: """Call read_input with custom history and no tab completion""" - print("Tab completion is off but up-arrow history is populated") + self.poutput("Tab completion is off but using custom history") try: input_str = self.read_input("> ", history=self.custom_history) - self.custom_history.append(input_str) except EOFError: pass + else: + self.custom_history.append(input_str) @cmd2.with_category(EXAMPLE_COMMANDS) def do_commands(self, _) -> None: """Call read_input the same way cmd2 prompt does to read commands""" - print("Tab completing and up-arrow history configured for commands") + self.poutput("Tab completing and up-arrow history configured for commands") try: self.read_input("> ", completion_mode=cmd2.CompletionMode.COMMANDS) except EOFError: @@ -47,34 +48,36 @@ class ReadInputApp(cmd2.Cmd): @cmd2.with_category(EXAMPLE_COMMANDS) def do_custom_choices(self, _) -> None: """Call read_input to use custom history and choices""" - print("Tab completing with static choices list and custom history") + self.poutput("Tab completing with static choices list and using custom history") try: input_str = self.read_input("> ", history=self.custom_history, completion_mode=cmd2.CompletionMode.CUSTOM, choices=['choice_1', 'choice_2', 'choice_3']) - self.custom_history.append(input_str) except EOFError: pass + else: + self.custom_history.append(input_str) # noinspection PyMethodMayBeStatic def choices_provider(self) -> List[str]: """Example choices provider function""" - return ["provider_1", "provider_2", "provider_3"] + return ["from_provider_1", "from_provider_2", "from_provider_3"] @cmd2.with_category(EXAMPLE_COMMANDS) def do_custom_choices_provider(self, _) -> None: """Call read_input to use custom history and choices provider function""" - print("Tab completing with choices from provider function and custom history") + self.poutput("Tab completing with choices from provider function and using custom history") try: input_str = self.read_input("> ", history=self.custom_history, completion_mode=cmd2.CompletionMode.CUSTOM, choices_provider=ReadInputApp.choices_provider) - self.custom_history.append(input_str) except EOFError: pass + else: + self.custom_history.append(input_str) @cmd2.with_category(EXAMPLE_COMMANDS) def do_custom_completer(self, _) -> None: """all read_input to use custom history and completer function""" - print("Tab completing paths and using custom history") + self.poutput("Tab completing paths and using custom history") try: input_str = self.read_input("> ", history=self.custom_history, completion_mode=cmd2.CompletionMode.CUSTOM, completer=cmd2.Cmd.path_complete) @@ -82,6 +85,26 @@ class ReadInputApp(cmd2.Cmd): except EOFError: pass + @cmd2.with_category(EXAMPLE_COMMANDS) + def do_custom_parser(self, _) -> None: + """Call read_input to use a custom history and an argument parser""" + parser = cmd2.Cmd2ArgumentParser(prog='', description="An example parser") + parser.add_argument('-o', '--option', help="an optional arg") + parser.add_argument('arg_1', help="a choice for this arg", metavar='arg_1', + choices=['my_choice', 'your_choice']) + parser.add_argument('arg_2', help="path of something", completer=cmd2.Cmd.path_complete) + + self.poutput("Tab completing with argument parser and using custom history") + self.poutput(parser.format_usage()) + + try: + input_str = self.read_input("> ", history=self.custom_history, completion_mode=cmd2.CompletionMode.CUSTOM, + parser=parser) + except EOFError: + pass + else: + self.custom_history.append(input_str) + if __name__ == '__main__': import sys -- cgit v1.2.1