Repository: python-cmd2/cmd2

Issue:
Add method to know if you are in a script
I frequently use command finalization hooks to print something just before returning to the prompt. I don't want the output to occur after each command running in a Python or text script though.

Currently I do the following to know if I'm in a script:
```Python
if self._in_py or self._current_script_dir is not None:
    # In script
````

It would be better to provide developers with a method so they don't have to reference protected members.

Discussion:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c69a0549..03fbef38 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,9 @@
     If these functions have an argument called `arg_tokens`, then AutoCompleter will automatically pass this
     dictionary to them.
     * Added CompletionError class that can be raised during argparse-based tab completion and printed to the user
+    * Added the following convenience methods
+        - `Cmd.in_script()` - return whether a text script is running
+        - `Cmd.in_pyscript()` - return whether a pyscript is running
 
 ## 0.9.16 (August 7, 2019)
 * Bug Fixes
diff --git a/cmd2/cmd2.py b/cmd2/cmd2.py
index 6bfcfdc8..d353af14 100755
--- a/cmd2/cmd2.py
+++ b/cmd2/cmd2.py
@@ -626,11 +626,6 @@ class Cmd(cmd.Cmd):
         """
         return ansi.strip_ansi(self.prompt)
 
-    @property
-    def aliases(self) -> Dict[str, str]:
-        """Read-only property to access the aliases stored in the StatementParser."""
-        return self.statement_parser.aliases
-
     def poutput(self, msg: Any, *, end: str = '\n') -> None:
         """Print message to self.stdout and appends a newline by default
 
@@ -744,7 +739,7 @@ class Cmd(cmd.Cmd):
 
                 # Don't attempt to use a pager that can block if redirecting or running a script (either text or Python)
                 # Also only attempt to use a pager if actually running in a real fully functional terminal
-                if functional_terminal and not self._redirecting and not self._in_py and not self._script_dir:
+                if functional_terminal and not self._redirecting and not self.in_pyscript() and not self.in_script():
                     if ansi.allow_ansi.lower() == ansi.ANSI_NEVER.lower():
                         msg_str = ansi.strip_ansi(msg_str)
 
@@ -1606,6 +1601,19 @@ class Cmd(cmd.Cmd):
         tokens_to_parse = raw_tokens if preserve_quotes else tokens
         return completer.complete_command(tokens_to_parse, text, line, begidx, endidx)
 
+    def in_script(self) -> bool:
+        """Return whether a text script is running"""
+        return self._current_script_dir is not None
+
+    def in_pyscript(self) -> bool:
+        """Return whether a pyscript is running"""
+        return self._in_py
+
+    @property
+    def aliases(self) -> Dict[str, str]:
+        """Read-only property to access the aliases stored in the StatementParser"""
+        return self.statement_parser.aliases
+
     def get_names(self):
         """Return an alphabetized list of names comprising the attributes of the cmd2 class instance."""
         return dir(self)
@@ -3228,7 +3236,7 @@ class Cmd(cmd.Cmd):
         :return: True if running of commands should stop
         """
         from .py_bridge import PyBridge
-        if self._in_py:
+        if self.in_pyscript():
             err = "Recursively entering interactive Python consoles is not allowed."
             self.perror(err)
             return


Test patch:
diff --git a/tests/test_cmd2.py b/tests/test_cmd2.py
index 94a49a95..25273b4c 100755
--- a/tests/test_cmd2.py
+++ b/tests/test_cmd2.py
@@ -20,7 +20,7 @@ except ImportError:
     from unittest import mock
 
 import cmd2
-from cmd2 import ansi, clipboard, constants, utils
+from cmd2 import ansi, clipboard, constants, plugin, utils
 from .conftest import run_cmd, normalize, verify_help_text, HELP_HISTORY
 from .conftest import SHORTCUTS_TXT, SHOW_TXT, SHOW_LONG, complete_tester
 
@@ -461,6 +461,24 @@ def test_relative_run_script_requires_an_argument(base_app):
     out, err = run_cmd(base_app, '_relative_run_script')
     assert 'Error: the following arguments' in err[1]
 
+def test_in_script(request):
+    class HookApp(cmd2.Cmd):
+        def __init__(self, *args, **kwargs):
+            super().__init__(*args, **kwargs)
+            self.register_cmdfinalization_hook(self.hook)
+
+        def hook(self: cmd2.Cmd, data: plugin.CommandFinalizationData) -> plugin.CommandFinalizationData:
+            if self.in_script():
+                self.poutput("WE ARE IN SCRIPT")
+            return data
+
+    hook_app = HookApp()
+    test_dir = os.path.dirname(request.module.__file__)
+    filename = os.path.join(test_dir, 'script.txt')
+    out, err = run_cmd(hook_app, 'run_script {}'.format(filename))
+
+    assert "WE ARE IN SCRIPT" in out[-1]
+
 def test_output_redirection(base_app):
     fd, filename = tempfile.mkstemp(prefix='cmd2_test', suffix='.txt')
     os.close(fd)