Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-cmd2 for openSUSE:Factory 
checked in at 2021-06-18 10:13:14
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-cmd2 (Old)
 and      /work/SRC/openSUSE:Factory/.python-cmd2.new.2625 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-cmd2"

Fri Jun 18 10:13:14 2021 rev:38 rq:899979 version:2.1.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-cmd2/python-cmd2.changes  2021-06-11 
22:29:59.842059854 +0200
+++ /work/SRC/openSUSE:Factory/.python-cmd2.new.2625/python-cmd2.changes        
2021-06-18 10:13:21.613960455 +0200
@@ -1,0 +2,8 @@
+Mon Jun 14 15:58:07 UTC 2021 - Martin Hauke <[email protected]>
+
+- Udpate to version 2.1.0
+  Enhancements
+  * Converted persistent history files from pickle to compressed
+    JSON.
+
+-------------------------------------------------------------------

Old:
----
  cmd2-2.0.1.tar.gz

New:
----
  cmd2-2.1.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-cmd2.spec ++++++
--- /var/tmp/diff_new_pack.gzpglB/_old  2021-06-18 10:13:22.085961079 +0200
+++ /var/tmp/diff_new_pack.gzpglB/_new  2021-06-18 10:13:22.085961079 +0200
@@ -19,7 +19,7 @@
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 %define         skip_python2 1
 Name:           python-cmd2
-Version:        2.0.1
+Version:        2.1.0
 Release:        0
 Summary:        Extra features for standard library's cmd module
 License:        MIT

++++++ cmd2-2.0.1.tar.gz -> cmd2-2.1.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cmd2-2.0.1/.github/workflows/lint.yml 
new/cmd2-2.1.0/.github/workflows/lint.yml
--- old/cmd2-2.0.1/.github/workflows/lint.yml   2021-04-06 04:23:51.000000000 
+0200
+++ new/cmd2-2.1.0/.github/workflows/lint.yml   2021-06-14 17:29:07.000000000 
+0200
@@ -23,6 +23,6 @@
         with:
           python-version: ${{ matrix.python-version }}
       - name: Install python prerequisites
-        run: pip install -U --user pip setuptools setuptools-scm flake8
+        run: pip install -U --user pip setuptools setuptools-scm nox
       - name: Lint
-        run: python -m flake8 .
+        run: python -m nox --non-interactive --session validate-${{ 
matrix.python-version }} -k flake8
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cmd2-2.0.1/.github/workflows/mypy.yml 
new/cmd2-2.1.0/.github/workflows/mypy.yml
--- old/cmd2-2.0.1/.github/workflows/mypy.yml   2021-04-07 02:10:44.000000000 
+0200
+++ new/cmd2-2.1.0/.github/workflows/mypy.yml   2021-06-14 17:29:07.000000000 
+0200
@@ -23,6 +23,6 @@
         with:
           python-version: ${{ matrix.python-version }}
       - name: Install python prerequisites
-        run: pip install -U --user pip setuptools setuptools-scm mypy
+        run: pip install -U --user pip setuptools setuptools-scm nox
       - name: MyPy
-        run: python -m mypy cmd2
+        run: python -m nox --non-interactive --session validate-${{ 
matrix.python-version }} -k mypy  # Run nox for mypy
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cmd2-2.0.1/CHANGELOG.md new/cmd2-2.1.0/CHANGELOG.md
--- old/cmd2-2.0.1/CHANGELOG.md 2021-06-07 06:30:12.000000000 +0200
+++ new/cmd2-2.1.0/CHANGELOG.md 2021-06-14 17:34:22.000000000 +0200
@@ -1,3 +1,7 @@
+## 2.1.0 (June 14, 2021)
+* Enhancements
+   * Converted persistent history files from pickle to compressed JSON
+
 ## 2.0.1 (June 7, 2021)
 * Bug Fixes
   * Exclude `plugins` and `tests_isolated` directories from tarball published 
to PyPI for `cmd2` release
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cmd2-2.0.1/PKG-INFO new/cmd2-2.1.0/PKG-INFO
--- old/cmd2-2.0.1/PKG-INFO     2021-06-07 06:36:34.064932000 +0200
+++ new/cmd2-2.1.0/PKG-INFO     2021-06-14 17:43:16.063191200 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: cmd2
-Version: 2.0.1
+Version: 2.1.0
 Summary: cmd2 - quickly build feature-rich and user-friendly interactive 
command line applications in Python
 Home-page: https://github.com/python-cmd2/cmd2
 Author: Catherine Devlin
@@ -407,3 +407,4 @@
 Description-Content-Type: text/markdown
 Provides-Extra: test
 Provides-Extra: dev
+Provides-Extra: validate
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cmd2-2.0.1/cmd2/cmd2.py new/cmd2-2.1.0/cmd2/cmd2.py
--- old/cmd2-2.0.1/cmd2/cmd2.py 2021-05-25 21:49:52.000000000 +0200
+++ new/cmd2-2.1.0/cmd2/cmd2.py 2021-06-14 17:29:07.000000000 +0200
@@ -34,7 +34,6 @@
 import glob
 import inspect
 import os
-import pickle
 import pydoc
 import re
 import sys
@@ -4443,15 +4442,13 @@
     def _initialize_history(self, hist_file: str) -> None:
         """Initialize history using history related attributes
 
-        This function can determine whether history is saved in the prior 
text-based
-        format (one line of input is stored as one line in the file), or the 
new-as-
-        of-version 0.9.13 pickle based format.
-
-        History created by versions <= 0.9.12 is in readline format, i.e. 
plain text files.
-
-        Initializing history does not effect history files on disk, versions 
>= 0.9.13 always
-        write history in the pickle format.
+        :param hist_file: optional path to persistent history file. If 
specified, then history from
+                          previous sessions will be included. Additionally, 
all history will be written
+                          to this file when the application exits.
         """
+        import json
+        import lzma
+
         self.history = History()
         # with no persistent history, nothing else in this method is relevant
         if not hist_file:
@@ -4474,36 +4471,31 @@
             self.perror(f"Error creating persistent history file directory 
'{hist_file_dir}': {ex}")
             return
 
-        # first we try and unpickle the history file
-        history = History()
-
+        # Read and process history file
         try:
             with open(hist_file, 'rb') as fobj:
-                history = pickle.load(fobj)
-        except (
-            AttributeError,
-            EOFError,
-            FileNotFoundError,
-            ImportError,
-            IndexError,
-            KeyError,
-            ValueError,
-            pickle.UnpicklingError,
-        ):
-            # If any of these errors occur when attempting to unpickle, just 
use an empty history
+                compressed_bytes = fobj.read()
+            history_json = 
lzma.decompress(compressed_bytes).decode(encoding='utf-8')
+            self.history = History.from_json(history_json)
+        except FileNotFoundError:
+            # Just use an empty history
             pass
         except OSError as ex:
             self.perror(f"Cannot read persistent history file '{hist_file}': 
{ex}")
             return
+        except (json.JSONDecodeError, lzma.LZMAError, KeyError, 
UnicodeDecodeError, ValueError) as ex:
+            self.perror(
+                f"Error processing persistent history file '{hist_file}': 
{ex}\n"
+                f"The history file will be recreated when this application 
exits."
+            )
 
-        self.history = history
         self.history.start_session()
         self.persistent_history_file = hist_file
 
         # populate readline history
         if rl_type != RlType.NONE:
             last = None
-            for item in history:
+            for item in self.history:
                 # Break the command into its individual lines
                 for line in item.raw.splitlines():
                     # readline only adds a single entry for multiple 
sequential identical lines
@@ -4520,14 +4512,19 @@
         atexit.register(self._persist_history)
 
     def _persist_history(self) -> None:
-        """Write history out to the history file"""
+        """Write history out to the persistent history file as compressed 
JSON"""
+        import lzma
+
         if not self.persistent_history_file:
             return
 
         self.history.truncate(self._persistent_history_length)
         try:
+            history_json = self.history.to_json()
+            compressed_bytes = 
lzma.compress(history_json.encode(encoding='utf-8'))
+
             with open(self.persistent_history_file, 'wb') as fobj:
-                pickle.dump(self.history, fobj)
+                fobj.write(compressed_bytes)
         except OSError as ex:
             self.perror(f"Cannot write persistent history file 
'{self.persistent_history_file}': {ex}")
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cmd2-2.0.1/cmd2/history.py 
new/cmd2-2.1.0/cmd2/history.py
--- old/cmd2-2.0.1/cmd2/history.py      2021-04-07 02:10:44.000000000 +0200
+++ new/cmd2-2.1.0/cmd2/history.py      2021-06-14 17:29:07.000000000 +0200
@@ -3,12 +3,15 @@
 History management classes
 """
 
+import json
 import re
 from collections import (
     OrderedDict,
 )
 from typing import (
+    Any,
     Callable,
+    Dict,
     Iterable,
     List,
     Optional,
@@ -33,6 +36,9 @@
     _listformat = ' {:>4}  {}'
     _ex_listformat = ' {:>4}x {}'
 
+    # Used in JSON dictionaries
+    _statement_field = 'statement'
+
     statement: Statement = attr.ib(default=None, 
validator=attr.validators.instance_of(Statement))
 
     def __str__(self) -> str:
@@ -94,6 +100,22 @@
 
         return ret_str
 
+    def to_dict(self) -> Dict[str, Any]:
+        """Utility method to convert this HistoryItem into a dictionary for 
use in persistent JSON history files"""
+        return {HistoryItem._statement_field: self.statement.to_dict()}
+
+    @staticmethod
+    def from_dict(source_dict: Dict[str, Any]) -> 'HistoryItem':
+        """
+        Utility method to restore a HistoryItem from a dictionary
+
+        :param source_dict: source data dictionary (generated using to_dict())
+        :return: HistoryItem object
+        :raises KeyError: if source_dict is missing required elements
+        """
+        statement_dict = source_dict[HistoryItem._statement_field]
+        return HistoryItem(Statement.from_dict(statement_dict))
+
 
 class History(List[HistoryItem]):
     """A list of :class:`~cmd2.history.HistoryItem` objects with additional 
methods
@@ -109,6 +131,11 @@
     class to gain access to the historical record.
     """
 
+    # Used in JSON dictionaries
+    _history_version = '1.0.0'
+    _history_version_field = 'history_version'
+    _history_items_field = 'history_items'
+
     def __init__(self, seq: Iterable[HistoryItem] = ()) -> None:
         super(History, self).__init__(seq)
         self.session_start_index = 0
@@ -301,3 +328,36 @@
             if filter_func is None or filter_func(self[index]):
                 results[index + 1] = self[index]
         return results
+
+    def to_json(self) -> str:
+        """Utility method to convert this History into a JSON string for use 
in persistent history files"""
+        json_dict = {
+            History._history_version_field: History._history_version,
+            History._history_items_field: [hi.to_dict() for hi in self],
+        }
+        return json.dumps(json_dict, ensure_ascii=False, indent=2)
+
+    @staticmethod
+    def from_json(history_json: str) -> 'History':
+        """
+        Utility method to restore History from a JSON string
+
+        :param history_json: history data as JSON string (generated using 
to_json())
+        :return: History object
+        :raises json.JSONDecodeError: if passed invalid JSON string
+        :raises KeyError: if JSON is missing required elements
+        :raises ValueError: if history version in JSON isn't supported
+        """
+        json_dict = json.loads(history_json)
+        version = json_dict[History._history_version_field]
+        if version != History._history_version:
+            raise ValueError(
+                f"Unsupported history file version: {version}. This 
application uses version {History._history_version}."
+            )
+
+        items = json_dict[History._history_items_field]
+        history = History()
+        for hi_dict in items:
+            history.append(HistoryItem.from_dict(hi_dict))
+
+        return history
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cmd2-2.0.1/cmd2/parsing.py 
new/cmd2-2.1.0/cmd2/parsing.py
--- old/cmd2-2.0.1/cmd2/parsing.py      2021-04-14 00:10:10.000000000 +0200
+++ new/cmd2-2.1.0/cmd2/parsing.py      2021-06-14 17:29:07.000000000 +0200
@@ -147,6 +147,9 @@
     # if output was redirected, the destination file token (quotes preserved)
     output_to: str = attr.ib(default='', 
validator=attr.validators.instance_of(str))
 
+    # Used in JSON dictionaries
+    _args_field = 'args'
+
     def __new__(cls, value: object, *pos_args: Any, **kw_args: Any) -> 
'Statement':
         """Create a new instance of Statement.
 
@@ -221,6 +224,31 @@
 
         return rtn
 
+    def to_dict(self) -> Dict[str, Any]:
+        """Utility method to convert this Statement into a dictionary for use 
in persistent JSON history files"""
+        return self.__dict__.copy()
+
+    @staticmethod
+    def from_dict(source_dict: Dict[str, Any]) -> 'Statement':
+        """
+        Utility method to restore a Statement from a dictionary
+
+        :param source_dict: source data dictionary (generated using to_dict())
+        :return: Statement object
+        :raises KeyError: if source_dict is missing required elements
+        """
+        # value needs to be passed as a positional argument. It corresponds to 
the args field.
+        try:
+            value = source_dict[Statement._args_field]
+        except KeyError as ex:
+            raise KeyError(f"Statement dictionary is missing {ex} field")
+
+        # Pass the rest at kwargs (minus args)
+        kwargs = source_dict.copy()
+        del kwargs[Statement._args_field]
+
+        return Statement(value, **kwargs)
+
 
 class StatementParser:
     """Parse user input as a string into discrete command components."""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cmd2-2.0.1/cmd2.egg-info/PKG-INFO 
new/cmd2-2.1.0/cmd2.egg-info/PKG-INFO
--- old/cmd2-2.0.1/cmd2.egg-info/PKG-INFO       2021-06-07 06:36:33.000000000 
+0200
+++ new/cmd2-2.1.0/cmd2.egg-info/PKG-INFO       2021-06-14 17:43:15.000000000 
+0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: cmd2
-Version: 2.0.1
+Version: 2.1.0
 Summary: cmd2 - quickly build feature-rich and user-friendly interactive 
command line applications in Python
 Home-page: https://github.com/python-cmd2/cmd2
 Author: Catherine Devlin
@@ -407,3 +407,4 @@
 Description-Content-Type: text/markdown
 Provides-Extra: test
 Provides-Extra: dev
+Provides-Extra: validate
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cmd2-2.0.1/cmd2.egg-info/SOURCES.txt 
new/cmd2-2.1.0/cmd2.egg-info/SOURCES.txt
--- old/cmd2-2.0.1/cmd2.egg-info/SOURCES.txt    2021-06-07 06:36:33.000000000 
+0200
+++ new/cmd2-2.1.0/cmd2.egg-info/SOURCES.txt    2021-06-14 17:43:15.000000000 
+0200
@@ -110,7 +110,6 @@
 docs/plugins/external_test.rst
 docs/plugins/index.rst
 examples/.cmd2rc
-examples/.coverage
 examples/alias_startup.py
 examples/arg_decorators.py
 examples/arg_print.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cmd2-2.0.1/cmd2.egg-info/requires.txt 
new/cmd2-2.1.0/cmd2.egg-info/requires.txt
--- old/cmd2-2.0.1/cmd2.egg-info/requires.txt   2021-06-07 06:36:33.000000000 
+0200
+++ new/cmd2-2.1.0/cmd2.egg-info/requires.txt   2021-06-14 17:43:15.000000000 
+0200
@@ -14,17 +14,18 @@
 pyreadline3
 
 [dev]
-pytest>=4.6
 codecov
+doc8
+flake8
+invoke
+mypy==0.902
+nox
+pytest>=4.6
 pytest-cov
 pytest-mock
-nox
-flake8
 sphinx
 sphinx-rtd-theme
 sphinx-autobuild
-doc8
-invoke
 twine>=1.11
 
 [test]
@@ -36,3 +37,7 @@
 
 [test:sys_platform == "darwin"]
 gnureadline
+
+[validate]
+flake8
+mypy==0.902
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cmd2-2.0.1/docs/conf.py new/cmd2-2.1.0/docs/conf.py
--- old/cmd2-2.0.1/docs/conf.py 2021-04-06 04:23:51.000000000 +0200
+++ new/cmd2-2.1.0/docs/conf.py 2021-06-14 17:29:07.000000000 +0200
@@ -181,7 +181,7 @@
     ('py:class', 'TextIO'),
     ('py:class', 'Union[None, Iterable, Callable]'),
     ('py:class', 'argparse._SubParsersAction'),
-    ('py:class', '_T'),
+    ('py:class', 'cmd2.utils._T'),
     ('py:class', 'StdSim'),
     ('py:class', 'frame'),
 ]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cmd2-2.0.1/docs/features/history.rst 
new/cmd2-2.1.0/docs/features/history.rst
--- old/cmd2-2.0.1/docs/features/history.rst    2020-02-26 00:41:35.000000000 
+0100
+++ new/cmd2-2.1.0/docs/features/history.rst    2021-06-14 17:29:07.000000000 
+0200
@@ -8,7 +8,7 @@
 history.
 
 :class:`cmd2.Cmd` offers the same ``readline`` capabilities, but also maintains
-it's own data structures for the history of all commands entered by the user.
+its own data structures for the history of all commands entered by the user.
 When the class is initialized, it creates an instance of the
 :class:`cmd2.history.History` class (which is a subclass of ``list``) as
 :data:`cmd2.Cmd.history`.
@@ -20,8 +20,9 @@
 ``cmd2`` adds the option of making this history persistent via optional
 arguments to :meth:`cmd2.Cmd.__init__`. If you pass a filename in the
 ``persistent_history_file`` argument, the contents of :data:`cmd2.Cmd.history`
-will be pickled into that history file. We chose to use pickle instead of plain
-text so that we can save the results of parsing all the commands.
+will be written as compressed JSON to that history file. We chose this format
+instead of plain text to preserve the complete :class:`cmd2.Statement` object
+for each command.
 
 .. note::
 
@@ -41,9 +42,7 @@
 class, and the :class:`cmd2.history.HistoryItem` class are all part of the
 public API for :class:`cmd2.Cmd`. You could use these classes to implement
 write your own ``history`` command (see below for documentation on how the
-included ``history`` command works). If you don't like pickled history, you
-could implement your own mechanism for saving and loading history from a plain
-text file.
+included ``history`` command works).
 
 
 For Users
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cmd2-2.0.1/examples/.coverage 
new/cmd2-2.1.0/examples/.coverage
--- old/cmd2-2.0.1/examples/.coverage   2019-06-07 05:49:31.000000000 +0200
+++ new/cmd2-2.1.0/examples/.coverage   1970-01-01 01:00:00.000000000 +0100
@@ -1 +0,0 @@
-!coverage.py: This is a private format, don't read it directly!{"lines":{}}
\ No newline at end of file
Binary files old/cmd2-2.0.1/examples/cmd2_history.dat and 
new/cmd2-2.1.0/examples/cmd2_history.dat differ
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cmd2-2.0.1/noxfile.py new/cmd2-2.1.0/noxfile.py
--- old/cmd2-2.0.1/noxfile.py   2021-04-06 04:23:51.000000000 +0200
+++ new/cmd2-2.1.0/noxfile.py   2021-06-14 17:29:07.000000000 +0200
@@ -39,3 +39,10 @@
             '--no-pty',
             '--append-cov',
         )
+
+
[email protected](python=['3.8', '3.9'])
[email protected]('step', ['mypy', 'flake8'])
+def validate(session, step):
+    session.install('invoke', './[validate]')
+    session.run('invoke', step)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cmd2-2.0.1/setup.py new/cmd2-2.1.0/setup.py
--- old/cmd2-2.0.1/setup.py     2021-04-06 04:23:51.000000000 +0200
+++ new/cmd2-2.1.0/setup.py     2021-06-14 17:29:07.000000000 +0200
@@ -66,19 +66,24 @@
     ],
     # development only dependencies:  install with 'pip install -e .[dev]'
     'dev': [
-        "pytest>=4.6",
         'codecov',
+        'doc8',
+        'flake8',
+        'invoke',
+        'mypy==0.902',
+        'nox',
+        "pytest>=4.6",
         'pytest-cov',
         'pytest-mock',
-        'nox',
-        'flake8',
         'sphinx',
         'sphinx-rtd-theme',
         'sphinx-autobuild',
-        'doc8',
-        'invoke',
         'twine>=1.11',
     ],
+    'validate': [
+        'flake8',
+        'mypy==0.902',
+    ],
 }
 
 PACKAGE_DATA = {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cmd2-2.0.1/tests/test_history.py 
new/cmd2-2.1.0/tests/test_history.py
--- old/cmd2-2.0.1/tests/test_history.py        2021-05-25 21:49:52.000000000 
+0200
+++ new/cmd2-2.1.0/tests/test_history.py        2021-06-14 17:29:07.000000000 
+0200
@@ -60,6 +60,72 @@
     return h
 
 
+# Represents the hist fixture's JSON
+hist_json = (
+    '{\n'
+    '  "history_version": "1.0.0",\n'
+    '  "history_items": [\n'
+    '    {\n'
+    '      "statement": {\n'
+    '        "args": "",\n'
+    '        "raw": "first",\n'
+    '        "command": "",\n'
+    '        "arg_list": [],\n'
+    '        "multiline_command": "",\n'
+    '        "terminator": "",\n'
+    '        "suffix": "",\n'
+    '        "pipe_to": "",\n'
+    '        "output": "",\n'
+    '        "output_to": ""\n'
+    '      }\n'
+    '    },\n'
+    '    {\n'
+    '      "statement": {\n'
+    '        "args": "",\n'
+    '        "raw": "second",\n'
+    '        "command": "",\n'
+    '        "arg_list": [],\n'
+    '        "multiline_command": "",\n'
+    '        "terminator": "",\n'
+    '        "suffix": "",\n'
+    '        "pipe_to": "",\n'
+    '        "output": "",\n'
+    '        "output_to": ""\n'
+    '      }\n'
+    '    },\n'
+    '    {\n'
+    '      "statement": {\n'
+    '        "args": "",\n'
+    '        "raw": "third",\n'
+    '        "command": "",\n'
+    '        "arg_list": [],\n'
+    '        "multiline_command": "",\n'
+    '        "terminator": "",\n'
+    '        "suffix": "",\n'
+    '        "pipe_to": "",\n'
+    '        "output": "",\n'
+    '        "output_to": ""\n'
+    '      }\n'
+    '    },\n'
+    '    {\n'
+    '      "statement": {\n'
+    '        "args": "",\n'
+    '        "raw": "fourth",\n'
+    '        "command": "",\n'
+    '        "arg_list": [],\n'
+    '        "multiline_command": "",\n'
+    '        "terminator": "",\n'
+    '        "suffix": "",\n'
+    '        "pipe_to": "",\n'
+    '        "output": "",\n'
+    '        "output_to": ""\n'
+    '      }\n'
+    '    }\n'
+    '  ]\n'
+    '}'
+)
+
+
 @pytest.fixture
 def persisted_hist():
     from cmd2.cmd2 import (
@@ -256,6 +322,37 @@
     assert hist.get(2).statement.raw == 'fourth'
 
 
+def test_history_to_json(hist):
+    assert hist_json == hist.to_json()
+
+
+def test_history_from_json(hist):
+    import json
+
+    from cmd2.history import (
+        History,
+    )
+
+    assert hist.from_json(hist_json) == hist
+
+    # Test invalid JSON
+    with pytest.raises(json.JSONDecodeError):
+        hist.from_json("")
+
+    # Send JSON with missing required element
+    with pytest.raises(KeyError):
+        hist.from_json("{}")
+
+    # Create JSON with invalid history version
+    backed_up_ver = History._history_version
+    History._history_version = "BAD_VERSION"
+    invalid_ver_json = hist.to_json()
+    History._history_version = backed_up_ver
+
+    with pytest.raises(ValueError):
+        hist.from_json(invalid_ver_json)
+
+
 #
 # test HistoryItem()
 #
@@ -704,7 +801,7 @@
 #
 @pytest.fixture(scope="session")
 def hist_file():
-    fd, filename = tempfile.mkstemp(prefix='hist_file', suffix='.txt')
+    fd, filename = tempfile.mkstemp(prefix='hist_file', suffix='.dat')
     os.close(fd)
     yield filename
     # teardown code
@@ -764,31 +861,6 @@
     assert 'Cannot read' in err
 
 
-def test_history_file_conversion_no_truncate_on_init(hist_file, capsys):
-    # make sure we don't truncate the plain text history file on init
-    # it shouldn't get converted to pickle format until we save history
-
-    # first we need some plain text commands in the history file
-    with open(hist_file, 'w') as hfobj:
-        hfobj.write('help\n')
-        hfobj.write('alias\n')
-        hfobj.write('alias create s shortcuts\n')
-
-    # Create a new cmd2 app
-    cmd2.Cmd(persistent_history_file=hist_file)
-
-    # history should be initialized, but the file on disk should
-    # still be plain text
-    with open(hist_file, 'r') as hfobj:
-        histlist = hfobj.readlines()
-
-    assert len(histlist) == 3
-    # history.get() is overridden to be one based, not zero based
-    assert histlist[0] == 'help\n'
-    assert histlist[1] == 'alias\n'
-    assert histlist[2] == 'alias create s shortcuts\n'
-
-
 def test_history_populates_readline(hist_file):
     # - create a cmd2 with persistent history
     app = cmd2.Cmd(persistent_history_file=hist_file)
@@ -822,7 +894,7 @@
 
 #
 # test cmd2's ability to write out history on exit
-# we are testing the _persist_history_on_exit() method, and
+# we are testing the _persist_history() method, and
 # we assume that the atexit module will call this method
 # properly
 #
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cmd2-2.0.1/tests/test_parsing.py 
new/cmd2-2.1.0/tests/test_parsing.py
--- old/cmd2-2.0.1/tests/test_parsing.py        2021-04-06 04:23:51.000000000 
+0200
+++ new/cmd2-2.1.0/tests/test_parsing.py        2021-06-14 17:29:07.000000000 
+0200
@@ -13,6 +13,7 @@
     utils,
 )
 from cmd2.parsing import (
+    Statement,
     StatementParser,
     shlex_split,
 )
@@ -944,6 +945,26 @@
         statement.raw = 'baz'
 
 
+def test_statement_as_dict(parser):
+    # Make sure to_dict() results can be restored to identical Statement
+    statement = parser.parse("!ls > out.txt")
+    assert statement == Statement.from_dict(statement.to_dict())
+
+    statement = parser.parse("!ls | grep text")
+    assert statement == Statement.from_dict(statement.to_dict())
+
+    statement = parser.parse("multiline arg; suffix")
+    assert statement == Statement.from_dict(statement.to_dict())
+
+    # from_dict() should raise KeyError if required field is missing
+    statement = parser.parse("command")
+    statement_dict = statement.to_dict()
+    del statement_dict[Statement._args_field]
+
+    with pytest.raises(KeyError):
+        Statement.from_dict(statement_dict)
+
+
 def test_is_valid_command_invalid(mocker, parser):
     # Non-string command
     # noinspection PyTypeChecker

Reply via email to