Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-calmjs.parse for 
openSUSE:Factory checked in at 2023-11-13 22:17:56
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-calmjs.parse (Old)
 and      /work/SRC/openSUSE:Factory/.python-calmjs.parse.new.17445 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-calmjs.parse"

Mon Nov 13 22:17:56 2023 rev:7 rq:1124864 version:1.3.1

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-calmjs.parse/python-calmjs.parse.changes  
2023-06-23 21:53:39.498977225 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-calmjs.parse.new.17445/python-calmjs.parse.changes
       2023-11-13 22:20:33.450810508 +0100
@@ -1,0 +2,12 @@
+Fri Nov 10 11:43:35 UTC 2023 - Dirk Müller <dmuel...@suse.com>
+
+- update to 1.3.1:
+  * Modified existing ``setup.py`` hook from an install hook to a
+    build hook to ensure the generated module files are present.
+    Should any of those modules are missing and the required
+    dependencies for are not present (i.e. ``ply`` and
+    ``setuptools``), the build will result in a
+    non-zero exit status and the documented error message should
+    reflect which of the required dependencies are missing.
+
+-------------------------------------------------------------------

Old:
----
  1.3.0.tar.gz

New:
----
  1.3.1.tar.gz

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

Other differences:
------------------
++++++ python-calmjs.parse.spec ++++++
--- /var/tmp/diff_new_pack.DUTDBM/_old  2023-11-13 22:20:33.942828624 +0100
+++ /var/tmp/diff_new_pack.DUTDBM/_new  2023-11-13 22:20:33.942828624 +0100
@@ -18,7 +18,7 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-calmjs.parse
-Version:        1.3.0
+Version:        1.3.1
 Release:        0
 Summary:        Various parsers for ECMA standards
 License:        MIT

++++++ 1.3.0.tar.gz -> 1.3.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/calmjs.parse-1.3.0/.github/workflows/build.yml 
new/calmjs.parse-1.3.1/.github/workflows/build.yml
--- old/calmjs.parse-1.3.0/.github/workflows/build.yml  2021-10-08 
13:04:59.000000000 +0200
+++ new/calmjs.parse-1.3.1/.github/workflows/build.yml  2023-10-29 
01:08:25.000000000 +0200
@@ -10,7 +10,7 @@
       - 1.2.x
       - 1.3.x
     tags:
-      - 1.3.0
+      - 1.3.1
   pull_request:
     branches:
       - master
@@ -21,34 +21,48 @@
     runs-on: ${{ matrix.os }}
     strategy:
       matrix:
-        os: [ubuntu-latest, macos-latest]
-        python-version: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9, "3.10", pypy2, pypy3]
+        os: [ubuntu-22.04, macos-latest]
+        python-version: [3.8, 3.9, "3.10", "3.11", "3.12", "pypy3.9"]
         include:
+          - os: ubuntu-22.04
+            python-version: "2.7"
           - os: windows-latest
             python-version: 3.9
           - os: windows-latest
             python-version: "3.10"
+          - os: windows-latest
+            python-version: "3.11"
+          - os: windows-latest
+            python-version: "3.12"
         exclude:
           - os: macos-latest
             python-version: 3.5
           - os: macos-latest
             python-version: 3.6
           - os: macos-latest
-            python-version: pypy2
-          - os: macos-latest
-            python-version: pypy3
+            python-version: "pypy3.9"
 
 
     steps:
-    - uses: actions/checkout@v2
-    - name: Set up Python ${{ matrix.python-version }}
-      uses: actions/setup-python@v2
+    - uses: actions/checkout@v3
+    - name: Set up Python ${{ matrix.python-version }} via setup-python
+      if: matrix.python-version != '2.7'
+      uses: actions/setup-python@v4
       with:
         python-version: ${{ matrix.python-version }}
+    - name: Set up Python ${{ matrix.python-version }} via apt-get
+      if: matrix.python-version == '2.7'
+      run: |
+        set -eux
+        sudo apt-get update
+        sudo apt-get install -y python2 python3-virtualenv
+        virtualenv -p python2 "${{ runner.temp }}/venv"
+        echo "${{ runner.temp }}/venv/bin" >> $GITHUB_PATH
     - name: Install dependencies
       run: |
         python -m pip install --upgrade pip
-        python -m pip install coverage flake8
+        # must install all dependencies so the tab modules can be generated
+        python -m pip install coverage flake8 ply setuptools
         python -m pip install -e .
     - name: Lint with flake8
       run: |
@@ -57,9 +71,13 @@
       run: |
         python -OO -m unittest calmjs.parse.tests.make_suite
         coverage run --include=src/* -m unittest calmjs.parse.tests.make_suite
+    # Python 3.12 on Windows resulted in MemoryError here, so optional.
+    - name: Coverage report
+      run: |
         coverage report -m
+      continue-on-error: true
     - name: Coveralls
-      if: ${{ matrix.os == 'ubuntu-latest' && matrix.python-version != '2.7' 
&& matrix.python-version != 'pypy2' }}
+      if: ${{ matrix.os == 'ubuntu-22.04' && matrix.python-version != '2.7' && 
matrix.python-version != 'pypy2' }}
       env:
         GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
       run: |
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/calmjs.parse-1.3.0/CHANGES.rst 
new/calmjs.parse-1.3.1/CHANGES.rst
--- old/calmjs.parse-1.3.0/CHANGES.rst  2021-10-08 13:04:59.000000000 +0200
+++ new/calmjs.parse-1.3.1/CHANGES.rst  2023-10-29 01:08:25.000000000 +0200
@@ -1,6 +1,16 @@
 Changelog
 =========
 
+1.3.1 - 2023-10-28
+------------------
+
+- Modified existing ``setup.py`` hook from an install hook to a build
+  hook to ensure the generated module files are present.  Should any of
+  those modules are missing and the required dependencies for are not
+  present (i.e. ``ply`` and ``setuptools``), the build will result in a
+  non-zero exit status and the documented error message should reflect
+  which of the required dependencies are missing.
+
 1.3.0 - 2021-10-08
 ------------------
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/calmjs.parse-1.3.0/README.rst 
new/calmjs.parse-1.3.1/README.rst
--- old/calmjs.parse-1.3.0/README.rst   2021-10-08 13:04:59.000000000 +0200
+++ new/calmjs.parse-1.3.1/README.rst   2023-10-29 01:08:25.000000000 +0200
@@ -5,12 +5,12 @@
 ECMAScript; a near feature complete fork of |slimit|_.  A CLI front-end
 for this package is shipped separately as |crimp|_.
 
-.. image:: 
https://github.com/calmjs/calmjs.parse/actions/workflows/build.yml/badge.svg?branch=1.3.0
-    :target: 
https://github.com/calmjs/calmjs.parse/actions/workflows/build.yml?query=branch:1.3.0
-.. image:: 
https://ci.appveyor.com/api/projects/status/5dj8dnu9gmj02msu/branch/1.3.0?svg=true
-    :target: 
https://ci.appveyor.com/project/metatoaster/calmjs-parse/branch/1.3.0
-.. image:: 
https://coveralls.io/repos/github/calmjs/calmjs.parse/badge.svg?branch=1.3.0
-    :target: https://coveralls.io/github/calmjs/calmjs.parse?branch=1.3.0
+.. image:: 
https://github.com/calmjs/calmjs.parse/actions/workflows/build.yml/badge.svg?branch=1.3.1
+    :target: 
https://github.com/calmjs/calmjs.parse/actions/workflows/build.yml?query=branch:1.3.1
+.. image:: 
https://ci.appveyor.com/api/projects/status/5dj8dnu9gmj02msu/branch/1.3.1?svg=true
+    :target: 
https://ci.appveyor.com/project/metatoaster/calmjs-parse/branch/1.3.1
+.. image:: 
https://coveralls.io/repos/github/calmjs/calmjs.parse/badge.svg?branch=1.3.1
+    :target: https://coveralls.io/github/calmjs/calmjs.parse?branch=1.3.1
 
 .. |calmjs.parse| replace:: ``calmjs.parse``
 .. |crimp| replace:: ``crimp``
@@ -78,9 +78,8 @@
 modules for its lexer.  The wheel distribution of |calmjs.parse| does
 not require this extra step as it contains these pre-generated modules
 for |ply| up to version 3.11 (the latest version available at the time
-of previous release), however the source tarball or if |ply| version
-that is installed lies outside of the supported versions, the following
-caveats will apply.
+of previous release), however the version of |ply| that is installed is
+beyond the supported version, the following caveats will apply.
 
 If a more recent release of |ply| becomes available and the environment
 upgrades to that version, those pre-generated modules may become
@@ -89,11 +88,18 @@
 step if a newer version of |calmjs.parse| is not available, or |ply| may
 be downgraded back to version 3.11 if possible.
 
+Alternatively, install a more recent version of |calmjs.parse| wheel
+that has the most complete set of pre-generated modules built.
+
 Once the package is installed, the installation may be `tested`_ or be
 `used directly`_.
 
-Alternative installation methods (for developers, advanced users)
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Manual installation and packaging requirements
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+*This section is for developers and advanced users; contains important
+information for package maintainers for OS distributions (e.g. Linux)
+that will prevent less than ideal experiences for downstream users.*
 
 Development is still ongoing with |calmjs.parse|, for the latest
 features and bug fixes, the development version may be installed through
@@ -101,14 +107,58 @@
 
 .. code:: console
 
-    $ pip install 
git+https://github.com/calmjs/calmjs.parse.git#egg=calmjs.parse
+    $ pip install ply setuptools  # this MUST be done first; see below for 
reason
+    $ pip install -e 
git+https://github.com/calmjs/calmjs.parse.git#egg=calmjs.parse
+
+Note that all dependencies MUST be pre-installed ``setup.py build`` step
+to run, otherwise the build step required to create the pre-generated
+modules will result in failure.
+
+If |ply| isn't installed:
+
+.. code:: console
+
+    $ python -m pip install -e .
+    ...
+    running egg_info
+    ...
+    WARNING: cannot find distribution for 'ply'; using default value,
+    assuming 'ply==3.11' for pre-generated modules
+    ERROR: cannot find pre-generated modules for the assumed 'ply'
+    version from above and/or cannot `import ply` to build generated
+    modules, aborting build; please either ensure that the source
+    archive containing the pre-generate modules is being used, or that
+    the python package 'ply' is installed and available for import
+    before attempting to use the setup.py to build this package; please
+    refer to the top level README for further details
+
+If ``setuptools`` isn't installed:
+
+.. code:: console
+
+    $ python -m pip install -e .
+    ...
+    running egg_info
+    ...
+    Traceback (most recent call last):
+      ...
+    ModuleNotFoundError: No module named 'pkg_resources'
 
-Alternatively, the git repository can be cloned directly and execute
+Naturally, the git repository can be cloned directly and execute
 ``python setup.py develop`` while inside the root of the source
-directory.
+directory; again, both |ply| AND ``setuptools`` MUST already have be
+available for import.
 
-A manual optimization step may need to be performed for platforms and
-systems that do not have utf8 as their default encoding.
+As the git repository does NOT contain any pre-generated modules or
+code, the above message is likely to be seen by developers or distro
+maintainers who are on their first try at interacting with this
+software.  However, the zip archives released on PyPI starting from
+version 1.3.0 do contain these modules fully pre-generated, thus they
+may be used as part of a standard installation step, i.e. without
+requiring |ply| be available for import before usage of the ``setup.py``
+for any purpose.  While the same warning message about |ply| being
+missing may be shown, the pre-generated modules will allow the build
+step to proceed as normal.
 
 Manual optimization
 ~~~~~~~~~~~~~~~~~~~
@@ -516,11 +566,13 @@
     '4 + 4'
 
 To assist with a more generalized usage, the ``ast_to_dict`` provides an
-additional ``fold_ops`` argument.  When set to ``True``, various
-operators will be folded to assist with computing certain constants into
-a single computed value.  This is often useful for ensuring concatenated
-strings are merged, and normalizing short-hand definition of boolean
-values via ``!0`` or ``!1``, among other commonly seen expressions.
+additional ``fold_ops`` argument.  When set to ``True``, operator
+folding will be enabled on supported types; for example, constants will
+be attempted to be folded into a single value as per how operators are
+handled in the ECMAScript specification.  This is often useful for
+ensuring concatenated strings are merged, and normalizing short-hand
+definition of boolean values via ``!0`` or ``!1``, among other commonly
+seen expressions.
 
 .. code:: pycon
 
@@ -712,6 +764,7 @@
 Further details and example usage can be consulted from the various
 docstrings found within the module.
 
+
 Limitations
 -----------
 
@@ -733,6 +786,7 @@
 statement will also be discarded as that is the second token consumed
 by the production rule that produces a ``Conditional`` node.
 
+
 Troubleshooting
 ---------------
 
@@ -766,6 +820,59 @@
 Further details on this topic may be found in the `manual optimization`_
 section of this document.
 
+WARNING: There are unused tokens on import
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This indicates that the installation method or source for this package
+being imported isn't optimized.  A quick workaround is to follow the
+instructions at the `manual optimization`_ section of this document to
+ensure these messages are no longer generated (and if this warning
+happens every time the module is imported, it means the symbol tables
+are regenerated every time that happens and this extra computational
+overhead should be corrected through the generation of that optimization
+module).
+
+The optimization modules are included with the wheel release and the
+source release on PyPI, but it is not part of the source repository as
+generated code are never committed.  Should a binary release made by
+a third-party results in this warning upon import, their release should
+be corrected to include the optimization module.
+
+Moreover, there are safeguards in place that prevent this warning from
+being generated for releases made for releases from 1.3.1 onwards by
+a more heavy handed enforcement of this optimization step at build time,
+but persistent (or careless) actors may circumvent this during the build
+process, but official releases made through PyPI should include the
+required optimization for all supported |ply| versions (which are
+versions 3.6 to 3.11, inclusive).
+
+Alternatively, this issue may also occur via usage of ``pyinstaller``
+if the package metadata is not copied for |ply| in versions prior to
+``calmjs.parse-1.3.1`` and will always occur if the hidden imports are
+not declared for those optimization modules.  The following hook should
+may be used to ensure |calmjs.parse| functions correctly in the compiled
+binary:
+
+.. code:: python
+
+    from PyInstaller.utils.hooks import collect_data_files, copy_metadata
+    from calmjs.parse.utils import generate_tab_names
+
+    datas = []
+    datas.extend(collect_data_files("ply"))
+    datas.extend(copy_metadata("ply"))
+    datas.extend(collect_data_files("calmjs.parse"))
+    datas.extend(copy_metadata("calmjs.parse"))
+
+    hiddenimports = []
+    hiddenimports.extend(generate_tab_names('calmjs.parse.parsers.es5'))
+
+    # if running under Python 3 with ply-3.11, above is equivalent to
+    # hiddenimports = [
+    #     "calmjs.parse.parsers.lextab_es5_py3_ply3_11",
+    #     "calmjs.parse.parsers.yacctab_es5_py3_ply3_11",
+    # ]
+
 Slow performance
 ~~~~~~~~~~~~~~~~
 
@@ -779,6 +886,18 @@
 arguments with name collisions, and the new function will take in all
 of those arguments in one go.
 
+ERROR message about import error when trying to install
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+As noted in the error message, the |ply|_ and ``setuptools`` package
+must be installed before attempting to install build the package in the
+situation where the pre-generated modules are missing.  This situation
+may be caused by building directly using the source provided by the
+source code repository, or where there is no matching pre-generated
+module matching with the installed version of |ply|.  Please ensure that
+|ply| is installed and available first before installing from source if
+this error message is sighted.
+
 
 Contribute
 ----------
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/calmjs.parse-1.3.0/appveyor.yml 
new/calmjs.parse-1.3.1/appveyor.yml
--- old/calmjs.parse-1.3.0/appveyor.yml 2021-10-08 13:04:59.000000000 +0200
+++ new/calmjs.parse-1.3.1/appveyor.yml 2023-10-29 01:08:25.000000000 +0200
@@ -1,24 +1,21 @@
+image: Visual Studio 2022
+
 environment:
   matrix:
     - PYTHON: "C:\\Python27"
-      nodejs_version: "4.6"
     - PYTHON: "C:\\Python33"
-      nodejs_version: "4.6"
     - PYTHON: "C:\\Python34"
-      nodejs_version: "6.9"
     - PYTHON: "C:\\Python35"
-      nodejs_version: "6.9"
     - PYTHON: "C:\\Python36"
-      nodejs_version: "8"
     - PYTHON: "C:\\Python37"
-      nodejs_version: "10"
     - PYTHON: "C:\\Python38"
-      nodejs_version: "10"
+    - PYTHON: "C:\\Python39-x64"
+    - PYTHON: "C:\\Python310-x64"
+    - PYTHON: "C:\\Python311-x64"
 
 install:
   - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
-  - ps: Install-Product node $env:nodejs_version
-  - "%PYTHON%\\python.exe -m pip install coverage"
+  - "%PYTHON%\\python.exe -m pip install setuptools coverage ply"
   - "%PYTHON%\\python.exe setup.py install"
 
 test_script:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/calmjs.parse-1.3.0/setup.py 
new/calmjs.parse-1.3.1/setup.py
--- old/calmjs.parse-1.3.0/setup.py     2021-10-08 13:04:59.000000000 +0200
+++ new/calmjs.parse-1.3.1/setup.py     2023-10-29 01:08:25.000000000 +0200
@@ -1,19 +1,27 @@
-import atexit
+import os
 import sys
 from setuptools import setup, find_packages
-from setuptools.command.install import install
+from setuptools.command.build_py import build_py
 from subprocess import call
 
 
-class InstallHook(install):
-    """For hooking the optimizer when setup exits"""
+class BuildHook(build_py):
+    """Forcing the optimizer to run before the build step"""
     def __init__(self, *a, **kw):
-        install.__init__(self, *a, **kw)
-        atexit.register(
-            call, [sys.executable, '-m', 'calmjs.parse.parsers.optimize'])
+        # must use clone of this, otherwise Python on Windows gets sad.
+        env = os.environ.copy()
+        env['PYTHONPATH'] = 'src'
+        code = call([
+            sys.executable, '-m', 'calmjs.parse.parsers.optimize', '--build'
+        ], env=env)
+        if code:
+            sys.exit(1)
+        build_py.__init__(self, *a, **kw)
 
 
-version = '1.3.0'
+# Attributes
+
+version = '1.3.1'
 
 classifiers = """
 Development Status :: 5 - Production/Stable
@@ -28,6 +36,10 @@
 Programming Language :: Python :: 3.6
 Programming Language :: Python :: 3.7
 Programming Language :: Python :: 3.8
+Programming Language :: Python :: 3.9
+Programming Language :: Python :: 3.10
+Programming Language :: Python :: 3.11
+Programming Language :: Python :: 3.12
 """.strip().splitlines()
 
 long_description = (
@@ -55,7 +67,7 @@
     include_package_data=True,
     zip_safe=False,
     cmdclass={
-        'install': InstallHook,
+        'build_py': BuildHook,
     },
     install_requires=[
         'setuptools',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/calmjs.parse-1.3.0/src/calmjs/parse/__init__.py 
new/calmjs.parse-1.3.1/src/calmjs/parse/__init__.py
--- old/calmjs.parse-1.3.0/src/calmjs/parse/__init__.py 2021-10-08 
13:04:59.000000000 +0200
+++ new/calmjs.parse-1.3.1/src/calmjs/parse/__init__.py 2023-10-29 
01:08:25.000000000 +0200
@@ -3,7 +3,14 @@
 Quick access helper functions
 """
 
-from calmjs.parse.factory import ParserUnparserFactory
+try:
+    from calmjs.parse.factory import ParserUnparserFactory
+except ImportError as e:  # pragma: no cover
+    exc = e
 
+    def import_error(*a, **kw):
+        raise exc
 
-es5 = ParserUnparserFactory('es5', 'pretty_print', 'minify_print')
+    es5 = import_error
+else:
+    es5 = ParserUnparserFactory('es5', 'pretty_print', 'minify_print')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/calmjs.parse-1.3.0/src/calmjs/parse/parsers/es5.py 
new/calmjs.parse-1.3.1/src/calmjs/parse/parsers/es5.py
--- old/calmjs.parse-1.3.0/src/calmjs/parse/parsers/es5.py      2021-10-08 
13:04:59.000000000 +0200
+++ new/calmjs.parse-1.3.1/src/calmjs/parse/parsers/es5.py      2023-10-29 
01:08:25.000000000 +0200
@@ -42,8 +42,10 @@
 
 asttypes = AstTypesFactory(pretty_print, ReprWalker())
 
-# The default values for the `Parser` constructor, passed on to ply; they must
-# be strings
+# These default values for the `Parser` constructor, passed on to ply;
+# they must be strings; these values are for reference only as
+# modifications to this value will not change what's been set up as
+# the Parser's default.
 lextab, yacctab = generate_tab_names(__name__)
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/calmjs.parse-1.3.0/src/calmjs/parse/parsers/optimize.py 
new/calmjs.parse-1.3.1/src/calmjs/parse/parsers/optimize.py
--- old/calmjs.parse-1.3.0/src/calmjs/parse/parsers/optimize.py 2021-10-08 
13:04:59.000000000 +0200
+++ new/calmjs.parse-1.3.1/src/calmjs/parse/parsers/optimize.py 2023-10-29 
01:08:25.000000000 +0200
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 """
-Helpers that will forcibly regenerate the tab files.
+Helpers for maintenance/generation of the lextab/yacctab modules.
 
 The original goal of this was to force the creation of tab files using
 the utf8 codec to workaround issues with the ply package, for systems
@@ -8,19 +8,36 @@
 """
 
 import codecs
+import os
 import sys
 from functools import partial
 from os import unlink
 from os.path import exists
-from ply import lex
 from importlib import import_module
+from calmjs.parse.utils import generate_tab_names
+from calmjs.parse.utils import ply_dist
 
-# have to do this for every parser modules
-from calmjs.parse.parsers import es5
+_ASSUME_PLY_VERSION = '3.11'
+_ASSUME_ENVVAR = 'CALMJS_PARSE_ASSUME_PLY_VERSION'
 
 
-def purge_tabs(module):
+def validate_imports(*imports):
     paths = []
+    missing = []
+    for name in imports:
+        try:
+            import_module(name)
+        except ImportError:
+            missing.append(name)
+        else:
+            paths.append(sys.modules.pop(name).__file__)
+    return paths, missing
+
+
+def find_tab_paths(module):
+    # return a list of lextab/yacctab module paths and a list of missing
+    # import names.
+    names = []
     for entry in ('lextab', 'yacctab'):
         # we assume the specified entries are defined as such
         name = getattr(module, entry)
@@ -31,14 +48,12 @@
                 'provided module `%s` does not export expected tab values ' %
                 module.__name__
             )
-        try:
-            import_module(name)
-        except ImportError:
-            # don't need to do anything
-            pass
-        else:
-            paths.append(sys.modules.pop(name).__file__)
+        names.append(name)
+    return validate_imports(*names)
+
 
+def purge_tabs(module):
+    paths, _ = find_tab_paths(module)
     unlink_modules(verify_paths(paths))
 
 
@@ -46,10 +61,15 @@
     for path in paths:
         if exists(path):
             yield path
+        # locate any adjacent .py[co]? files based on module path
+        # returned; mostly a problem with Python 2
         if path[-4:] in ('.pyc', '.pyo'):
-            # find the .py file, too.
             if exists(path[:-1]):
                 yield path[:-1]
+        else:
+            for c in 'co':
+                if exists(path + c):
+                    yield path + c
 
 
 def unlink_modules(paths):
@@ -64,13 +84,118 @@
     module.Parser()
 
 
-def reoptimize_all(monkey_patch=False):
+def _assume_ply_version():
+    version = os.environ.get(_ASSUME_ENVVAR, _ASSUME_PLY_VERSION)
+    if ply_dist is None:
+        if _ASSUME_ENVVAR in os.environ:
+            source = "using environment variable %r" % _ASSUME_ENVVAR
+        else:
+            # allow bypassing of setuptools as ply provides this
+            # attribute
+            try:
+                import ply
+                version = ply.__version__
+                source = "using value provided by ply"
+            except ImportError:  # pragma: no cover
+                ply = None
+                source = "using default value"
+
+        sys.stderr.write(
+            u"WARNING: cannot find distribution for 'ply'; "
+            "%s, assuming 'ply==%s' for pre-generated modules\n" % (
+                source, version))
+    return version
+
+
+def optimize_build(module_name, assume_ply_version=True):
+    """
+    optimize build helper for first build
+
+    assume_ply_version
+        This flag denotes whether or not to assume a ply version should
+        ply be NOT installed; this will either assume ply to be whatever
+        value assigned to _ASSUME_PLY_VERSION (i.e. 3.11), or read from
+        the environment variable `CALMJS_PARSE_ASSUME_PLY_VERSION`.
+
+        The goal is to allow the build to proceed if the pre-generated
+        files are already present, before the dependency resolution at
+        the installation time actually kicks in to install ply.
+
+        Default: True
+    """
+
+    kws = {}
+    if assume_ply_version:
+        kws['_version'] = _assume_ply_version()
+
+    lextab, yacctab = generate_tab_names(module_name, **kws)
+    paths, missing = validate_imports(lextab, yacctab)
+    if missing:
+        # only import, purge and regenerate if any are missing.
+        unlink_modules(verify_paths(paths))
+        module = import_module(module_name)
+        # use whatever assumed version or otherwise as set up by
+        # the local generation function.
+        module.Parser(lextab=lextab, yacctab=yacctab)
+
+
+def reoptimize_all(monkey_patch=False, first_build=False):
+    """
+    The main optimize method for maintainence of the generated tab
+    modules required by ply
+
+    Arguments:
+
+    monkey_patch
+        patches the default open function in ply.lex to use utf8
+
+        default: False
+
+    first_build
+        flag for switching between reoptimize/optimize_build method;
+        setting the flag to True specifies the latter.
+
+        default: False
+    """
+
     if monkey_patch:
-        lex.open = partial(codecs.open, encoding='utf8')
-    modules = (es5,)
-    for module in modules:
-        reoptimize(module)
+        try:
+            from ply import lex
+        except ImportError:  # pragma: no cover
+            pass  # fail later; only fail if import ply is truly needed
+        else:
+            lex.open = partial(codecs.open, encoding='utf8')
+
+    modules = ('.es5',)
+    try:
+        for name in modules:
+            if first_build:
+                # A consideration for modifying this flag to simply
+                # check for a marker file to denote none of this being
+                # needed (i.e. this tarball was fully prepared), but it
+                # will not solve the issue where the distro packager
+                # already got an even more recent version of ply
+                # installed (as unlikely as that is) and that build step
+                # then is completely skipped.
+                optimize_build('calmjs.parse.parsers' + name)
+            else:
+                module = import_module(name, 'calmjs.parse.parsers')
+                reoptimize(module)
+    except ImportError as e:
+        if not first_build or 'ply' not in str(e):
+            raise
+        sys.stderr.write(
+            u"ERROR: cannot find pre-generated modules for the assumed 'ply' "
+            "version from above and/or cannot `import ply` to build generated "
+            "modules, aborting build; please either ensure that the source "
+            "archive containing the pre-generate modules is being used, or "
+            "that the python package 'ply' is installed and available for "
+            "import before attempting to use the setup.py to build this "
+            "package; please refer to the top level README for further "
+            "details\n"
+        )
+        sys.exit(1)
 
 
 if __name__ == '__main__':  # pragma: no cover
-    reoptimize_all(True)
+    reoptimize_all(True, '--build' in sys.argv)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/calmjs.parse-1.3.0/src/calmjs/parse/tests/__init__.py 
new/calmjs.parse-1.3.1/src/calmjs/parse/tests/__init__.py
--- old/calmjs.parse-1.3.0/src/calmjs/parse/tests/__init__.py   2021-10-08 
13:04:59.000000000 +0200
+++ new/calmjs.parse-1.3.1/src/calmjs/parse/tests/__init__.py   2023-10-29 
01:08:25.000000000 +0200
@@ -65,18 +65,25 @@
         'calmjs.parse.tests', pattern='test_*.py',
         top_level_dir=dirname(__file__)
     )
-    test_suite.addTest(doctest.DocTestSuite(es5lexer, optionflags=optflags))
-    test_suite.addTest(doctest.DocTestSuite(walkers, optionflags=optflags))
-    test_suite.addTest(doctest.DocTestSuite(sourcemap, optionflags=optflags))
-    test_suite.addTest(doctest.DocTestCase(
-        # skipping all the error case tests which should all be in the
-        # troubleshooting section at the end; bump the index whenever
-        # more failure examples are added.
-        # also note that line number is unknown, as PKG_INFO has headers
-        # and also the counter is somehow inaccurate in this case.
-        doctest.DocTest(pkgdesc_tests[:-1], {
-            'open': open}, 'PKG_INFO', 'README.rst', None, pkgdesc),
-        optionflags=optflags,
-    ))
+    try:
+        test_suite.addTest(doctest.DocTestSuite(
+            es5lexer, optionflags=optflags))
+        test_suite.addTest(doctest.DocTestSuite(
+            walkers, optionflags=optflags))
+        test_suite.addTest(doctest.DocTestSuite(
+            sourcemap, optionflags=optflags))
+        test_suite.addTest(doctest.DocTestCase(
+            # skipping all the error case tests which should all be in the
+            # troubleshooting section at the end; bump the index whenever
+            # more failure examples are added.
+            # also note that line number is unknown, as PKG_INFO has headers
+            # and also the counter is somehow inaccurate in this case.
+            doctest.DocTest(pkgdesc_tests[:-1], {
+                'open': open}, 'PKG_INFO', 'README.rst', None, pkgdesc),
+            optionflags=optflags,
+        ))
+    except AttributeError:
+        # Assuming this is in Python>3.9 where the -OO flag was used...
+        pass
 
     return test_suite
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/calmjs.parse-1.3.0/src/calmjs/parse/tests/test_parsers_optimize.py 
new/calmjs.parse-1.3.1/src/calmjs/parse/tests/test_parsers_optimize.py
--- old/calmjs.parse-1.3.0/src/calmjs/parse/tests/test_parsers_optimize.py      
2021-10-08 13:04:59.000000000 +0200
+++ new/calmjs.parse-1.3.1/src/calmjs/parse/tests/test_parsers_optimize.py      
2023-10-29 01:08:25.000000000 +0200
@@ -5,12 +5,14 @@
 import os
 import sys
 
+from io import StringIO
 from shutil import rmtree
 from tempfile import mkdtemp
 from types import ModuleType
 from ply import lex
 from calmjs.parse.parsers import optimize
 from calmjs.parse.parsers import es5
+from calmjs.parse.utils import ply_dist
 
 
 class OptimizeTestCase(unittest.TestCase):
@@ -22,16 +24,26 @@
     def tearDown(self):
         optimize.unlink = os.unlink
         optimize.import_module = importlib.import_module
+        optimize.ply_dist = ply_dist
         # undo whatever monkey patch that may have happened
         lex.open = open
 
+    def break_ply(self):
+        import ply
+
+        def cleanup():
+            sys.modules['ply'] = ply
+
+        self.addCleanup(cleanup)
+        sys.modules['ply'] = None
+
     def test_verify_paths(self):
         tempdir = mkdtemp()
         self.addCleanup(rmtree, tempdir)
 
         # create fake module files
         modules = [os.path.join(tempdir, name) for name in (
-            'foo.pyc', 'bar.pyc', 'foo.py')]
+            'foo.pyc', 'bar.py', 'bar.pyc', 'foo.py')]
 
         for module in modules:
             with open(module, 'w'):
@@ -42,11 +54,28 @@
             sorted(optimize.verify_paths(modules[:2])),
         )
 
-    def test_unlink_modules(self):
+    def test_find_tab_paths(self):
+        fake_es5 = ModuleType('fake_es5')
+        fake_es5.lextab = 'some_lextab'
+        fake_es5.yacctab = 'some_yacctab'
+        paths, missing = optimize.find_tab_paths(fake_es5)
+        self.assertEqual([], paths)
+        self.assertEqual(['some_lextab', 'some_yacctab'], missing)
+
         # ensure the parser exists
         es5.Parser()
         # should have created the optimized version of the file, if not
         # already exists
+        answers = [
+            sys.modules[es5.lextab].__file__,
+            sys.modules[es5.yacctab].__file__,
+        ]
+        paths, missing = optimize.find_tab_paths(es5)
+        self.assertEqual(paths, answers)
+        self.assertEqual([], missing)
+
+    def test_unlink_modules(self):
+        es5.Parser()
         p = sys.modules[es5.yacctab].__file__
         self.assertTrue(os.path.exists(p))
         # unlink has been patched out
@@ -87,3 +116,166 @@
     def test_reoptimize_monkey_patched(self):
         optimize.reoptimize_all(True)
         self.assertIsNot(lex.open, open)
+        self.assertNotEqual(len(self.purged), 0)
+
+    def test_optimize_build(self):
+        called = []
+
+        def sentinel(*a, **kw):
+            called.append(True)
+
+        fake_es5 = ModuleType('fake_namespace.fake_es5')
+        fake_es5.Parser = sentinel
+
+        # inject fake namespace and module
+        sys.modules['fake_namespace'] = ModuleType('fake_namespace')
+        self.addCleanup(sys.modules.pop, 'fake_namespace')
+        sys.modules['fake_namespace.fake_es5'] = fake_es5
+        self.addCleanup(sys.modules.pop, 'fake_namespace.fake_es5')
+
+        optimize.optimize_build('fake_namespace.fake_es5')
+        self.assertEqual(len(self.purged), 0)
+        self.assertTrue(called)
+
+    def test_optimize_first_build(self):
+        optimize.reoptimize_all(True, first_build=True)
+        # shouldn't have purged any modules
+        self.assertEqual(len(self.purged), 0)
+
+    def test_optimize_first_build_valid_with_broken_ply(self):
+        self.break_ply()
+        optimize.reoptimize_all(True, first_build=True)
+        # shouldn't have purged any modules
+        self.assertEqual(len(self.purged), 0)
+
+    def test_assume_ply_version_default_ply(self):
+        # only applicable if no ply_dist found
+        optimize.ply_dist = None
+        stderr = sys.stderr
+        self.addCleanup(setattr, sys, 'stderr', stderr)
+
+        # where ply is actually available; and since the real thing is
+        # expected to be present and usable, intersperse that real value
+        # into the expected string.
+        import ply
+        sys.stderr = StringIO()
+        optimize._assume_ply_version()
+        self.assertTrue(sys.stderr.getvalue().startswith(
+            "WARNING: cannot find distribution for 'ply'; using value "
+            "provided by ply, assuming 'ply==%s' for pre-generated modules" % (
+                ply.__version__
+            )))
+
+    def test_assume_ply_version_override_ply(self):
+        # can still override if ply is actually available
+        optimize.ply_dist = None
+        stderr = sys.stderr
+        self.addCleanup(setattr, sys, 'stderr', stderr)
+
+        self.addCleanup(os.environ.pop, optimize._ASSUME_ENVVAR, None)
+        sys.stderr = StringIO()
+        os.environ[optimize._ASSUME_ENVVAR] = '0.9999'  # should never exist
+        optimize._assume_ply_version()
+        self.assertTrue(sys.stderr.getvalue().startswith(
+            "WARNING: cannot find distribution for 'ply'; using environment "
+            "variable 'CALMJS_PARSE_ASSUME_PLY_VERSION', "
+            "assuming 'ply==0.9999' for pre-generated modules"))
+
+    def test_assume_ply_version_no_ply(self):
+        # default when ply is fully broken.
+        optimize.ply_dist = None
+        stderr = sys.stderr
+        self.addCleanup(setattr, sys, 'stderr', stderr)
+
+        self.break_ply()
+        sys.stderr = StringIO()
+        optimize._assume_ply_version()
+        self.assertTrue(sys.stderr.getvalue().startswith(
+            "WARNING: cannot find distribution for 'ply'; using default "
+            "value, assuming 'ply==3.11' for pre-generated modules"))
+
+    def test_optimize_first_build_valid_with_broken_ply_error(self):
+        def fail_import(*a, **kw):
+            raise ImportError('no module named ply')
+
+        optimize.import_module = fail_import
+
+        with self.assertRaises(ImportError):
+            optimize.reoptimize_all()
+
+        stderr = sys.stderr
+
+        def cleanup():
+            sys.stderr = stderr
+
+        self.addCleanup(cleanup)
+
+        sys.stderr = StringIO()
+        with self.assertRaises(SystemExit):
+            optimize.reoptimize_all(first_build=True)
+
+        self.assertTrue(sys.stderr.getvalue().startswith(
+            "ERROR: cannot find pre-generated modules for the assumed 'ply' "
+            "version"))
+
+    def test_optimize_first_build_assume_broken_ply_error(self):
+        optimize.ply_dist = None
+
+        self.break_ply()
+
+        def fail_import(*a, **kw):
+            raise ImportError('no module named ply')
+
+        optimize.import_module = fail_import
+
+        with self.assertRaises(ImportError):
+            optimize.reoptimize_all()
+
+        stderr = sys.stderr
+
+        def cleanup():
+            sys.stderr = stderr
+
+        self.addCleanup(cleanup)
+
+        sys.stderr = StringIO()
+        with self.assertRaises(SystemExit):
+            optimize.reoptimize_all(first_build=True)
+
+        lines = sys.stderr.getvalue().splitlines()
+        self.assertTrue(lines[0].startswith(
+            "WARNING: cannot find distribution for 'ply'; using default value"
+            ))
+        self.assertTrue(lines[1].startswith(
+            "ERROR: cannot find pre-generated modules for the assumed 'ply' "
+            "version"))
+
+    def test_optimize_build_assume_broken_ply_but_available(self):
+        optimize.ply_dist = None
+        called = []
+
+        def sentinel(*a, **kw):
+            called.append(True)
+
+        fake_es5 = ModuleType('fake_namespace.fake_es5')
+        fake_es5.Parser = sentinel
+
+        # inject fake namespace and module
+        sys.modules['fake_namespace'] = ModuleType('fake_namespace')
+        self.addCleanup(sys.modules.pop, 'fake_namespace')
+        sys.modules['fake_namespace.fake_es5'] = fake_es5
+        self.addCleanup(sys.modules.pop, 'fake_namespace.fake_es5')
+        stderr = sys.stderr
+        self.addCleanup(setattr, sys, 'stderr', stderr)
+        sys.stderr = StringIO()
+
+        optimize.optimize_build('fake_namespace.fake_es5')
+        self.assertEqual(len(self.purged), 0)
+        self.assertTrue(called)
+        # this parser will not actually error as it does nothing; and
+        # so not actually care whether ply actually available here or
+        # not.
+        self.assertTrue(sys.stderr.getvalue().startswith(
+            "WARNING: cannot find distribution for 'ply'; "
+            ))
+        self.assertNotIn('ERROR', sys.stderr.getvalue())
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/calmjs.parse-1.3.0/src/calmjs/parse/tests/test_walkers.py 
new/calmjs.parse-1.3.1/src/calmjs/parse/tests/test_walkers.py
--- old/calmjs.parse-1.3.0/src/calmjs/parse/tests/test_walkers.py       
2021-10-08 13:04:59.000000000 +0200
+++ new/calmjs.parse-1.3.1/src/calmjs/parse/tests/test_walkers.py       
2023-10-29 01:08:25.000000000 +0200
@@ -218,3 +218,16 @@
 
             "]>"
         )
+
+    def test_walker_skip(self):
+        t = es5('''
+        a = 1;
+        b = '2';
+        ''')
+        self.assertEqual(str(walker.extract(
+            t, lambda n: isinstance(n, asttypes.Assign), skip=1)), "b = '2'")
+
+        with self.assertRaises(TypeError) as e:
+            walker.extract(t, lambda n: isinstance(n, asttypes.Assign), skip=2)
+
+        self.assertEqual(str(e.exception), 'no match found')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/calmjs.parse-1.3.0/src/calmjs/parse/unparsers/extractor.py 
new/calmjs.parse-1.3.1/src/calmjs/parse/unparsers/extractor.py
--- old/calmjs.parse-1.3.0/src/calmjs/parse/unparsers/extractor.py      
2021-10-08 13:04:59.000000000 +0200
+++ new/calmjs.parse-1.3.1/src/calmjs/parse/unparsers/extractor.py      
2023-10-29 01:08:25.000000000 +0200
@@ -76,7 +76,6 @@
 # See ECMA-262 5.1 Edition, Section 9
 # Note that this parser does not provide an undefined token or global
 # binding, it's currently not handled.
-# The hint argument is the PreferredType
 
 def value_to_str(value):
     """
@@ -111,7 +110,7 @@
 
 
 def to_primitive(fragment, hint):
-    # TODO implement the correct return value for either Object/Array
+    # The hint argument is the PreferredType
     if (issubclass(fragment.folded_type, Array) or
             issubclass(fragment.folded_type, Object)):
         value = value_to_str(fragment.value)
@@ -794,7 +793,6 @@
     """
 
     def __call__(self, walk, dispatcher, node):
-        # TODO this is getting similar with AsDict
         misc_chunks = defaultdict(list)
         nodes = iter(node)
         for target_node in nodes:
@@ -1049,7 +1047,6 @@
         ),),
     ),
     'FuncDecl': (
-        # TODO DeclareAsFunc?
         GroupAsAssignment((
             Attr(Declare('identifier')),
             PushScope,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/calmjs.parse-1.3.0/src/calmjs/parse/utils.py 
new/calmjs.parse-1.3.1/src/calmjs/parse/utils.py
--- old/calmjs.parse-1.3.0/src/calmjs/parse/utils.py    2021-10-08 
13:04:59.000000000 +0200
+++ new/calmjs.parse-1.3.1/src/calmjs/parse/utils.py    2023-10-29 
01:08:25.000000000 +0200
@@ -13,6 +13,14 @@
     from pkg_resources import working_set
     from pkg_resources import Requirement
     ply_dist = working_set.find(Requirement.parse('ply'))
+    # note that for **extremely** ancient versions of setuptools, e.g.
+    # setuptools<0.6c11, or some very non-standard environment that does
+    # not include the required metadata (e.g. pyinstaller without the
+    # required metadata), will require the following workaround...
+    if ply_dist is None:  # pragma: no cover
+        from pkg_resources import Distribution
+        import ply
+        ply_dist = Distribution(project_name='ply', version=ply.__version__)
 except ImportError:  # pragma: no cover
     ply_dist = None
 
@@ -34,7 +42,7 @@
         return repr(s)
 
 
-def generate_tab_names(name):
+def generate_tab_names(name, _version='unknown'):
     """
     Return the names to lextab and yacctab modules for the given module
     name.  Typical usage should be like so::
@@ -44,8 +52,8 @@
 
     package_name, module_name = name.rsplit('.', 1)
 
-    version = ply_dist.version.replace(
-        '.', '_') if ply_dist is not None else 'unknown'
+    version = (ply_dist.version if ply_dist is not None else _version).replace(
+        '.', '_')
     data = (package_name, module_name, py_major, version)
     lextab = '%s.lextab_%s_py%d_ply%s' % data
     yacctab = '%s.yacctab_%s_py%d_ply%s' % data

Reply via email to