From a187f5132b5f5b3415556b06c90cc352ab5cafbf Mon Sep 17 00:00:00 2001 From: Quintijn Date: Fri, 16 Aug 2024 14:17:27 +0200 Subject: [PATCH 01/24] fix the path for natlinkcore in documentation conf.py --- documentation/conf.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/documentation/conf.py b/documentation/conf.py index f334caa..399d9fc 100644 --- a/documentation/conf.py +++ b/documentation/conf.py @@ -15,9 +15,10 @@ import os import sys -src_directory = os.path.join(os.path.abspath('..'), 'src', 'natlinkcore') +src_directory = os.path.join(os.path.abspath('..'), 'src') sys.path.insert(0, src_directory) -# print(f'src_directory: {src_directory}') +# print(sys.path) +print('='*20) import natlinkcore # -- Project information ----------------------------------------------------- @@ -60,6 +61,7 @@ def __getattr__(cls, name): 'win32process', 'winreg', 'winxpgui', + 'natlink' } for module_name in mock_modules: From 60e65865e2f80540696661b0a7322c99d766b6e9 Mon Sep 17 00:00:00 2001 From: Quintijn Date: Sun, 18 Aug 2024 13:14:26 +0200 Subject: [PATCH 02/24] trying to get sphinx working, no result yet --- documentation/conf.py | 7 +++++-- documentation/config.rst | 2 +- documentation/documentation.rst | 16 +++++++++++----- documentation/loader.rst | 2 +- documentation/modules.rst | 8 +++++--- documentation/natlinkutils.rst | 12 ++++++++++++ 6 files changed, 35 insertions(+), 12 deletions(-) create mode 100644 documentation/natlinkutils.rst diff --git a/documentation/conf.py b/documentation/conf.py index 399d9fc..7f27414 100644 --- a/documentation/conf.py +++ b/documentation/conf.py @@ -61,9 +61,12 @@ def __getattr__(cls, name): 'win32process', 'winreg', 'winxpgui', - 'natlink' + 'natlink', + 'pydebugstring', + 'debugpy', + 'vocola2', + 'unimacro' } - for module_name in mock_modules: sys.modules[module_name] = Mock() diff --git a/documentation/config.rst b/documentation/config.rst index c05f7d5..0a102f7 100644 --- a/documentation/config.rst +++ b/documentation/config.rst @@ -5,5 +5,5 @@ config ====== The :code:`config` module reads the Natlink config file (`natlink.ini`), as used by other modules, especially the `loader`. -.. automodule:: natlink.config +.. automodule:: natlinkcore.config :members: diff --git a/documentation/documentation.rst b/documentation/documentation.rst index 9bfc4fb..8b71401 100644 --- a/documentation/documentation.rst +++ b/documentation/documentation.rst @@ -24,19 +24,25 @@ Then run the following command on Windows to build the documentation: .. code:: shell - $ make.bat html + $ make html -Or use the Makefile on other systems: -.. code:: shell - - $ make html If there were no errors during the build process, open the *_build/html/index.html* file in a web browser. Make changes, rebuild the documentation and reload the doc page(s) in your browser as you go. +Python versions +-------------------------- +Note that Natlink is running on a 32 bits python version. + +It appears that Sphinx is not running on a 32 bits python version. So the documentation must be compiled on another (64bit) python version than you are running Natlink on! + + + + + .. Links. .. _Read the docs: https://readthedocs.org/ .. _Sphinx documentation engine: https://www.sphinx-doc.org/en/master/ diff --git a/documentation/loader.rst b/documentation/loader.rst index 3a9b184..aa9ad5f 100644 --- a/documentation/loader.rst +++ b/documentation/loader.rst @@ -9,5 +9,5 @@ modules according to the config file `natlink.ini`. Moreover, it also handles callbacks from Dragon/Natlink. -.. automodule:: natlink.loader +.. automodule:: natlinkcore.loader :members: diff --git a/documentation/modules.rst b/documentation/modules.rst index f880302..7608e77 100644 --- a/documentation/modules.rst +++ b/documentation/modules.rst @@ -1,12 +1,14 @@ Modules ======= -This section includes the available modules in the natlink project +This section includes the available modules in the natlinkcore repository + .. toctree:: :maxdepth: 2 + natlinkutils config loader natlinkstatus - - \ No newline at end of file + + diff --git a/documentation/natlinkutils.rst b/documentation/natlinkutils.rst new file mode 100644 index 0000000..0da1673 --- /dev/null +++ b/documentation/natlinkutils.rst @@ -0,0 +1,12 @@ + +.. _RefNatlinkutils: + +Natlinkutils +==================== + +The :code:`natlinkule` module contains the basic Grammar Classes and a few utility functions + +natlinkstatus module +--------------------- +.. automodule:: natlinkcore.natlinkutils + :members: \ No newline at end of file From 29e2959a03e6a453443f0d7d1b20d76fe93eca05 Mon Sep 17 00:00:00 2001 From: Quintijn Date: Sun, 18 Aug 2024 15:53:02 +0200 Subject: [PATCH 03/24] starting finally the documentation, including docstring formatting through rst. currently natlinkutils.py is more or less working --- documentation/conf.py | 5 +- documentation/config.rst | 1 + documentation/documentation.rst | 1 + documentation/errors Quintijn.txt | 440 +++++++++++++++++++++++++----- documentation/index.rst | 1 + documentation/loader.rst | 1 + documentation/modules.rst | 3 +- documentation/natlinkstatus.rst | 3 +- documentation/natlinkutils.rst | 7 +- documentation/project.rst | 1 + src/natlinkcore/natlinkutils.py | 100 ++++--- 11 files changed, 457 insertions(+), 106 deletions(-) diff --git a/documentation/conf.py b/documentation/conf.py index 7f27414..62c4e47 100644 --- a/documentation/conf.py +++ b/documentation/conf.py @@ -65,7 +65,10 @@ def __getattr__(cls, name): 'pydebugstring', 'debugpy', 'vocola2', - 'unimacro' + 'unimacro', + 'vocoladirectory', + 'unimacrodirectory', + 'unimacrogrammarsdirectory', } for module_name in mock_modules: sys.modules[module_name] = Mock() diff --git a/documentation/config.rst b/documentation/config.rst index 0a102f7..2a183bf 100644 --- a/documentation/config.rst +++ b/documentation/config.rst @@ -7,3 +7,4 @@ The :code:`config` module reads the Natlink config file (`natlink.ini`), as used .. automodule:: natlinkcore.config :members: + diff --git a/documentation/documentation.rst b/documentation/documentation.rst index 8b71401..8c750d8 100644 --- a/documentation/documentation.rst +++ b/documentation/documentation.rst @@ -49,3 +49,4 @@ It appears that Sphinx is not running on a 32 bits python version. So the docume .. _reStructuredText format: http://docutils.sourceforge.net/rst.html .. _restructuredText primer: http://docutils.sourceforge.net/docs/user/rst/quickstart.html .. _sphinx.ext.autodoc: https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html + diff --git a/documentation/errors Quintijn.txt b/documentation/errors Quintijn.txt index 654a028..dee991e 100644 --- a/documentation/errors Quintijn.txt +++ b/documentation/errors Quintijn.txt @@ -1,82 +1,394 @@ +Microsoft Windows [Version 10.0.19045.4780] +(c) Microsoft Corporation. Alle rechten voorbehouden. -Copyright (C) Microsoft Corporation. All rights reserved. +C:\Users\Gebruiker>cd \dt\natlinkcore\documentation -Try the new cross-platform PowerShell https://aka.ms/pscore6 +C:\DT\natlinkcore\documentation>cd \dt\natlinkcoredoug -PS C:\Users\Gebruiker> cd C:\dt\NatlinkcoreDoug\documentation\ -PS C:\dt\NatlinkcoreDoug\documentation> .\make html Running Sphinx v5.1.1 -natlinkcore, version: "5.3", release: "5.3.8" +C:\DT\natlinkcoreDoug>cd documentations +Het systeem kan het opgegeven pad niet vinden. + +C:\DT\natlinkcoreDoug>cd documentation + +C:\DT\natlinkcoreDoug\documentation>make html +Running Sphinx v7.4.7 +==================== +natlinkcore, version: "5.3", release: "5.3.13" +loading translations [en]... done +Converting `source_suffix = '.rst'` to `source_suffix = {'.rst': 'restructuredtext'}`. loading pickled environment... done building [mo]: targets for 0 po files that are out of date -building [html]: targets for 0 source files that are out of date -updating environment: 0 added, 3 changed, 0 removed -reading sources... [100%] natlinkstatus -WARNING: autodoc: failed to import module 'config' from module 'natlink'; the following exception was raised: -Traceback (most recent call last): - File "C:\Python310-32\lib\site-packages\sphinx\ext\autodoc\importer.py", line 58, in import_module - return importlib.import_module(modname) - File "C:\Python310-32\lib\importlib\__init__.py", line 126, in import_module - return _bootstrap._gcd_import(name[level:], package, level) - File "", line 1050, in _gcd_import - File "", line 1027, in _find_and_load - File "", line 1006, in _find_and_load_unlocked - File "", line 688, in _load_unlocked - File "", line 883, in exec_module - File "", line 241, in _call_with_frames_removed - File "C:\Program Files (x86)\Natlink\site-packages\natlink\__init__.py", line 90, in - _original_natconnect=natConnect -NameError: name 'natConnect' is not defined - -WARNING: autodoc: failed to import module 'loader' from module 'natlink'; the following exception was raised: -Traceback (most recent call last): - File "C:\Python310-32\lib\site-packages\sphinx\ext\autodoc\importer.py", line 58, in import_module - return importlib.import_module(modname) - File "C:\Python310-32\lib\importlib\__init__.py", line 126, in import_module - return _bootstrap._gcd_import(name[level:], package, level) - File "", line 1050, in _gcd_import - File "", line 1027, in _find_and_load - File "", line 1006, in _find_and_load_unlocked - File "", line 688, in _load_unlocked - File "", line 883, in exec_module - File "", line 241, in _call_with_frames_removed - File "C:\Program Files (x86)\Natlink\site-packages\natlink\__init__.py", line 90, in - _original_natconnect=natConnect -NameError: name 'natConnect' is not defined - -WARNING: autodoc: failed to import module 'natlinkstatus' from module 'natlinkcore'; the following exception was raised: -Traceback (most recent call last): - File "C:\Python310-32\lib\site-packages\sphinx\ext\autodoc\importer.py", line 58, in import_module - return importlib.import_module(modname) - File "C:\Python310-32\lib\importlib\__init__.py", line 126, in import_module - return _bootstrap._gcd_import(name[level:], package, level) - File "", line 1050, in _gcd_import - File "", line 1027, in _find_and_load - File "", line 1006, in _find_and_load_unlocked - File "", line 688, in _load_unlocked - File "", line 883, in exec_module - File "", line 241, in _call_with_frames_removed - File "C:\DT\NatlinkcoreDoug\src\natlinkcore\natlinkstatus.py", line 110, in - import natlink - File "C:\Program Files (x86)\Natlink\site-packages\natlink\__init__.py", line 90, in - _original_natconnect=natConnect -NameError: name 'natConnect' is not defined +writing output... +building [html]: targets for 2 source files that are out of date +updating environment: 0 added, 2 changed, 0 removed +from_config_parser: skip "vocola2" ("vocoladirectory"): is not a valid directory +from_config_parser: skip "unimacro" ("unimacrodirectory"): is not a valid directory +from_config_parser: skip "unimacro/unimacrogrammars" ("unimacrogrammarsdirectory"): + expanded to directory "unimacro\unimacrogrammars" is not a valid directory + +C:\DT\natlinkcoreDoug\documentation\modules.rst:7: ERROR: Error in "toctree" directive: +invalid option block. +.. toctree:: + :maxdepth: 2 + config + loader + natlinkstatus + natlinkutils looking for now-outdated files... none found pickling environment... done -checking consistency... C:\DT\NatlinkcoreDoug\documentation\config.rst: WARNING: document isn't included in any toctree -C:\DT\NatlinkcoreDoug\documentation\developers.rst: WARNING: document isn't included in any toctree -C:\DT\NatlinkcoreDoug\documentation\loader.rst: WARNING: document isn't included in any toctree -C:\DT\NatlinkcoreDoug\documentation\natlinkstatus.rst: WARNING: document isn't included in any toctree +checking consistency... C:\DT\natlinkcoreDoug\documentation\config.rst: WARNING: document isn't included in any toctree +C:\DT\natlinkcoreDoug\documentation\loader.rst: WARNING: document isn't included in any toctree +C:\DT\natlinkcoreDoug\documentation\natlinkstatus.rst: WARNING: document isn't included in any toctree done preparing documents... done +copying assets... +copying static files... done +copying extra files... done +copying assets: done writing output... [100%] natlinkstatus -generating indices... genindex done +generating indices... genindex py-modindex done +highlighting module code... [100%] natlinkcore.natlinkstatus +writing additional pages... search done +dumping search index in English (code: en)... done +dumping object inventory... done +build succeeded, 4 warnings. + +The HTML pages are in _build\html. + +C:\DT\natlinkcoreDoug\documentation>cd build +Het systeem kan het opgegeven pad niet vinden. + +C:\DT\natlinkcoreDoug\documentation>make html +Running Sphinx v7.4.7 +==================== +natlinkcore, version: "5.3", release: "5.3.13" +loading translations [en]... done +Converting `source_suffix = '.rst'` to `source_suffix = {'.rst': 'restructuredtext'}`. +loading pickled environment... done +building [mo]: targets for 0 po files that are out of date +writing output... +building [html]: targets for 1 source files that are out of date +updating environment: 0 added, 1 changed, 0 removed +reading sources... [100%] modules +C:\DT\natlinkcoreDoug\documentation\modules.rst:7: ERROR: Error in "toctree" directive: +invalid option block. + +.. toctree:: + :maxdepth: 2 + natlinkutils + config + loader + natlinkstatus +looking for now-outdated files... none found +pickling environment... done +checking consistency... C:\DT\natlinkcoreDoug\documentation\config.rst: WARNING: document isn't included in any toctree +C:\DT\natlinkcoreDoug\documentation\loader.rst: WARNING: document isn't included in any toctree +C:\DT\natlinkcoreDoug\documentation\natlinkstatus.rst: WARNING: document isn't included in any toctree +done +preparing documents... done +copying assets... +copying static files... done +copying extra files... done +copying assets: done +writing output... [100%] modules +generating indices... genindex py-modindex done +from_config_parser: skip "vocola2" ("vocoladirectory"): is not a valid directory +from_config_parser: skip "unimacro" ("unimacrodirectory"): is not a valid directory +from_config_parser: skip "unimacro/unimacrogrammars" ("unimacrogrammarsdirectory"): + expanded to directory "unimacro\unimacrogrammars" is not a valid directory + +writing additional pages... search done +dumping search index in English (code: en)... done +dumping object inventory... done +build succeeded, 4 warnings. + +The HTML pages are in _build\html. + +C:\DT\natlinkcoreDoug\documentation>make html +Running Sphinx v7.4.7 +==================== +natlinkcore, version: "5.3", release: "5.3.13" +loading translations [en]... done +Converting `source_suffix = '.rst'` to `source_suffix = {'.rst': 'restructuredtext'}`. +loading pickled environment... done +building [mo]: targets for 0 po files that are out of date +writing output... +building [html]: targets for 0 source files that are out of date +updating environment: 1 added, 0 changed, 0 removed +reading sources... [100%] natlinkutils +C:\DT\natlinkcoreDoug\src\natlinkcore\natlinkutils.py:docstring of natlinkcore.natlinkutils.GrammarBase:128: ERROR: Unexpected indentation. +C:\DT\natlinkcoreDoug\src\natlinkcore\natlinkutils.py:docstring of natlinkcore.natlinkutils.GrammarBase:130: ERROR: Unexpected indentation. +C:\DT\natlinkcoreDoug\src\natlinkcore\natlinkutils.py:docstring of natlinkcore.natlinkutils.GrammarBase:131: WARNING: Block quote ends without a blank line; unexpected unindent. +C:\DT\natlinkcoreDoug\src\natlinkcore\natlinkutils.py:docstring of natlinkcore.natlinkutils.SelectGramBase:14: ERROR: Unexpected indentation. +C:\DT\natlinkcoreDoug\src\natlinkcore\natlinkutils.py:docstring of natlinkcore.natlinkutils.SelectGramBase:80: WARNING: Definition list ends without a blank line; unexpected unindent. +looking for now-outdated files... none found +pickling environment... done +checking consistency... C:\DT\natlinkcoreDoug\documentation\config.rst: WARNING: document isn't included in any toctree +C:\DT\natlinkcoreDoug\documentation\loader.rst: WARNING: document isn't included in any toctree +C:\DT\natlinkcoreDoug\documentation\natlinkstatus.rst: WARNING: document isn't included in any toctree +C:\DT\natlinkcoreDoug\documentation\natlinkutils.rst: WARNING: document isn't included in any toctree +done +preparing documents... done +copying assets... +copying static files... done +copying extra files... done +copying assets: done +writing output... [100%] natlinkutils +generating indices... genindex py-modindex done +from_config_parser: skip "vocola2" ("vocoladirectory"):tatus + expanded to directory "" is not a valid directory +from_config_parser: skip "unimacro" ("unimacrodirectory"): + expanded to directory "" is not a valid directory +from_config_parser: skip "unimacro/unimacrogrammars" ("unimacrogrammarsdirectory"): + expanded to directory "MagicMock\mock.__getitem__()\2546067778640\unimacrogrammars" is not a valid directory +highlighting module code... [100%] natlinkcore.natlinkutils writing additional pages... search done +dumping search index in English (code: en)... done +dumping object inventory... done +build succeeded, 9 warnings. + +The HTML pages are in _build\html. + +C:\DT\natlinkcoreDoug\documentation>make html +Running Sphinx v7.4.7 +==================== +natlinkcore, version: "5.3", release: "5.3.13" +loading translations [en]... done +Converting `source_suffix = '.rst'` to `source_suffix = {'.rst': 'restructuredtext'}`. +loading pickled environment... done +building [mo]: targets for 0 po files that are out of date +writing output... +building [html]: targets for 1 source files that are out of date +updating environment: 0 added, 1 changed, 0 removed +reading sources... [100%] modules +C:\DT\natlinkcoreDoug\documentation\modules.rst:7: ERROR: Error in "toctree" directive: +invalid option block. + +.. toctree:: + :maxdepth: 2 + natlinkutils.rst + config.rst + loader.rst + natlinkstatus.rst +looking for now-outdated files... none found +pickling environment... done +checking consistency... C:\DT\natlinkcoreDoug\documentation\config.rst: WARNING: document isn't included in any toctree +C:\DT\natlinkcoreDoug\documentation\loader.rst: WARNING: document isn't included in any toctree +C:\DT\natlinkcoreDoug\documentation\natlinkstatus.rst: WARNING: document isn't included in any toctree +C:\DT\natlinkcoreDoug\documentation\natlinkutils.rst: WARNING: document isn't included in any toctree +done +preparing documents... done +copying assets... copying static files... done copying extra files... done +copying assets: done +writing output... [100%] modules +generating indices... genindex py-modindex done +from_config_parser: skip "vocola2" ("vocoladirectory"):tatus + expanded to directory "" is not a valid directory +from_config_parser: skip "unimacro" ("unimacrodirectory"): + expanded to directory "" is not a valid directory +from_config_parser: skip "unimacro/unimacrogrammars" ("unimacrogrammarsdirectory"): + expanded to directory "MagicMock\mock.__getitem__()\2389399509136\unimacrogrammars" is not a valid directory +highlighting module code... [100%] natlinkcore.natlinkutils +writing additional pages... search done dumping search index in English (code: en)... done dumping object inventory... done -build succeeded, 7 warnings. +build succeeded, 5 warnings. The HTML pages are in _build\html. -PS C:\dt\NatlinkcoreDoug\documentation> \ No newline at end of file + +C:\DT\natlinkcoreDoug\documentation>make html +Running Sphinx v7.4.7 +==================== +natlinkcore, version: "5.3", release: "5.3.13" +loading translations [en]... done +Converting `source_suffix = '.rst'` to `source_suffix = {'.rst': 'restructuredtext'}`. +loading pickled environment... done +building [mo]: targets for 0 po files that are out of date +writing output... +building [html]: targets for 1 source files that are out of date +updating environment: 0 added, 1 changed, 0 removed +reading sources... [100%] modules +C:\DT\natlinkcoreDoug\documentation\modules.rst:7: ERROR: Error in "toctree" directive: +invalid option block. + +.. toctree:: + :maxdepth: 2 + natlinkutils + config + loader + natlinkstatus +looking for now-outdated files... none found +pickling environment... done +checking consistency... C:\DT\natlinkcoreDoug\documentation\config.rst: WARNING: document isn't included in any toctree +C:\DT\natlinkcoreDoug\documentation\loader.rst: WARNING: document isn't included in any toctree +C:\DT\natlinkcoreDoug\documentation\natlinkstatus.rst: WARNING: document isn't included in any toctree +C:\DT\natlinkcoreDoug\documentation\natlinkutils.rst: WARNING: document isn't included in any toctree +done +preparing documents... done +copying assets... +copying static files... done +copying extra files... done +copying assets: done +writing output... [100%] modules +generating indices... genindex py-modindex done +from_config_parser: skip "vocola2" ("vocoladirectory"):tatus + expanded to directory "" is not a valid directory +from_config_parser: skip "unimacro" ("unimacrodirectory"): + expanded to directory "" is not a valid directory +from_config_parser: skip "unimacro/unimacrogrammars" ("unimacrogrammarsdirectory"): + expanded to directory "MagicMock\mock.__getitem__()\2383906996304\unimacrogrammars" is not a valid directory +highlighting module code... [100%] natlinkcore.natlinkutils +writing additional pages... search done +dumping search index in English (code: en)... done +dumping object inventory... done +build succeeded, 5 warnings. + +The HTML pages are in _build\html. + +C:\DT\natlinkcoreDoug\documentation>make html +Running Sphinx v7.4.7 +==================== +natlinkcore, version: "5.3", release: "5.3.13" +loading translations [en]... done +Converting `source_suffix = '.rst'` to `source_suffix = {'.rst': 'restructuredtext'}`. +loading pickled environment... done +building [mo]: targets for 0 po files that are out of date +writing output... +building [html]: targets for 0 source files that are out of date +updating environment: 0 added, 0 changed, 0 removed +reading sources... +looking for now-outdated files... none found +no targets are out of date. +build succeeded. + +The HTML pages are in _build\html. + +C:\DT\natlinkcoreDoug\documentation>make html +Running Sphinx v7.4.7 +==================== +natlinkcore, version: "5.3", release: "5.3.13" +loading translations [en]... done +Converting `source_suffix = '.rst'` to `source_suffix = {'.rst': 'restructuredtext'}`. +loading pickled environment... done +building [mo]: targets for 0 po files that are out of date +writing output... +building [html]: targets for 1 source files that are out of date +updating environment: 0 added, 1 changed, 0 removed +reading sources... [100%] modules +C:\DT\natlinkcoreDoug\documentation\modules.rst:7: ERROR: Error in "toctree" directive: +invalid option block. + +.. toctree:: + :maxdepth: 2 + natlinkutils + config + loader + natlinkstatus + natlinktimer + readwritefile +looking for now-outdated files... none found +pickling environment... done +checking consistency... C:\DT\natlinkcoreDoug\documentation\config.rst: WARNING: document isn't included in any toctree +C:\DT\natlinkcoreDoug\documentation\loader.rst: WARNING: document isn't included in any toctree +C:\DT\natlinkcoreDoug\documentation\natlinkstatus.rst: WARNING: document isn't included in any toctree +C:\DT\natlinkcoreDoug\documentation\natlinkutils.rst: WARNING: document isn't included in any toctree +done +preparing documents... done +copying assets... +copying static files... done +copying extra files... done +copying assets: done +writing output... [100%] modules +generating indices... genindex py-modindex done +from_config_parser: skip "vocola2" ("vocoladirectory"):tatus + expanded to directory "" is not a valid directory +from_config_parser: skip "unimacro" ("unimacrodirectory"): + expanded to directory "" is not a valid directory +from_config_parser: skip "unimacro/unimacrogrammars" ("unimacrogrammarsdirectory"): + expanded to directory "MagicMock\mock.__getitem__()\2731595383760\unimacrogrammars" is not a valid directory +highlighting module code... [100%] natlinkcore.natlinkutils +writing additional pages... search done +dumping search index in English (code: en)... done +dumping object inventory... done +build succeeded, 5 warnings. + +The HTML pages are in _build\html. + +C:\DT\natlinkcoreDoug\documentation>make html +Running Sphinx v7.4.7 +==================== +natlinkcore, version: "5.3", release: "5.3.13" +loading translations [en]... done +Converting `source_suffix = '.rst'` to `source_suffix = {'.rst': 'restructuredtext'}`. +loading pickled environment... done +building [mo]: targets for 0 po files that are out of date +writing output... +building [html]: targets for 0 source files that are out of date +updating environment: 0 added, 0 changed, 0 removed +reading sources... +looking for now-outdated files... none found +no targets are out of date. +build succeeded. + +The HTML pages are in _build\html. + +C:\DT\natlinkcoreDoug\documentation>make html +Running Sphinx v7.4.7 +==================== +natlinkcore, version: "5.3", release: "5.3.13" +loading translations [en]... done +Converting `source_suffix = '.rst'` to `source_suffix = {'.rst': 'restructuredtext'}`. +loading pickled environment... done +building [mo]: targets for 0 po files that are out of date +writing output... +building [html]: targets for 8 source files that are out of date +updating environment: 0 added, 8 changed, 0 removed +from_config_parser: skip "vocola2" ("vocoladirectory"): + expanded to directory "" is not a valid directory +from_config_parser: skip "unimacro" ("unimacrodirectory"): + expanded to directory "" is not a valid directory +from_config_parser: skip "unimacro/unimacrogrammars" ("unimacrogrammarsdirectory"): + expanded to directory "MagicMock\mock.__getitem__()\2039310573712\unimacrogrammars" is not a valid directory +reading sources... [100%] project +C:\DT\natlinkcoreDoug\documentation\modules.rst:7: ERROR: Error in "toctree" directive: +invalid option block. + +.. toctree:: + :maxdepth: 2 + natlinkutils + config + loader + natlinkstatus + natlinktimer + readwritefile +C:\DT\natlinkcoreDoug\src\natlinkcore\natlinkutils.py:docstring of natlinkcore.natlinkutils.GrammarBase:128: ERROR: Unexpected indentation. +C:\DT\natlinkcoreDoug\src\natlinkcore\natlinkutils.py:docstring of natlinkcore.natlinkutils.GrammarBase:130: ERROR: Unexpected indentation. +C:\DT\natlinkcoreDoug\src\natlinkcore\natlinkutils.py:docstring of natlinkcore.natlinkutils.GrammarBase:131: WARNING: Block quote ends without a blank line; unexpected unindent. +C:\DT\natlinkcoreDoug\src\natlinkcore\natlinkutils.py:docstring of natlinkcore.natlinkutils.SelectGramBase:14: ERROR: Unexpected indentation. +C:\DT\natlinkcoreDoug\src\natlinkcore\natlinkutils.py:docstring of natlinkcore.natlinkutils.SelectGramBase:80: WARNING: Definition list ends without a blank line; unexpected unindent. +looking for now-outdated files... none found +pickling environment... done +checking consistency... C:\DT\natlinkcoreDoug\documentation\config.rst: WARNING: document isn't included in any toctree +C:\DT\natlinkcoreDoug\documentation\loader.rst: WARNING: document isn't included in any toctree +C:\DT\natlinkcoreDoug\documentation\natlinkstatus.rst: WARNING: document isn't included in any toctree +C:\DT\natlinkcoreDoug\documentation\natlinkutils.rst: WARNING: document isn't included in any toctree +done +preparing documents... done +copying assets... +copying static files... done +copying extra files... done +copying assets: done +writing output... [100%] project +generating indices... genindex py-modindex done +highlighting module code... [100%] natlinkcore.natlinkutils +writing additional pages... search done +dumping search index in English (code: en)... done +dumping object inventory... done +build succeeded, 10 warnings. + +The HTML pages are in _build\html. + +C:\DT\natlinkcoreDoug\documentation> \ No newline at end of file diff --git a/documentation/index.rst b/documentation/index.rst index cc04de3..98b032a 100644 --- a/documentation/index.rst +++ b/documentation/index.rst @@ -20,3 +20,4 @@ Indices and tables * :ref:`genindex` * :ref:`modindex` * :ref:`search` + diff --git a/documentation/loader.rst b/documentation/loader.rst index aa9ad5f..3fe76a8 100644 --- a/documentation/loader.rst +++ b/documentation/loader.rst @@ -11,3 +11,4 @@ Moreover, it also handles callbacks from Dragon/Natlink. .. automodule:: natlinkcore.loader :members: + diff --git a/documentation/modules.rst b/documentation/modules.rst index 7608e77..76ed240 100644 --- a/documentation/modules.rst +++ b/documentation/modules.rst @@ -10,5 +10,6 @@ This section includes the available modules in the natlinkcore repository config loader natlinkstatus - + natlinktimer + readwritefile diff --git a/documentation/natlinkstatus.rst b/documentation/natlinkstatus.rst index c97467d..550ab5b 100644 --- a/documentation/natlinkstatus.rst +++ b/documentation/natlinkstatus.rst @@ -9,4 +9,5 @@ The :code:`natlinkstatus` module keeps the status information of natlink natlinkstatus module --------------------- .. automodule:: natlinkcore.natlinkstatus - :members: \ No newline at end of file + :members: + diff --git a/documentation/natlinkutils.rst b/documentation/natlinkutils.rst index 0da1673..18b0788 100644 --- a/documentation/natlinkutils.rst +++ b/documentation/natlinkutils.rst @@ -4,9 +4,8 @@ Natlinkutils ==================== -The :code:`natlinkule` module contains the basic Grammar Classes and a few utility functions +The :code:`natlinkutils` module contains the basic Grammar Classes of "raw" Natlink (not Dragonfly) and a few utility functions -natlinkstatus module ---------------------- .. automodule:: natlinkcore.natlinkutils - :members: \ No newline at end of file + :members: + diff --git a/documentation/project.rst b/documentation/project.rst index 912e801..dfce012 100644 --- a/documentation/project.rst +++ b/documentation/project.rst @@ -7,3 +7,4 @@ Project :caption: Contents: documentation + diff --git a/src/natlinkcore/natlinkutils.py b/src/natlinkcore/natlinkutils.py index 44e1b05..02903ab 100644 --- a/src/natlinkcore/natlinkutils.py +++ b/src/natlinkcore/natlinkutils.py @@ -6,11 +6,8 @@ This file contains utility classes and functions for grammar files. - Documentation - - This file contains three base classes and some useful constants and - utility functions: - +Grammar classes +=============== GrammarBase - base class for all command and control grammars. See documentation below just before the class definition. @@ -20,18 +17,24 @@ SelectGramBase - base class for all selection grammars. See documentation below just before the class definition. + +Functions +========== + +among others, see below: + buttonClick( btnName='left',count=1 ) This function simulates a button click or button double click. Pass in the button name ('left','right' or 'middle') and the count (1 or 2) + matchWindow( moduleInfo, modName, wndText ) A utility function which determines whether moduleInfo matches a specified module name and window title. Returns window handle on match and None on mismatch. Note that moduleInfo may be ("","",0) which we should handle cleanly. - """ #pylint:disable=C0116, C0209, C0302, R0902, W0702, E1101, W0703, R1735 #pylint:disable=W0237 # inconsistent calling sequences for different classes @@ -179,7 +182,7 @@ def matchWindow(moduleInfo, modName, wndText): Returns window handle on match and None on mismatch. - Note that moduleInfo may be ("","",0) which we should handle cleanly. + Note that moduleInfo may be `("", "", 0)` which we should handle cleanly. """ if len(moduleInfo) < 3 or not moduleInfo[0]: return None @@ -288,16 +291,33 @@ def getModifierKeyCodes(modifiers): return [modifier_dict[m] for m in modifiers] def playString(keys, hooks=None): - """Deprecated. + """natlink.playString was deprecated, but is repaired via a python function with version 5.4.0 - natlink.playString, but sometimes has a drop or double of the first character it plays on the screen. +Therefore this function in natlinkutils is obsolete. - dtactions has a sendkeys function, which can replace playSting most of the time... +`playString` outputs a string of (keyboard) characters, including +control characters (like `{ctrl+c}` or `{right 3}`) +to the foreground window. + +It is implemented via the `sendkeys` function in repository `dtactions`. + +:: + + So you can either do: + from dtactions.sendkeys import sendkeys (...) sendkeys('hello world') + + or: + + from natlink import playString + (...) + playString('hello world again') + +And forget about `natlinkutils.playString`!! """ - raise DeprecationWarning('use natlink.playString or another sendkeys or sendinput function...') + natlink.playString(keys) #Classes-------------------------------------------------------------------- @@ -630,24 +650,25 @@ class GrammarBase(GramClassBase): Now if "this big red object is good" is recognized, the following callbacks will be made (in the order indicated). - -(Note: The second parameter to gotResults_XXX and gotResults is the same as -the second parameter to gotResultsInit. The example has been abbreviated -to save space.) - - gotBegin( ??? ) - gotResultsObject( 'self', resObj ) - gotResultsInit( - ['this','big','red','object','is','good'], - [('this','start'),('big','ruleOne'),('red','RuleTwo'), - ('object','ruleOne'),('is','start'),('good','start')] ) - gotResults_start( ['this'], ... ) - gotResults_ruleOne( ['big'], ... ) - gotResults_ruleTwo( ['red'], ... ) - gotResults_ruleOne( ['object'], ... ) - gotResults_start( ['is','good'], ... ) - gotResults( ['this','big','red','object','is','good'], ... ) - """ +""" +# +# (Note: The second parameter to gotResults_XXX and gotResults is the same as +# the second parameter to gotResultsInit. The example has been abbreviated +# to save space.) +# +# gotBegin( ??? ) +# gotResultsObject( 'self', resObj ) +# gotResultsInit( +# ['this','big','red','object','is','good'], +# [('this','start'),('big','ruleOne'),('red','RuleTwo'), +# ('object','ruleOne'),('is','start'),('good','start')] ) +# gotResults_start( ['this'], ... ) +# gotResults_ruleOne( ['big'], ... ) +# gotResults_ruleTwo( ['red'], ... ) +# gotResults_ruleOne( ['object'], ... ) +# gotResults_start( ['is','good'], ... ) +# gotResults( ['this','big','red','object','is','good'], ... ) +# """ def __init__(self): GramClassBase.__init__(self) @@ -1139,7 +1160,9 @@ class SelectGramBase(GramClassBase): Quintijn december 2010 adapt to throughWords as well, for future enhancement VoiceCode Quintijn August, 2009 + load( selectWords=['Select'], throughWord='Through', throughWords=None, allResults=0, hypothesis=0 ) + selectWords is a list of words or phrases which can introduce a comand of this type. For example, you can pass in a list like ['Select','Correct','Insert Before','Insert After'] to simulate @@ -1202,17 +1225,21 @@ class SelectGramBase(GramClassBase): gotResults( words, start, end ) called when results are available for this grammar. - words = list of recognized words; the first word will be one of + words = list of recognized words + the first word will be one of the words or phrases in selectWords (passed in when the grammar was created). + start = index into getSelectText of the start of the selection + end = index into getSelectText of the end of the selection + gotHypothesis( words ) only called when the hypothesis flag is set on load, this callback contains the partial recognition hypothesis during recognition. - - + + """ def load( self, selectWords=None, throughWord='through', throughWords=None,allResults=0, hypothesis=0 ): @@ -1269,9 +1296,10 @@ def resultsCallback(self, wordsAndNums, resObj): def makeGrammar(self, selectWords, throughWords): """make a selection grammar, which is similar to a Microsoft SAPI grammar. - Use the routines in gramparser.py to build the grammar just like with + Uses the routines in `gramparser.py` to build the grammar just like with command grammars. - selectWords and throughWords are lists or words + + `selectWords` and `throughWords` are lists or words. """ output = [] output.append(struct.pack("LL", 10, 0)) @@ -1294,14 +1322,16 @@ def makeGrammar(self, selectWords, throughWords): #--------------------------------------------------------------------------- -# Utility subroutine. This returns the base module name from a complete path def getBaseName(name): + """Utility subroutine. This returns the base module name from a complete path + """ return os.path.splitext(os.path.split(name)[1])[0] def convertResults(fullResults): """This utility routine converts a fullResults parameter into a dictionary +:: [ ('red','color'), ('flower','object'), ('and','conj'), ('green','color') ] Becomes: From 6d77c4dd230c6b679b197fa90a98dac9b614c8ab Mon Sep 17 00:00:00 2001 From: Quintijn Date: Fri, 30 Aug 2024 12:31:44 +0200 Subject: [PATCH 04/24] some corrections documentation --- documentation/conf.py | 1 + 1 file changed, 1 insertion(+) diff --git a/documentation/conf.py b/documentation/conf.py index 62c4e47..b41de6a 100644 --- a/documentation/conf.py +++ b/documentation/conf.py @@ -69,6 +69,7 @@ def __getattr__(cls, name): 'vocoladirectory', 'unimacrodirectory', 'unimacrogrammarsdirectory', + 'unimacro.spokenforms', } for module_name in mock_modules: sys.modules[module_name] = Mock() From 38ffad2d2ca9f5347142c1afb0a0c96a862b95f0 Mon Sep 17 00:00:00 2001 From: Quintijn Date: Fri, 30 Aug 2024 12:32:10 +0200 Subject: [PATCH 05/24] some corrections documentatoin --- documentation/errors Quintijn.txt | 394 ------------------------------ src/natlinkcore/config.py | 29 ++- 2 files changed, 14 insertions(+), 409 deletions(-) delete mode 100644 documentation/errors Quintijn.txt diff --git a/documentation/errors Quintijn.txt b/documentation/errors Quintijn.txt deleted file mode 100644 index dee991e..0000000 --- a/documentation/errors Quintijn.txt +++ /dev/null @@ -1,394 +0,0 @@ -Microsoft Windows [Version 10.0.19045.4780] -(c) Microsoft Corporation. Alle rechten voorbehouden. - -C:\Users\Gebruiker>cd \dt\natlinkcore\documentation - -C:\DT\natlinkcore\documentation>cd \dt\natlinkcoredoug - -C:\DT\natlinkcoreDoug>cd documentations -Het systeem kan het opgegeven pad niet vinden. - -C:\DT\natlinkcoreDoug>cd documentation - -C:\DT\natlinkcoreDoug\documentation>make html -Running Sphinx v7.4.7 -==================== -natlinkcore, version: "5.3", release: "5.3.13" -loading translations [en]... done -Converting `source_suffix = '.rst'` to `source_suffix = {'.rst': 'restructuredtext'}`. -loading pickled environment... done -building [mo]: targets for 0 po files that are out of date -writing output... -building [html]: targets for 2 source files that are out of date -updating environment: 0 added, 2 changed, 0 removed -from_config_parser: skip "vocola2" ("vocoladirectory"): is not a valid directory -from_config_parser: skip "unimacro" ("unimacrodirectory"): is not a valid directory -from_config_parser: skip "unimacro/unimacrogrammars" ("unimacrogrammarsdirectory"): - expanded to directory "unimacro\unimacrogrammars" is not a valid directory - -C:\DT\natlinkcoreDoug\documentation\modules.rst:7: ERROR: Error in "toctree" directive: -invalid option block. - -.. toctree:: - :maxdepth: 2 - config - loader - natlinkstatus - natlinkutils -looking for now-outdated files... none found -pickling environment... done -checking consistency... C:\DT\natlinkcoreDoug\documentation\config.rst: WARNING: document isn't included in any toctree -C:\DT\natlinkcoreDoug\documentation\loader.rst: WARNING: document isn't included in any toctree -C:\DT\natlinkcoreDoug\documentation\natlinkstatus.rst: WARNING: document isn't included in any toctree -done -preparing documents... done -copying assets... -copying static files... done -copying extra files... done -copying assets: done -writing output... [100%] natlinkstatus -generating indices... genindex py-modindex done -highlighting module code... [100%] natlinkcore.natlinkstatus -writing additional pages... search done -dumping search index in English (code: en)... done -dumping object inventory... done -build succeeded, 4 warnings. - -The HTML pages are in _build\html. - -C:\DT\natlinkcoreDoug\documentation>cd build -Het systeem kan het opgegeven pad niet vinden. - -C:\DT\natlinkcoreDoug\documentation>make html -Running Sphinx v7.4.7 -==================== -natlinkcore, version: "5.3", release: "5.3.13" -loading translations [en]... done -Converting `source_suffix = '.rst'` to `source_suffix = {'.rst': 'restructuredtext'}`. -loading pickled environment... done -building [mo]: targets for 0 po files that are out of date -writing output... -building [html]: targets for 1 source files that are out of date -updating environment: 0 added, 1 changed, 0 removed -reading sources... [100%] modules -C:\DT\natlinkcoreDoug\documentation\modules.rst:7: ERROR: Error in "toctree" directive: -invalid option block. - -.. toctree:: - :maxdepth: 2 - natlinkutils - config - loader - natlinkstatus -looking for now-outdated files... none found -pickling environment... done -checking consistency... C:\DT\natlinkcoreDoug\documentation\config.rst: WARNING: document isn't included in any toctree -C:\DT\natlinkcoreDoug\documentation\loader.rst: WARNING: document isn't included in any toctree -C:\DT\natlinkcoreDoug\documentation\natlinkstatus.rst: WARNING: document isn't included in any toctree -done -preparing documents... done -copying assets... -copying static files... done -copying extra files... done -copying assets: done -writing output... [100%] modules -generating indices... genindex py-modindex done -from_config_parser: skip "vocola2" ("vocoladirectory"): is not a valid directory -from_config_parser: skip "unimacro" ("unimacrodirectory"): is not a valid directory -from_config_parser: skip "unimacro/unimacrogrammars" ("unimacrogrammarsdirectory"): - expanded to directory "unimacro\unimacrogrammars" is not a valid directory - -writing additional pages... search done -dumping search index in English (code: en)... done -dumping object inventory... done -build succeeded, 4 warnings. - -The HTML pages are in _build\html. - -C:\DT\natlinkcoreDoug\documentation>make html -Running Sphinx v7.4.7 -==================== -natlinkcore, version: "5.3", release: "5.3.13" -loading translations [en]... done -Converting `source_suffix = '.rst'` to `source_suffix = {'.rst': 'restructuredtext'}`. -loading pickled environment... done -building [mo]: targets for 0 po files that are out of date -writing output... -building [html]: targets for 0 source files that are out of date -updating environment: 1 added, 0 changed, 0 removed -reading sources... [100%] natlinkutils -C:\DT\natlinkcoreDoug\src\natlinkcore\natlinkutils.py:docstring of natlinkcore.natlinkutils.GrammarBase:128: ERROR: Unexpected indentation. -C:\DT\natlinkcoreDoug\src\natlinkcore\natlinkutils.py:docstring of natlinkcore.natlinkutils.GrammarBase:130: ERROR: Unexpected indentation. -C:\DT\natlinkcoreDoug\src\natlinkcore\natlinkutils.py:docstring of natlinkcore.natlinkutils.GrammarBase:131: WARNING: Block quote ends without a blank line; unexpected unindent. -C:\DT\natlinkcoreDoug\src\natlinkcore\natlinkutils.py:docstring of natlinkcore.natlinkutils.SelectGramBase:14: ERROR: Unexpected indentation. -C:\DT\natlinkcoreDoug\src\natlinkcore\natlinkutils.py:docstring of natlinkcore.natlinkutils.SelectGramBase:80: WARNING: Definition list ends without a blank line; unexpected unindent. -looking for now-outdated files... none found -pickling environment... done -checking consistency... C:\DT\natlinkcoreDoug\documentation\config.rst: WARNING: document isn't included in any toctree -C:\DT\natlinkcoreDoug\documentation\loader.rst: WARNING: document isn't included in any toctree -C:\DT\natlinkcoreDoug\documentation\natlinkstatus.rst: WARNING: document isn't included in any toctree -C:\DT\natlinkcoreDoug\documentation\natlinkutils.rst: WARNING: document isn't included in any toctree -done -preparing documents... done -copying assets... -copying static files... done -copying extra files... done -copying assets: done -writing output... [100%] natlinkutils -generating indices... genindex py-modindex done -from_config_parser: skip "vocola2" ("vocoladirectory"):tatus - expanded to directory "" is not a valid directory -from_config_parser: skip "unimacro" ("unimacrodirectory"): - expanded to directory "" is not a valid directory -from_config_parser: skip "unimacro/unimacrogrammars" ("unimacrogrammarsdirectory"): - expanded to directory "MagicMock\mock.__getitem__()\2546067778640\unimacrogrammars" is not a valid directory -highlighting module code... [100%] natlinkcore.natlinkutils -writing additional pages... search done -dumping search index in English (code: en)... done -dumping object inventory... done -build succeeded, 9 warnings. - -The HTML pages are in _build\html. - -C:\DT\natlinkcoreDoug\documentation>make html -Running Sphinx v7.4.7 -==================== -natlinkcore, version: "5.3", release: "5.3.13" -loading translations [en]... done -Converting `source_suffix = '.rst'` to `source_suffix = {'.rst': 'restructuredtext'}`. -loading pickled environment... done -building [mo]: targets for 0 po files that are out of date -writing output... -building [html]: targets for 1 source files that are out of date -updating environment: 0 added, 1 changed, 0 removed -reading sources... [100%] modules -C:\DT\natlinkcoreDoug\documentation\modules.rst:7: ERROR: Error in "toctree" directive: -invalid option block. - -.. toctree:: - :maxdepth: 2 - natlinkutils.rst - config.rst - loader.rst - natlinkstatus.rst -looking for now-outdated files... none found -pickling environment... done -checking consistency... C:\DT\natlinkcoreDoug\documentation\config.rst: WARNING: document isn't included in any toctree -C:\DT\natlinkcoreDoug\documentation\loader.rst: WARNING: document isn't included in any toctree -C:\DT\natlinkcoreDoug\documentation\natlinkstatus.rst: WARNING: document isn't included in any toctree -C:\DT\natlinkcoreDoug\documentation\natlinkutils.rst: WARNING: document isn't included in any toctree -done -preparing documents... done -copying assets... -copying static files... done -copying extra files... done -copying assets: done -writing output... [100%] modules -generating indices... genindex py-modindex done -from_config_parser: skip "vocola2" ("vocoladirectory"):tatus - expanded to directory "" is not a valid directory -from_config_parser: skip "unimacro" ("unimacrodirectory"): - expanded to directory "" is not a valid directory -from_config_parser: skip "unimacro/unimacrogrammars" ("unimacrogrammarsdirectory"): - expanded to directory "MagicMock\mock.__getitem__()\2389399509136\unimacrogrammars" is not a valid directory -highlighting module code... [100%] natlinkcore.natlinkutils -writing additional pages... search done -dumping search index in English (code: en)... done -dumping object inventory... done -build succeeded, 5 warnings. - -The HTML pages are in _build\html. - -C:\DT\natlinkcoreDoug\documentation>make html -Running Sphinx v7.4.7 -==================== -natlinkcore, version: "5.3", release: "5.3.13" -loading translations [en]... done -Converting `source_suffix = '.rst'` to `source_suffix = {'.rst': 'restructuredtext'}`. -loading pickled environment... done -building [mo]: targets for 0 po files that are out of date -writing output... -building [html]: targets for 1 source files that are out of date -updating environment: 0 added, 1 changed, 0 removed -reading sources... [100%] modules -C:\DT\natlinkcoreDoug\documentation\modules.rst:7: ERROR: Error in "toctree" directive: -invalid option block. - -.. toctree:: - :maxdepth: 2 - natlinkutils - config - loader - natlinkstatus -looking for now-outdated files... none found -pickling environment... done -checking consistency... C:\DT\natlinkcoreDoug\documentation\config.rst: WARNING: document isn't included in any toctree -C:\DT\natlinkcoreDoug\documentation\loader.rst: WARNING: document isn't included in any toctree -C:\DT\natlinkcoreDoug\documentation\natlinkstatus.rst: WARNING: document isn't included in any toctree -C:\DT\natlinkcoreDoug\documentation\natlinkutils.rst: WARNING: document isn't included in any toctree -done -preparing documents... done -copying assets... -copying static files... done -copying extra files... done -copying assets: done -writing output... [100%] modules -generating indices... genindex py-modindex done -from_config_parser: skip "vocola2" ("vocoladirectory"):tatus - expanded to directory "" is not a valid directory -from_config_parser: skip "unimacro" ("unimacrodirectory"): - expanded to directory "" is not a valid directory -from_config_parser: skip "unimacro/unimacrogrammars" ("unimacrogrammarsdirectory"): - expanded to directory "MagicMock\mock.__getitem__()\2383906996304\unimacrogrammars" is not a valid directory -highlighting module code... [100%] natlinkcore.natlinkutils -writing additional pages... search done -dumping search index in English (code: en)... done -dumping object inventory... done -build succeeded, 5 warnings. - -The HTML pages are in _build\html. - -C:\DT\natlinkcoreDoug\documentation>make html -Running Sphinx v7.4.7 -==================== -natlinkcore, version: "5.3", release: "5.3.13" -loading translations [en]... done -Converting `source_suffix = '.rst'` to `source_suffix = {'.rst': 'restructuredtext'}`. -loading pickled environment... done -building [mo]: targets for 0 po files that are out of date -writing output... -building [html]: targets for 0 source files that are out of date -updating environment: 0 added, 0 changed, 0 removed -reading sources... -looking for now-outdated files... none found -no targets are out of date. -build succeeded. - -The HTML pages are in _build\html. - -C:\DT\natlinkcoreDoug\documentation>make html -Running Sphinx v7.4.7 -==================== -natlinkcore, version: "5.3", release: "5.3.13" -loading translations [en]... done -Converting `source_suffix = '.rst'` to `source_suffix = {'.rst': 'restructuredtext'}`. -loading pickled environment... done -building [mo]: targets for 0 po files that are out of date -writing output... -building [html]: targets for 1 source files that are out of date -updating environment: 0 added, 1 changed, 0 removed -reading sources... [100%] modules -C:\DT\natlinkcoreDoug\documentation\modules.rst:7: ERROR: Error in "toctree" directive: -invalid option block. - -.. toctree:: - :maxdepth: 2 - natlinkutils - config - loader - natlinkstatus - natlinktimer - readwritefile -looking for now-outdated files... none found -pickling environment... done -checking consistency... C:\DT\natlinkcoreDoug\documentation\config.rst: WARNING: document isn't included in any toctree -C:\DT\natlinkcoreDoug\documentation\loader.rst: WARNING: document isn't included in any toctree -C:\DT\natlinkcoreDoug\documentation\natlinkstatus.rst: WARNING: document isn't included in any toctree -C:\DT\natlinkcoreDoug\documentation\natlinkutils.rst: WARNING: document isn't included in any toctree -done -preparing documents... done -copying assets... -copying static files... done -copying extra files... done -copying assets: done -writing output... [100%] modules -generating indices... genindex py-modindex done -from_config_parser: skip "vocola2" ("vocoladirectory"):tatus - expanded to directory "" is not a valid directory -from_config_parser: skip "unimacro" ("unimacrodirectory"): - expanded to directory "" is not a valid directory -from_config_parser: skip "unimacro/unimacrogrammars" ("unimacrogrammarsdirectory"): - expanded to directory "MagicMock\mock.__getitem__()\2731595383760\unimacrogrammars" is not a valid directory -highlighting module code... [100%] natlinkcore.natlinkutils -writing additional pages... search done -dumping search index in English (code: en)... done -dumping object inventory... done -build succeeded, 5 warnings. - -The HTML pages are in _build\html. - -C:\DT\natlinkcoreDoug\documentation>make html -Running Sphinx v7.4.7 -==================== -natlinkcore, version: "5.3", release: "5.3.13" -loading translations [en]... done -Converting `source_suffix = '.rst'` to `source_suffix = {'.rst': 'restructuredtext'}`. -loading pickled environment... done -building [mo]: targets for 0 po files that are out of date -writing output... -building [html]: targets for 0 source files that are out of date -updating environment: 0 added, 0 changed, 0 removed -reading sources... -looking for now-outdated files... none found -no targets are out of date. -build succeeded. - -The HTML pages are in _build\html. - -C:\DT\natlinkcoreDoug\documentation>make html -Running Sphinx v7.4.7 -==================== -natlinkcore, version: "5.3", release: "5.3.13" -loading translations [en]... done -Converting `source_suffix = '.rst'` to `source_suffix = {'.rst': 'restructuredtext'}`. -loading pickled environment... done -building [mo]: targets for 0 po files that are out of date -writing output... -building [html]: targets for 8 source files that are out of date -updating environment: 0 added, 8 changed, 0 removed -from_config_parser: skip "vocola2" ("vocoladirectory"): - expanded to directory "" is not a valid directory -from_config_parser: skip "unimacro" ("unimacrodirectory"): - expanded to directory "" is not a valid directory -from_config_parser: skip "unimacro/unimacrogrammars" ("unimacrogrammarsdirectory"): - expanded to directory "MagicMock\mock.__getitem__()\2039310573712\unimacrogrammars" is not a valid directory -reading sources... [100%] project -C:\DT\natlinkcoreDoug\documentation\modules.rst:7: ERROR: Error in "toctree" directive: -invalid option block. - -.. toctree:: - :maxdepth: 2 - natlinkutils - config - loader - natlinkstatus - natlinktimer - readwritefile -C:\DT\natlinkcoreDoug\src\natlinkcore\natlinkutils.py:docstring of natlinkcore.natlinkutils.GrammarBase:128: ERROR: Unexpected indentation. -C:\DT\natlinkcoreDoug\src\natlinkcore\natlinkutils.py:docstring of natlinkcore.natlinkutils.GrammarBase:130: ERROR: Unexpected indentation. -C:\DT\natlinkcoreDoug\src\natlinkcore\natlinkutils.py:docstring of natlinkcore.natlinkutils.GrammarBase:131: WARNING: Block quote ends without a blank line; unexpected unindent. -C:\DT\natlinkcoreDoug\src\natlinkcore\natlinkutils.py:docstring of natlinkcore.natlinkutils.SelectGramBase:14: ERROR: Unexpected indentation. -C:\DT\natlinkcoreDoug\src\natlinkcore\natlinkutils.py:docstring of natlinkcore.natlinkutils.SelectGramBase:80: WARNING: Definition list ends without a blank line; unexpected unindent. -looking for now-outdated files... none found -pickling environment... done -checking consistency... C:\DT\natlinkcoreDoug\documentation\config.rst: WARNING: document isn't included in any toctree -C:\DT\natlinkcoreDoug\documentation\loader.rst: WARNING: document isn't included in any toctree -C:\DT\natlinkcoreDoug\documentation\natlinkstatus.rst: WARNING: document isn't included in any toctree -C:\DT\natlinkcoreDoug\documentation\natlinkutils.rst: WARNING: document isn't included in any toctree -done -preparing documents... done -copying assets... -copying static files... done -copying extra files... done -copying assets: done -writing output... [100%] project -generating indices... genindex py-modindex done -highlighting module code... [100%] natlinkcore.natlinkutils -writing additional pages... search done -dumping search index in English (code: en)... done -dumping object inventory... done -build succeeded, 10 warnings. - -The HTML pages are in _build\html. - -C:\DT\natlinkcoreDoug\documentation> \ No newline at end of file diff --git a/src/natlinkcore/config.py b/src/natlinkcore/config.py index d4c2cd1..2a575fe 100644 --- a/src/natlinkcore/config.py +++ b/src/natlinkcore/config.py @@ -144,20 +144,19 @@ def from_first_found_file(cls, files: Iterable[str]) -> 'NatlinkConfig': raise NoGoodConfigFoundException('No natlink config file found, please run configure natlink program\n\t(***configurenatlink***)') def expand_path(input_path: str) -> str: - r"""expand path if it starts with "~" or has environment variables (%XXXX%) + r"""expand path if it starts with "~" or starts with an environment variable or name of python package - Paths can be: - - - the name of a python package, or a sub directory of a python package - - Obsolete: natlink_userdir/...: the directory where natlink.ini is is searched for, either %(NATLINK_USERDIR) or ~/.natlink - - ~/...: the home directory - - Now: natlink_settingsdir/...: the directory where natlink.ini is is searched for, either %(NATLINK_SETTINGSDIR) or ~/.natlink - - ~/...: the home directory - - some environment variable: this environment variable is expanded. - - The Documents directory can be found by "~\Documents"... - - When nothing to expand, return input +Paths can be: + +- the name of a python package, or a sub directory of a python package +- natlink_settingsdir/...: the directory where natlink.ini is is searched for, either`%(NATLINK_SETTINGSDIR)` or `~/.natlink` +- `~/...`: the home directory +- Obsolete: natlink_userdir/...: instead natlink_settingsdir will be searched for, and a message is thrown. In the config program things are checked more thoroughly. +- some other environment variable: this environment variable is expanded + +The Documents directory can be found by "~\Documents"... + +When there is nothing to expand, just return the input """ expanduser, expandvars, normpath, isdir = os.path.expanduser, os.path.expandvars, os.path.normpath, os.path.isdir @@ -228,10 +227,10 @@ def expand_path(input_path: str) -> str: return normpath(env_expanded) def expand_natlink_settingsdir(): - """not with envvariables, but special: + """Return the location of the natlink config files if NATLINK_SETTINGSDIR is set: return this, but... it should end with ".natlink" - if NATLINK_SETTINGSDIR is NOT set: return Path.home()/'.natlink' + if NATLINK_SETTINGSDIR is NOT set: return `Path.home()/'.natlink'` """ normpath = os.path.normpath nsd = os.getenv('natlink_settingsdir') From fc1abff7f9b6c468323677201af634958ee0081a Mon Sep 17 00:00:00 2001 From: Quintijn Date: Tue, 8 Oct 2024 12:38:39 +0200 Subject: [PATCH 06/24] fixing relative small error in natlinkconfigfunctions.py --- .../configure/natlinkconfigfunctions.py | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/natlinkcore/configure/natlinkconfigfunctions.py b/src/natlinkcore/configure/natlinkconfigfunctions.py index be6a243..700047a 100644 --- a/src/natlinkcore/configure/natlinkconfigfunctions.py +++ b/src/natlinkcore/configure/natlinkconfigfunctions.py @@ -233,11 +233,22 @@ def setDirectory(self, option, dir_path, section=None): nice_dir_path = self.prefix_home(dir_path) nice_dir_path = nice_dir_path.replace('/', '\\') self.config_set(section, option, nice_dir_path) + dir_path = dir_path.strip() + directory = createIfNotThere(dir_path, level_up=1) + if not (directory and Path(directory).is_dir()): + logging.warning(f'Cannot set directory "{option}", the given path is invalid: "{directory}" ("{dir_path}")') + self.config_remove('previous settings', option) - if section == 'directories': - logging.info(f'Set option "{option}" to "{dir_path}"') + if nice_dir_path == dir_path: + if section == 'directories': + logging.info(f'Set option "{option}" to "{dir_path}"') + else: + logging.info(f'Set in section "{section}", option "{option}" to "{dir_path}"') else: - logging.info(f'Set in section "{section}", option "{option}" to "{dir_path}"') + if section == 'directories': + logging.info(f'Set option "{option}" to "{nice_dir_path}" (expanding to "{dir_path}")') + else: + logging.info(f'Set in section "{section}", option "{option}" to "{nice_dir_path}" (expanding to "{dir_path}")') return def clearDirectory(self, option, section=None): @@ -442,7 +453,10 @@ def enable_vocola(self, arg): if not vocola_user_dir: return # vocGrammarsDir = self.status.getVocolaGrammarsDirectory() - vocGrammarsDir = r'natlink_settings\vocolagrammars' + vocGrammarsDir = config.expand_path(r'natlink_settingsdir\vocolagrammars') + if not vocGrammarsDir: + logging.warning('Could not expand directory for vocola grammars') + return self.setDirectory('vocoladirectory','vocola2') #always vocola2 self.setDirectory('vocolagrammarsdirectory', vocGrammarsDir) self.copyUnimacroIncludeFile() @@ -756,7 +770,7 @@ def createIfNotThere(path_name, level_up=None): """ level_up = level_up or 1 dir_path = isValidDir(path_name) - if dir_path: + if dir_path and Path(dir_path).is_dir(): return dir_path start_path = config.expand_path(path_name) up_path = Path(start_path) From f03a994b4979f5f520623a400b4cc418d7a19115 Mon Sep 17 00:00:00 2001 From: Quintijn Date: Tue, 8 Oct 2024 14:10:07 +0200 Subject: [PATCH 07/24] test for run in elevated mode... --- .../configure/natlinkconfigfunctions.py | 9 ++++++-- tests/manual_test_pyuac.py | 23 +++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 tests/manual_test_pyuac.py diff --git a/src/natlinkcore/configure/natlinkconfigfunctions.py b/src/natlinkcore/configure/natlinkconfigfunctions.py index 700047a..e882a40 100644 --- a/src/natlinkcore/configure/natlinkconfigfunctions.py +++ b/src/natlinkcore/configure/natlinkconfigfunctions.py @@ -24,7 +24,7 @@ from pathlib import Path import configparser import logging - +import pyuac try: from natlinkcore import natlinkstatus except OSError: @@ -42,7 +42,12 @@ def do_pip(*args): Run a pip command with args. Diagnostic logging.3 """ - + # run pip in elevated mode: + if not pyuac.isUserAdmin(): + pyuac.runAsAdmin() + + if pyuac.isUserAdmin(): + print('continue in Admin (elevated) mode...') command = [sys.executable,"-m", "pip"] + list(args) logging.info(f"command: {command} ") diff --git a/tests/manual_test_pyuac.py b/tests/manual_test_pyuac.py new file mode 100644 index 0000000..0faeeb5 --- /dev/null +++ b/tests/manual_test_pyuac.py @@ -0,0 +1,23 @@ +""" +Update as of 19-02-2023 + +The update to the below script is now alive as a Python package by the same author. You can install it from PyPi, which lives at https://pypi.org/project/pyuac/, and the source code/home page is located at https://github.com/Preston-Landers/pyuac. Install it using: + +pip install pyuac +pip install pypiwin32 +Direct usage of the package is: +""" + +import pyuac + +def main(): + print("Do stuff here that requires being run as an admin.") + # The window will disappear as soon as the program exits! + input("Press enter to close the window. >") + +if __name__ == "__main__": + if not pyuac.isUserAdmin(): + print("Re-launching as admin!") + pyuac.runAsAdmin() + else: + main() # Already an admin here. \ No newline at end of file From 3706c21e933447a98bd4b42b49c98fb085819029 Mon Sep 17 00:00:00 2001 From: Quintijn Date: Tue, 8 Oct 2024 12:38:39 +0200 Subject: [PATCH 08/24] fixing relative small error in natlinkconfigfunctions.py --- .../configure/natlinkconfigfunctions.py | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/natlinkcore/configure/natlinkconfigfunctions.py b/src/natlinkcore/configure/natlinkconfigfunctions.py index 861854d..b9f24fb 100644 --- a/src/natlinkcore/configure/natlinkconfigfunctions.py +++ b/src/natlinkcore/configure/natlinkconfigfunctions.py @@ -235,11 +235,22 @@ def setDirectory(self, option, dir_path, section=None): nice_dir_path = self.prefix_home(dir_path) nice_dir_path = nice_dir_path.replace('/', '\\') self.config_set(section, option, nice_dir_path) + dir_path = dir_path.strip() + directory = createIfNotThere(dir_path, level_up=1) + if not (directory and Path(directory).is_dir()): + logging.warning(f'Cannot set directory "{option}", the given path is invalid: "{directory}" ("{dir_path}")') + self.config_remove('previous settings', option) - if section == 'directories': - logging.info(f'Set option "{option}" to "{dir_path}"') + if nice_dir_path == dir_path: + if section == 'directories': + logging.info(f'Set option "{option}" to "{dir_path}"') + else: + logging.info(f'Set in section "{section}", option "{option}" to "{dir_path}"') else: - logging.info(f'Set in section "{section}", option "{option}" to "{dir_path}"') + if section == 'directories': + logging.info(f'Set option "{option}" to "{nice_dir_path}" (expanding to "{dir_path}")') + else: + logging.info(f'Set in section "{section}", option "{option}" to "{nice_dir_path}" (expanding to "{dir_path}")') return def clearDirectory(self, option, section=None): @@ -444,7 +455,10 @@ def enable_vocola(self, arg): if not vocola_user_dir: return # vocGrammarsDir = self.status.getVocolaGrammarsDirectory() - vocGrammarsDir = r'natlink_settings\vocolagrammars' + vocGrammarsDir = config.expand_path(r'natlink_settingsdir\vocolagrammars') + if not vocGrammarsDir: + logging.warning('Could not expand directory for vocola grammars') + return self.setDirectory('vocoladirectory','vocola2') #always vocola2 self.setDirectory('vocolagrammarsdirectory', vocGrammarsDir) self.copyUnimacroIncludeFile() @@ -758,7 +772,7 @@ def createIfNotThere(path_name, level_up=None): """ level_up = level_up or 1 dir_path = isValidDir(path_name) - if dir_path: + if dir_path and Path(dir_path).is_dir(): return dir_path start_path = config.expand_path(path_name) up_path = Path(start_path) From 1ad6ae22bbf93348c757db7c8111e256d11bc5a0 Mon Sep 17 00:00:00 2001 From: Quintijn Date: Tue, 8 Oct 2024 14:10:07 +0200 Subject: [PATCH 09/24] test for run in elevated mode... --- .../configure/natlinkconfigfunctions.py | 9 ++++++-- tests/manual_test_pyuac.py | 23 +++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 tests/manual_test_pyuac.py diff --git a/src/natlinkcore/configure/natlinkconfigfunctions.py b/src/natlinkcore/configure/natlinkconfigfunctions.py index b9f24fb..284bf92 100644 --- a/src/natlinkcore/configure/natlinkconfigfunctions.py +++ b/src/natlinkcore/configure/natlinkconfigfunctions.py @@ -24,7 +24,7 @@ from pathlib import Path import configparser import logging - +import pyuac try: from natlinkcore import natlinkstatus except OSError: @@ -42,7 +42,12 @@ def do_pip(*args): Run a pip command with args. Diagnostic logging.3 """ - + # run pip in elevated mode: + if not pyuac.isUserAdmin(): + pyuac.runAsAdmin() + + if pyuac.isUserAdmin(): + print('continue in Admin (elevated) mode...') command = [sys.executable,"-m", "pip"] + list(args) logging.info(f"command: {command} ") diff --git a/tests/manual_test_pyuac.py b/tests/manual_test_pyuac.py new file mode 100644 index 0000000..0faeeb5 --- /dev/null +++ b/tests/manual_test_pyuac.py @@ -0,0 +1,23 @@ +""" +Update as of 19-02-2023 + +The update to the below script is now alive as a Python package by the same author. You can install it from PyPi, which lives at https://pypi.org/project/pyuac/, and the source code/home page is located at https://github.com/Preston-Landers/pyuac. Install it using: + +pip install pyuac +pip install pypiwin32 +Direct usage of the package is: +""" + +import pyuac + +def main(): + print("Do stuff here that requires being run as an admin.") + # The window will disappear as soon as the program exits! + input("Press enter to close the window. >") + +if __name__ == "__main__": + if not pyuac.isUserAdmin(): + print("Re-launching as admin!") + pyuac.runAsAdmin() + else: + main() # Already an admin here. \ No newline at end of file From 3d90f4780cd9f28122821f8e0de715446a54e3ac Mon Sep 17 00:00:00 2001 From: Quintijn Hoogenboom Date: Mon, 2 Dec 2024 13:38:16 +0100 Subject: [PATCH 10/24] problem vocolagrammarsdirectory faded, but still check the automatic do_pip function!!! --- src/natlinkcore/configure/natlinkconfig_cli.py | 4 ++-- src/natlinkcore/configure/natlinkconfigfunctions.py | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/natlinkcore/configure/natlinkconfig_cli.py b/src/natlinkcore/configure/natlinkconfig_cli.py index 4bbbf95..5f5fc93 100644 --- a/src/natlinkcore/configure/natlinkconfig_cli.py +++ b/src/natlinkcore/configure/natlinkconfig_cli.py @@ -90,9 +90,9 @@ def _main(Options=None): class CLI(cmd.Cmd): """provide interactive shell control for the different options. """ - def __init__(self, Config=None,extra_pip_options=[]): + def __init__(self, Config=None,extra_pip_options=None): cmd.Cmd.__init__(self) - self.extra_pip_options=extra_pip_options + self.extra_pip_options=extra_pip_options or [] self.prompt = '\nNatlink config> ' self.info = "type 'u' for usage" self.Config = None diff --git a/src/natlinkcore/configure/natlinkconfigfunctions.py b/src/natlinkcore/configure/natlinkconfigfunctions.py index 284bf92..dd8f35e 100644 --- a/src/natlinkcore/configure/natlinkconfigfunctions.py +++ b/src/natlinkcore/configure/natlinkconfigfunctions.py @@ -39,12 +39,15 @@ def do_pip(*args): """ - Run a pip command with args. + Run a pip command with args. Diagnostic logging.3 """ # run pip in elevated mode: if not pyuac.isUserAdmin(): - pyuac.runAsAdmin() + # print('If you want to pip upgrade packages, a new "elevated" process is started. Please answer Y in that case') + print('Please run this program in "elevated" mode, when you want to pip upgrade packages') + return + # pyuac.runAsAdmin() if pyuac.isUserAdmin(): print('continue in Admin (elevated) mode...') From ab090c439fc605322f72a3e6fa48a1c692a198b0 Mon Sep 17 00:00:00 2001 From: Quintijn Hoogenboom Date: Tue, 3 Dec 2024 16:03:35 +0100 Subject: [PATCH 11/24] setting elevated mode in natlinkconfigfunctions, as now used in Configure Natlink with CLI. fixing import dragonfly2 to import dragonfly in natlinkstatus.py --- src/natlinkcore/configure/natlinkconfigfunctions.py | 6 +++--- src/natlinkcore/natlinkstatus.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/natlinkcore/configure/natlinkconfigfunctions.py b/src/natlinkcore/configure/natlinkconfigfunctions.py index dd8f35e..028bc8e 100644 --- a/src/natlinkcore/configure/natlinkconfigfunctions.py +++ b/src/natlinkcore/configure/natlinkconfigfunctions.py @@ -45,9 +45,9 @@ def do_pip(*args): # run pip in elevated mode: if not pyuac.isUserAdmin(): # print('If you want to pip upgrade packages, a new "elevated" process is started. Please answer Y in that case') - print('Please run this program in "elevated" mode, when you want to pip upgrade packages') - return - # pyuac.runAsAdmin() + # print('Please run this program in "elevated" mode, when you want to pip upgrade packages') + # return + pyuac.runAsAdmin() if pyuac.isUserAdmin(): print('continue in Admin (elevated) mode...') diff --git a/src/natlinkcore/natlinkstatus.py b/src/natlinkcore/natlinkstatus.py index a805b03..92b0346 100644 --- a/src/natlinkcore/natlinkstatus.py +++ b/src/natlinkcore/natlinkstatus.py @@ -532,12 +532,12 @@ def getDragonflyDirectory(self): if self.DragonflyDirectory is not None: return self.DragonflyDirectory try: - import dragonfly2 + import dragonfly except ImportError: self.DragonflyDirectory = "" return "" - - self.DragonflyDirectory = str(Path(dragonfly2.__file__).parent) + + self.DragonflyDirectory = str(Path(dragonfly.__file__).parent) return self.DragonflyDirectory getdragonflydirectory = getDragonflyDirectory From d6cb783c7a9206c83237f89439162f654b4b4fa5 Mon Sep 17 00:00:00 2001 From: Quintijn Hoogenboom Date: Wed, 2 Apr 2025 15:38:11 +0200 Subject: [PATCH 12/24] making a small (but essential fix) in natlinkconfigfunctions setDirectory, and then specifying a path that has to be created one level up (vocolaGrammarsDirectory) --- src/natlinkcore/configure/natlinkconfigfunctions.py | 7 +++++-- tests/test_config.py | 4 ++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/natlinkcore/configure/natlinkconfigfunctions.py b/src/natlinkcore/configure/natlinkconfigfunctions.py index 028bc8e..2985fbd 100644 --- a/src/natlinkcore/configure/natlinkconfigfunctions.py +++ b/src/natlinkcore/configure/natlinkconfigfunctions.py @@ -230,10 +230,13 @@ def setDirectory(self, option, dir_path, section=None): return dir_path = dir_path.strip() + directory = config.expand_path(dir_path) + if directory is False: + logging.error(f'Cannot expand dir_path: "{dir_path}"') + return + directory = createIfNotThere(dir_path, level_up=1) if not (directory and Path(directory).is_dir()): - if directory is False: - directory = config.expand_path(dir_path) if dir_path == directory: logging.info(f'Cannot set "{option}", the given path is invalid: "{directory}"') else: diff --git a/tests/test_config.py b/tests/test_config.py index 3a32b74..a4eb6e8 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -157,6 +157,10 @@ def test_expand_path(mock_syspath,mock_settingsdir): result = expand_path('natlink_settingsdir/invalid_dir') assert not os.path.isdir(result) + # invalid prefix (natlink_settingsdir) + result = expand_path('natlink_sssettingsdir/invalid_prefix') + assert not os.path.isdir(result) + # try package result = expand_path('natlinkcore') assert os.path.isdir(result) From c74eeb5a75950fffc1603754af3382a21c8e1c40 Mon Sep 17 00:00:00 2001 From: Quintijn Hoogenboom Date: Wed, 2 Apr 2025 15:59:01 +0200 Subject: [PATCH 13/24] How check if you are in elevatied mode (natlinkconfigfunctions line 39) --- src/natlinkcore/configure/natlinkconfigfunctions.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/natlinkcore/configure/natlinkconfigfunctions.py b/src/natlinkcore/configure/natlinkconfigfunctions.py index 2985fbd..3ff58fc 100644 --- a/src/natlinkcore/configure/natlinkconfigfunctions.py +++ b/src/natlinkcore/configure/natlinkconfigfunctions.py @@ -36,12 +36,17 @@ isfile, isdir, join = os.path.isfile, os.path.isdir, os.path.join +def _am_elevated(): + """return True is state is elevated + """ + pass # todoDoug def do_pip(*args): """ Run a pip command with args. Diagnostic logging.3 """ + I_am-elevated = _am_elevated() # run pip in elevated mode: if not pyuac.isUserAdmin(): # print('If you want to pip upgrade packages, a new "elevated" process is started. Please answer Y in that case') From 0afcb90229b91e67ef5ef9eaabc085796b73059f Mon Sep 17 00:00:00 2001 From: Quintijn Hoogenboom Date: Wed, 2 Apr 2025 16:06:16 +0200 Subject: [PATCH 14/24] new functions to make _am_elevated and _want_elevated --- src/natlinkcore/configure/natlinkconfigfunctions.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/natlinkcore/configure/natlinkconfigfunctions.py b/src/natlinkcore/configure/natlinkconfigfunctions.py index 3ff58fc..8897660 100644 --- a/src/natlinkcore/configure/natlinkconfigfunctions.py +++ b/src/natlinkcore/configure/natlinkconfigfunctions.py @@ -41,6 +41,12 @@ def _am_elevated(): """ pass # todoDoug +def _want_elevated(): + """elevated mode is wanted? + If sys.executable does not start with the home directory I would think + """ + pass # todoDoug + def do_pip(*args): """ Run a pip command with args. From 413073efde40258c9da0e3006f81fddae2d9131f Mon Sep 17 00:00:00 2001 From: Quintijn Hoogenboom Date: Wed, 2 Apr 2025 17:39:23 +0200 Subject: [PATCH 15/24] added commands p and P in natlinkconfig_cli.py. The gui can call these with do_p and do_P. These (are going) to pip upgade the existing packages. Doug, please help --- .../configure/natlinkconfig_cli.py | 44 ++++++++++++++++++- .../configure/natlinkconfigfunctions.py | 10 +++-- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/src/natlinkcore/configure/natlinkconfig_cli.py b/src/natlinkcore/configure/natlinkconfig_cli.py index 5f5fc93..abaa9a3 100644 --- a/src/natlinkcore/configure/natlinkconfig_cli.py +++ b/src/natlinkcore/configure/natlinkconfig_cli.py @@ -13,6 +13,10 @@ from argparse import ArgumentParser extra_pip_options=[] + +# packages that should go with the do_p or do_P command, upgrading with pip +# is packages is already there: todoDoug +packages_to_pip = ['natlinkcore', 'dragonfly' 'unimacro', 'vocola2', 'caster'] appname="natlink" logdir = Path(user_log_dir(appname=appname,ensure_exists=True)) logfilename=logdir/"cli_log.txt" @@ -42,7 +46,7 @@ def _main(Options=None): """ - shortOptions = "DVNOHKaAiIxXbBuqe" + shortOptions = "DVNOHKaAiIxXbBuqepP" shortArgOptions = "d:v:n:o:h:k:" if Options: if isinstance(Options, str): @@ -193,6 +197,44 @@ def do_j(self, arg): self.Config.printPythonPath() + def do_p(self, arg): + """do pip according to runtime options or NOT in --pre mode + + upgrade all installed packages of the list natlinkcore, dragonfly, unimacro vocola2. + + Other packages (dtactions) should be in the dependencies of above. + + """ + do_pre = arg == '--pre' or '' ## todoDoug + for package in packages_to_pip: ## global variable + try: + import package + except ImportError: + print(f'package not installed: "{package}", do not upgrade') + continue + + self.Config.do_pip(package) + + def do_P(self, arg): + """Upgrade pip packags with --pre mode on + + upgrade all installed packages of the list natlinkcore, dragonfly, unimacro vocola2. + + Other packages (dtactions) should be in the dependencies of above. + + """ + self.do_p("--pre") + + def help_p(self): + print(''*60) + print("""The commands "p" and "P" will pip upgrade the installed packages, +like natlinkcore, vocola2, unimacro, dragonfly. +Other modules will follow as dependencies. +(lower case) "p" will take only major releases, +(upper case) "P" will also take so called "pre" releases. + + """) + def do_e(self,arg): print("extensions and folders for registered natlink extensions:") ef="" diff --git a/src/natlinkcore/configure/natlinkconfigfunctions.py b/src/natlinkcore/configure/natlinkconfigfunctions.py index 8897660..202d65c 100644 --- a/src/natlinkcore/configure/natlinkconfigfunctions.py +++ b/src/natlinkcore/configure/natlinkconfigfunctions.py @@ -36,23 +36,27 @@ isfile, isdir, join = os.path.isfile, os.path.isdir, os.path.join -def _am_elevated(): +def am_elevated(): """return True is state is elevated """ pass # todoDoug + return False -def _want_elevated(): +def want_elevated(): """elevated mode is wanted? If sys.executable does not start with the home directory I would think """ pass # todoDoug + return False def do_pip(*args): """ Run a pip command with args. Diagnostic logging.3 """ - I_am-elevated = _am_elevated() + I_am_elevated = am_elevated() + I_want_elevated = want_elevated() + print(f'do_pip: I_am_elevated: {I_am_elevated}, I_want_elevated: {I_want_elevated}') # run pip in elevated mode: if not pyuac.isUserAdmin(): # print('If you want to pip upgrade packages, a new "elevated" process is started. Please answer Y in that case') From e7c944bbe00ec44fbe633144cf52b8a039878675 Mon Sep 17 00:00:00 2001 From: Quintijn Hoogenboom Date: Thu, 3 Apr 2025 16:40:01 +0200 Subject: [PATCH 16/24] a few details of the --pre or not action with do_pip added. Doug, look for todoDoug --- src/natlinkcore/configure/natlinkconfig_cli.py | 2 +- src/natlinkcore/configure/natlinkconfigfunctions.py | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/natlinkcore/configure/natlinkconfig_cli.py b/src/natlinkcore/configure/natlinkconfig_cli.py index abaa9a3..646d4e9 100644 --- a/src/natlinkcore/configure/natlinkconfig_cli.py +++ b/src/natlinkcore/configure/natlinkconfig_cli.py @@ -213,7 +213,7 @@ def do_p(self, arg): print(f'package not installed: "{package}", do not upgrade') continue - self.Config.do_pip(package) + self.Config.do_pip(package, do_pre) def do_P(self, arg): """Upgrade pip packags with --pre mode on diff --git a/src/natlinkcore/configure/natlinkconfigfunctions.py b/src/natlinkcore/configure/natlinkconfigfunctions.py index 202d65c..6e726c5 100644 --- a/src/natlinkcore/configure/natlinkconfigfunctions.py +++ b/src/natlinkcore/configure/natlinkconfigfunctions.py @@ -53,7 +53,13 @@ def do_pip(*args): """ Run a pip command with args. Diagnostic logging.3 + + This one should go in the class below, I think (QH), because that is + self.Config in the natlinkconfig_cli.py (which is called from natlinkconfig_gui.py) + + """ + # todoDoug I_am_elevated = am_elevated() I_want_elevated = want_elevated() print(f'do_pip: I_am_elevated: {I_am_elevated}, I_want_elevated: {I_want_elevated}') From 32fb80307c976c08f465315f063fde8e04ee38c7 Mon Sep 17 00:00:00 2001 From: Quintijn Hoogenboom Date: Fri, 4 Apr 2025 16:45:38 +0200 Subject: [PATCH 17/24] fixed vocola config problem by option "must_exist" in function expand_path of config.py. Reworked some testing and some config functions. --- src/natlinkcore/config.py | 120 +++++++++++------- .../configure/natlinkconfigfunctions.py | 27 ++-- tests/test_config.py | 29 ++++- 3 files changed, 119 insertions(+), 57 deletions(-) diff --git a/src/natlinkcore/config.py b/src/natlinkcore/config.py index fcc35c7..c5bac47 100644 --- a/src/natlinkcore/config.py +++ b/src/natlinkcore/config.py @@ -143,7 +143,7 @@ def from_first_found_file(cls, files: Iterable[str]) -> 'NatlinkConfig': # should not happen, because of DefaultConfig (was InstallTest) raise NoGoodConfigFoundException('No natlink config file found, please run configure natlink program\n\t(***configurenatlink***)') -def expand_path(input_path: str) -> str: +def expand_path(input_dir: str, must_exist: bool = True) -> str: r"""expand path if it starts with "~" or starts with an environment variable or name of python package Paths can be: @@ -154,77 +154,103 @@ def expand_path(input_path: str) -> str: - Obsolete: natlink_userdir/...: instead natlink_settingsdir will be searched for, and a message is thrown. In the config program things are checked more thoroughly. - some other environment variable: this environment variable is expanded + +must_exist: + default: True, the expanded path should exist. + If not (possibly when in natlinkconfigfunctions a directory should be created), pass False + The Documents directory can be found by "~\Documents"... When there is nothing to expand, just return the input """ - expanduser, expandvars, normpath, isdir = os.path.expanduser, os.path.expandvars, os.path.normpath, os.path.isdir + expanduser, expandvars, normpath, isdir, join = os.path.expanduser, os.path.expandvars, os.path.normpath, os.path.isdir, os.path.join - # I think, this is tackled below, input_path is one word, without slashes or ~ or %(...) (QH) + # I think, this is tackled below, input_dir is one word, without slashes or ~ or %(...) (QH) # try: - # package_spec=u.find_spec(input_path) + # package_spec=u.find_spec(input_dir) # if package_spec is not None: # package_path=str(p.Path(package_spec.origin).parent) # return normpath(package_path) # except: # pass - if input_path.startswith('~'): + if input_dir.startswith('~'): home = expanduser('~') - env_expanded = home + input_path[1:] - # print(f'expand_path: "{input_path}" include "~": expanded: "{env_expanded}"') - return normpath(env_expanded) + home_expanded = home + input_dir[1:] + # print(f'expand_path: "{input_dir}" include "~": expanded: "{env_expanded}"') + if must_exist: + assert isdir(home_expanded) + return normpath(home_expanded) + + if isdir(input_dir): + return normpath(input_dir) + + # split in trunk and rest: + if input_dir.find('/') > 0: + trunk, rest = input_dir.split('/', 1) + elif input_dir.find('\\') > 0: + trunk, rest = input_dir.split('\\', 1) + else: + trunk, rest = input_dir, '' ## "natlink_userdir" will go obsolete, to be replaced with "natlink_settingsdir" below: - if input_path.startswith('natlink_userdir/') or input_path.startswith('natlink_userdir\\'): - nud = expand_natlink_settingsdir() - if isdir(nud): - dir_path = input_path.replace('natlink_userdir', nud) - dir_path = normpath(dir_path) - if isdir(dir_path): - return dir_path - print(f'no valid directory found with "natlink_userdir": "{dir_path}"\nbut "natlink_userdir" should be replaced by "natlink_settingsdir" anyway') - return dir_path - print(f'natlink_userdir does not expand to a valid directory: "{nud}"\nbut "natlink_userdir" should be replaced by "natlink_settingsdir" anyway') - return normpath(nud) - - if input_path.startswith('natlink_settingsdir/') or input_path.startswith('natlink_settingsdir\\'): + if trunk == 'natlink_userdir': + trunk = expand_natlink_settingsdir() + if isdir(trunk): + replaced_dir = join(trunk, rest) + if must_exist: + if isdir(replaced_dir): + return normpath(replaced_dir) + else: + print(f'no valid directory found with "natlink_userdir": "{input_dir}"\nbut "natlink_userdir" should be replaced by "natlink_settingsdir" anyway') + return '' + # must exist False: + return replaced_dir + # trunk is NOT a directory: + print(f'no valid directory found with "natlink_userdir": "{trunk}"\nbut "natlink_userdir" should be replaced by "natlink_settingsdir" anyway') + return '' + + if trunk == 'natlink_settingsdir': nsd = expand_natlink_settingsdir() - if isdir(nsd): - dir_path = input_path.replace('natlink_settingsdir', nsd) - dir_path = normpath(dir_path) - if isdir(dir_path): - return dir_path - print(f'no valid directory found with "natlink_settingsdir": "{dir_path}"') - return dir_path - print(f'natlink_settingsdir does not expand to a valid directory: "{nsd}"') - return normpath(nsd) - + if not isdir(nsd): + print(f'natlink_settingsdir does not expand to a valid directory: "{nsd}" (input_dir: "{input_dir}"') + return '' + + replaced_dir = normpath(join(nsd, rest)) + if isdir(replaced_dir): + return replaced_dir + if must_exist: + return '' + return replaced_dir # try if package: - if input_path.find('/') > 0: - package_trunk, rest = input_path.split('/', 1) - elif input_path.find('\\') > 0: - package_trunk, rest = input_path.split('\\', 1) - else: - package_trunk, rest = input_path, '' - # find path for package. not an alternative way without loading the package is to use importlib.util.findspec. + # find path for package. not an alternative way without loading the package is to use importlib.util.findspec. # first check for exclude "C:" as trunk: - if package_trunk and package_trunk[-1] != ":": + if trunk and trunk[-1] != ":": try: - pack = __import__(package_trunk) + pack = __import__(trunk) package_path = pack.__path__[-1] - if rest: - dir_expanded = str(Path(package_path)/rest) - return dir_expanded - return package_path + replaced_dir = join(package_path, rest) + if isdir(replaced_dir): + return replaced_dir + if not must_exist: + return replaced_dir except (ModuleNotFoundError, OSError): pass - env_expanded = expandvars(input_path) - # print(f'env_expanded: "{env_expanded}", from envvar: "{input_path}"') - return normpath(env_expanded) + + # if here: try if there are extenden environment variables to expand: + env_expanded = expandvars(input_dir) + # print(f'env_expanded: "{env_expanded}", from envvar: "{input_dir}"') + env_expanded = normpath(env_expanded) + if isdir(env_expanded): + return env_expanded + + if not must_exist: + return env_expanded + # no valid path, but MUST exist (default) + return '' def expand_natlink_settingsdir(): """Return the location of the natlink config files diff --git a/src/natlinkcore/configure/natlinkconfigfunctions.py b/src/natlinkcore/configure/natlinkconfigfunctions.py index 6e726c5..ce1b7a2 100644 --- a/src/natlinkcore/configure/natlinkconfigfunctions.py +++ b/src/natlinkcore/configure/natlinkconfigfunctions.py @@ -460,7 +460,8 @@ def enable_vocola(self, arg): """enable vocola, by setting arg (prompting if False), and other settings """ vocola_user_dir = self.status.getVocolaUserDirectory() - if vocola_user_dir and isdir(vocola_user_dir): + if self.status.vocolaIsEnabled(): + # if vocola_user_dir and isdir(vocola_user_dir): logging.info(f'VocolaUserDirectory is already defined: "{vocola_user_dir}"\n\tto change, first clear (option "V") and then set again') logging.info('\nWhen you want to upgrade Vocola (vocola2), also first clear ("V"), then choose this option ("v") again.\n') return @@ -468,11 +469,11 @@ def enable_vocola(self, arg): voc_dir = self.status.getVocolaDirectory() if voc_dir: logging.info('==== instal and/or update vocola2====\n') - try: - do_pip("install",*self.extra_pip_options, "--upgrade", "vocola2") - except subprocess.CalledProcessError: - logging.info('====\ncould not pip install --upgrade vocola2\n====\n') - return + # try: + # do_pip("install",*self.extra_pip_options, "--upgrade", "vocola2") + # except subprocess.CalledProcessError: + # logging.info('====\ncould not pip install --upgrade vocola2\n====\n') + # return else: try: do_pip("install",*self.extra_pip_options, "vocola2") @@ -487,10 +488,20 @@ def enable_vocola(self, arg): if not vocola_user_dir: return # vocGrammarsDir = self.status.getVocolaGrammarsDirectory() - vocGrammarsDir = config.expand_path(r'natlink_settingsdir\vocolagrammars') + vocGrammarsDir = config.expand_path(r'natlink_settingsdir\vocolagrammars', must_exist=False) if not vocGrammarsDir: - logging.warning('Could not expand directory for vocola grammars') + logging.error('Could not expand directory for vocola grammars') return + vocGrammarsPath = Path(vocGrammarsDir) + if not vocGrammarsPath.is_dir(): + parent = vocGrammarsPath.parent + if parent.is_dir(): + vocGrammarsPath.mkdir() + assert vocGrammarsPath.is_dir() + else: + logging.error(f'vocolaGrammarsDirectory is not a valid directory: "{vocGrammarsDir}"') + return + self.setDirectory('vocoladirectory','vocola2') #always vocola2 self.setDirectory('vocolagrammarsdirectory', vocGrammarsDir) self.copyUnimacroIncludeFile() diff --git a/tests/test_config.py b/tests/test_config.py index a4eb6e8..8d67988 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -148,6 +148,14 @@ def test_expand_path(mock_syspath,mock_settingsdir): # testing nonexisting package (QH) result=expand_path('nonexisting_package') assert not os.path.isdir(result) + assert result == '' + + # rare cases, must_exist specified, but results in this: + result=expand_path('nonexisting_package', must_exist=False) + assert not os.path.isdir(result) + assert result == 'nonexisting_package' + + # assume FakeGrammars is a valid directory: result = expand_path('natlink_settingsdir/FakeGrammars') @@ -156,13 +164,20 @@ def test_expand_path(mock_syspath,mock_settingsdir): # invalid directory result = expand_path('natlink_settingsdir/invalid_dir') assert not os.path.isdir(result) + assert result == '' + + # invalid directory, must_exist == False: + result = expand_path('natlink_settingsdir/invalid_dir', must_exist=False) + assert not os.path.isdir(result) + assert result.endswith('invalid_dir') # invalid prefix (natlink_settingsdir) result = expand_path('natlink_sssettingsdir/invalid_prefix') assert not os.path.isdir(result) - + assert result == '' + # try package - result = expand_path('natlinkcore') + result = expand_path('natlinkcore') assert os.path.isdir(result) result = expand_path('natlinkcore/DefaultConfig') @@ -177,8 +192,18 @@ def test_expand_path(mock_syspath,mock_settingsdir): result = expand_path('natlinkcore\\NonExisting') assert not os.path.isdir(result) + result = expand_path('natlinkcore\\NonExisting', must_exist=False) + assert not os.path.isdir(result) + assert result.endswith('NonExisting') + + result = expand_path('/natlinkcore') assert not os.path.isdir(result) + assert result == '' + + result = expand_path('/natlinkcore', must_exist=False) + assert not os.path.isdir(result) + assert result == '/natlinkcore' From 0aac009e9184a7cd912a8cbb08442f5b67ed5874 Mon Sep 17 00:00:00 2001 From: Quintijn Hoogenboom Date: Fri, 5 Dec 2025 13:04:40 +0100 Subject: [PATCH 18/24] not sure what happened here. it seems the Natlink.BadWindow is not thrown correctly. Should be investigated in the natlinkutils or natlink tests... --- src/natlinkcore/natlinkutils.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/natlinkcore/natlinkutils.py b/src/natlinkcore/natlinkutils.py index 448fadf..8ac796c 100644 --- a/src/natlinkcore/natlinkutils.py +++ b/src/natlinkcore/natlinkutils.py @@ -750,6 +750,7 @@ def activate(self, ruleName, window=0, exclusive=None, noError=0): return if ruleName in self.activeRules: + print(f'want to activate rule "{ruleName}" for window {window}') if window == self.activeRules[ruleName]: if not noError or debug_print: print(f'rule "{ruleName}" already active for window {window}') @@ -757,7 +758,11 @@ def activate(self, ruleName, window=0, exclusive=None, noError=0): debug_print(f'change rule "{ruleName}" from window {self.activeRules[ruleName]} to {window}') self.gramObj.deactivate(ruleName) # debug_print( print('activate rule %s (window: %s)'% (ruleName, window)) - self.gramObj.activate(ruleName, window) + try: + self.gramObj.activate(ruleName, window) + except natlink.BadWindow: + print(f'Cannot activate rule "{ruleName}" for window {window}, BadWindow') + return self.activeRules[ruleName] = window if not exclusive is None: debug_print(f'set exclusive mode to {exclusive} for rule "{ruleName}"') From e22236fb7c4a8b51fefc86a09365aa5573402806 Mon Sep 17 00:00:00 2001 From: Quintijn Hoogenboom Date: Fri, 20 Mar 2026 14:47:06 +0100 Subject: [PATCH 19/24] toml file; change ~= into >= --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9fda2a0..7ebe144 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,9 +13,9 @@ dependencies= [ "FreeSimpleGUI>=5.1.0", "pydebugstring >= 1.0.0.1", - "dtactions ~= 1.6.4.dev3", + "dtactions >= 1.6.4.dev3", "platformdirs >= 4.2.0", - "natlink ~= 5.5.8.dev2" + "natlink >= 5.5.8.dev2" ] classifiers=[ "Development Status :: 4 - Beta", From fb204ad580bea06c7148a6452302727df82c4af3 Mon Sep 17 00:00:00 2001 From: Quintijn Hoogenboom Date: Thu, 26 Mar 2026 12:19:09 +0100 Subject: [PATCH 20/24] adding check for admin or non admin mode (very simple). add option "f" or "F" to force normal or elevated mode for pip commands pip --pre is prepared, but does not work via the current windows pip command. added documentation for help_f (help_F) and for do_i (give information). --- pyproject.toml | 5 +- .../configure/natlinkconfig_cli.py | 144 ++++++++++++++---- .../configure/natlinkconfigfunctions.py | 108 ++++++------- 3 files changed, 165 insertions(+), 92 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 7ebe144..7931a08 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "flit_core.buildapi" [project] name = "natlinkcore" authors = [{name = "Quintijn Hoogenboom (maintainer)", email = "q.hoogenboom@antenna.nl"}] -version="5.4.2.dev6" +version="5.4.2.dev7" dynamic = [ "description"] requires-python = ">=3.10" readme = "readme.md" @@ -15,7 +15,8 @@ dependencies= [ "pydebugstring >= 1.0.0.1", "dtactions >= 1.6.4.dev3", "platformdirs >= 4.2.0", - "natlink >= 5.5.8.dev2" + "natlink >= 5.5.7" + # "natlink >= 5.5.8.dev2" ] classifiers=[ "Development Status :: 4 - Beta", diff --git a/src/natlinkcore/configure/natlinkconfig_cli.py b/src/natlinkcore/configure/natlinkconfig_cli.py index 646d4e9..1e58551 100644 --- a/src/natlinkcore/configure/natlinkconfig_cli.py +++ b/src/natlinkcore/configure/natlinkconfig_cli.py @@ -1,4 +1,4 @@ -#pylint:disable=R0904 +#pylint:disable=R0904, C0415, W1203 import sys import getopt import cmd @@ -6,17 +6,16 @@ import os.path import logging import configparser +# from argparse import ArgumentParser from pathlib import Path +import pyuac from natlinkcore.configure import extensions_and_folders from natlinkcore.configure import natlinkconfigfunctions from platformdirs import user_log_dir -from argparse import ArgumentParser - -extra_pip_options=[] # packages that should go with the do_p or do_P command, upgrading with pip # is packages is already there: todoDoug -packages_to_pip = ['natlinkcore', 'dragonfly' 'unimacro', 'vocola2', 'caster'] +packages_to_pip = ['natlinkcore', 'dragonfly', 'unimacro', 'vocola2', 'caster'] appname="natlink" logdir = Path(user_log_dir(appname=appname,ensure_exists=True)) logfilename=logdir/"cli_log.txt" @@ -46,7 +45,7 @@ def _main(Options=None): """ - shortOptions = "DVNOHKaAiIxXbBuqepP" + shortOptions = "DVNOHKaAiIxXbBuqepPfF" shortArgOptions = "d:v:n:o:h:k:" if Options: if isinstance(Options, str): @@ -59,18 +58,18 @@ def _main(Options=None): options, args = getopt.getopt(Options, shortOptions+shortArgOptions,["pre"]) except getopt.GetoptError: print(f'invalid option: {Options}') - cli.usage() + # cli.usage() return if args: print(f'should not have extraneous arguments: {args}') + extra_pip_options = [] if "--pre" in options: extra_pip_options.append("--pre") logging.info("pip extra option --pre") options.remove("--pre") cli = CLI(extra_pip_options=extra_pip_options) - cli.Config = natlinkconfigfunctions.NatlinkConfig(cli.extra_pip_options) for o, v in options: @@ -97,10 +96,12 @@ class CLI(cmd.Cmd): def __init__(self, Config=None,extra_pip_options=None): cmd.Cmd.__init__(self) self.extra_pip_options=extra_pip_options or [] + self.pip_force_elevated = None # command f or F self.prompt = '\nNatlink config> ' self.info = "type 'u' for usage" self.Config = None self.message = '' + self.accept_pip_commands = False # if __name__ == "__main__": # print("Type 'u' for usage ") @@ -160,6 +161,10 @@ def usage(self): d/D - enable/disable the DragonflyDirectory, the directory where you can put your Dragonfly scripts (UserDirectory can also be used) +[Install/configure] +f/F - Force elevated mode for installing packages (F), or force normal mode (f). + + [UserDirectory] n/N - enable/disable UserDirectory, the directory where User Natlink grammar files are located (e.g., "~\UserDirectory") @@ -180,9 +185,14 @@ def usage(self): # info---------------------------------------------------------- def do_i(self, arg): - self.Config.status.__init__() + # self.Config.status() try: S = self.Config.status.getNatlinkStatusString() + if self.pip_force_elevated is not None: + if self.pip_force_elevated: + S = S + '\n-- Pip commands will need User Admin mode (elevated)' + else: + S = S + '\n-- Pip commands will need normal mode (non elevated)' S = S + '\n\nIf you changed things, you must restart Dragon' print(S) except configparser.NoSectionError: @@ -190,8 +200,9 @@ def do_i(self, arg): def do_I(self, arg): # inifile natlinkstatus.ini settings: - self.Config.status.__init__() + self.Config.status() self.Config.openConfigFile() + def do_j(self, arg): # print PythonPath: @@ -204,7 +215,14 @@ def do_p(self, arg): Other packages (dtactions) should be in the dependencies of above. + Check if elevated mode is required and prompt the user to restart in + the correct mode if necessary. + """ + result = self.check_elevated_mode() + if not result: + return + do_pre = arg == '--pre' or '' ## todoDoug for package in packages_to_pip: ## global variable try: @@ -212,8 +230,8 @@ def do_p(self, arg): except ImportError: print(f'package not installed: "{package}", do not upgrade') continue - - self.Config.do_pip(package, do_pre) + params = [] + self.Config.pip_package(package, *params, do_pre) def do_P(self, arg): """Upgrade pip packags with --pre mode on @@ -225,14 +243,88 @@ def do_P(self, arg): """ self.do_p("--pre") + def check_elevated_mode(self): + """Check if you are in correct mode, for doing pip commands + """ + if self.accept_pip_commands is not None: + return self.accept_pip_commands + if self.pip_force_elevated is not None: + self.accept_pip_commands = self.pip_force_elevated + return self.accept_pip_commands + + I_am_elevated = self.am_elevated() + I_want_elevated = self.want_elevated() + print(f'check_elevated_mode: I_am_elevated: {I_am_elevated}, I_want_elevated: {I_want_elevated}') + if I_am_elevated is I_want_elevated: + self.accept_pip_commands = True + return True + if I_want_elevated: + message = '\n'.join(["You need elevated mode for this function.", + "Please quit this process and restart the config program in elevated mode.", + "", + "The checking can be wrong.", + 'If you still want to proceed in this process, use the command "F" to do so']) + print(message) + return False + + # need non elevated, normal mode + message = '\n'.join(["You do not need elevated mode for this function.", + "Please quit this process and restart the config program in normal mode.", + "", + 'The checking can be wrong.', + 'If you want to proceed in this process, use the command "f" to do so']) + print(message) + return False + + + + + def am_elevated(self): + return pyuac.isUserAdmin() + def want_elevated(self): + r"""check if sys.executable has "\Users" in its path, then not elevated. + + """ + exestr = sys.executable + return exestr.lower().lower().find(r'\users') == -1 + + def do_f(self, arg): + """normal mode for pip commands + only needed if the default check does not give the wanted result + """ + self.pip_force_elevated = False + + def do_F(self, arg): + self.pip_force_elevated = True + + def help_f(self): + print('-'*60) + T = ["Normally the configure program (GUI or CLI) decides correct", + "whether you need elevated mode or not.", + 'Non elevated when Python is installed in your user area.', '', + r'The check is whether the path of "python.exe" starts with "\Users" (case insensitive).', + '(If so: normal (non elevated) mode.)', '', + 'Only if this check does not give the correct result,', + 'you will need the options "f" or "F", for overriding above check.', + 'Do this command before you install (or update) pip modules,', + 'for example when you configure one of the packages,', + 'like Dragonfly, Vocola or Unimacro.', '', + 'So: option "f" for non-elevated mode,', + 'and option "F" for elevated (User Admin) mode.'] + print('\n'.join(T)) + + help_F = help_f + def help_p(self): print(''*60) print("""The commands "p" and "P" will pip upgrade the installed packages, -like natlinkcore, vocola2, unimacro, dragonfly. +like natlinkcore, dragonfly, vocola2, unimacro. Other modules will follow as dependencies. (lower case) "p" will take only major releases, (upper case) "P" will also take so called "pre" releases. - + +If you need elevated mode, and are not, of vice versa, the program will prompt you +to start in the correct mode. """) def do_e(self,arg): @@ -313,20 +405,7 @@ def help_o(self): help_O = help_o - # Unimacro Command Files Editor----------------------------------------------- - # not needed for Aaron's GUI: - # def do_p(self, arg): - # self.message = "Set Unimacro INI file editor" - # print(f'do action: {self.message}') - # key = "UnimacroIniFilesEditor" - # self.Config.setFile(key, arg, section='unimacro') - # - # def do_P(self, arg): - # self.message = "Clear Unimacro INI file editor, go back to default Notepad" - # print(f'do action: {self.message}') - # key = "UnimacroIniFilesEditor" - # self.Config.clearFile(key, section='unimacro') - + ### possibly in future re-wanted functions: # Unimacro Vocola features----------------------------------------------- # managing the include file wrapper business. # can be called from the Vocola compatibility button in the config GUI. @@ -521,11 +600,16 @@ def help_u(self): def main_cli(): #a hack until we switch to ArgParse from getopt, check for --pre + extra_pip_options = [] if len(sys.argv) == 2 and sys.argv[1]=="--pre": extra_pip_options.append("--pre") - if len(sys.argv) == 1 or ( len(sys.argv) == 2 and sys.argv[1]=="--pre"): - Cli = CLI(extra_pip_options=extra_pip_options) + if len(sys.argv) == 1 or ( len(sys.argv) == 2 and sys.argv[1]=="--pre"): + if extra_pip_options: + Cli = CLI(extra_pip_options=extra_pip_options) + else: + Cli = CLI() + Cli.Config = natlinkconfigfunctions.NatlinkConfig(extra_pip_options=extra_pip_options) Cli.info = "" print('\nWelcome to the NatlinkConfig Command Line Interface\n') diff --git a/src/natlinkcore/configure/natlinkconfigfunctions.py b/src/natlinkcore/configure/natlinkconfigfunctions.py index ce1b7a2..7d9b0c2 100644 --- a/src/natlinkcore/configure/natlinkconfigfunctions.py +++ b/src/natlinkcore/configure/natlinkconfigfunctions.py @@ -24,7 +24,6 @@ from pathlib import Path import configparser import logging -import pyuac try: from natlinkcore import natlinkstatus except OSError: @@ -36,53 +35,10 @@ isfile, isdir, join = os.path.isfile, os.path.isdir, os.path.join -def am_elevated(): - """return True is state is elevated - """ - pass # todoDoug - return False - -def want_elevated(): - """elevated mode is wanted? - If sys.executable does not start with the home directory I would think - """ - pass # todoDoug - return False - -def do_pip(*args): - """ - Run a pip command with args. - Diagnostic logging.3 - - This one should go in the class below, I think (QH), because that is - self.Config in the natlinkconfig_cli.py (which is called from natlinkconfig_gui.py) - - - """ - # todoDoug - I_am_elevated = am_elevated() - I_want_elevated = want_elevated() - print(f'do_pip: I_am_elevated: {I_am_elevated}, I_want_elevated: {I_want_elevated}') - # run pip in elevated mode: - if not pyuac.isUserAdmin(): - # print('If you want to pip upgrade packages, a new "elevated" process is started. Please answer Y in that case') - # print('Please run this program in "elevated" mode, when you want to pip upgrade packages') - # return - pyuac.runAsAdmin() - - if pyuac.isUserAdmin(): - print('continue in Admin (elevated) mode...') - - command = [sys.executable,"-m", "pip"] + list(args) - logging.info(f"command: {command} ") - completed_process=subprocess.run(command,capture_output=True) - logging.debug(f"completed_process: {completed_process}") - completed_process.check_returncode() - class NatlinkConfig: """performs the configuration tasks of Natlink - setting UserDirectory, UnimacroDirectory and options, VocolaDirectory and options, + setting UserDirectory, UnimacroDirectory and options, VocolaDirectory and options, DragonflyDirectory, Autohotkey options (ahk), and Debug option of Natlink. and also clearing the different directories. @@ -90,7 +46,7 @@ class NatlinkConfig: """ def __init__(self,extra_pip_options=None): self.extra_pip_options = [] if extra_pip_options is None else extra_pip_options - + self.do_pip_with_pre = '--pre' in self.extra_pip_options self.config_path = self.get_check_config_locations() self.config_dir = str(Path(self.config_path).parent) self.status = natlinkstatus.NatlinkStatus() @@ -120,7 +76,7 @@ def check_config(self): """ # ensure the [directories] section is present: try: - sect = self.Config['directories'] + self.Config['directories'] except KeyError: self.Config.add_section('directories') self.config_write() @@ -373,6 +329,30 @@ def disableDebugOutput(self): if old_value: self.config_set('settings', key, old_value) self.config_set('settings', key, 'INFO') + + def pip_package(self, package, params, do_pre=None): + """ + Run a pip command with possible pre option + Diagnostic logging.3 + + """ + # this check is in the natlinkconfig_cli: + # result = self.check_elevated_mode() + # print(f'result of check_elevated_mode: {result}') + # if not result: + # return + command = [sys.executable,"-m", "pip"] + command.append('install') + command.append(package) + ## premature option::: + # if do_pre or self.do_pip_with_pre: + # command.append("--pre") + command.extend(params) + logging.info(f' pip command: {command} ') + completed_process = subprocess.run(command,capture_output=True) + logging.debug(f' completed_process: {completed_process}') + completed_process.check_returncode() + def enable_unimacro(self, arg): unimacro_user_dir = self.status.getUnimacroUserDirectory() @@ -383,15 +363,17 @@ def enable_unimacro(self, arg): uni_dir = self.status.getUnimacroDirectory() if uni_dir: - logging.info('==== instal and/or update unimacro====\n') + logging.info('==== instal and/or update unimacro====\n') + params = ["--upgrade"] try: - do_pip("install", *self.extra_pip_options, "--upgrade", "unimacro") + self.pip_package("unimacro", params, self.do_pip_with_pre) except subprocess.CalledProcessError: logging.info('====\ncould not pip install --upgrade unimacro\n====\n') return else: + params = [] try: - do_pip("install",*self.extra_pip_options, "unimacro") + self.pip_package("unimacro", params, self.do_pip_with_pre) except subprocess.CalledProcessError: logging.info('====\ncould not pip install unimacro\n====\n') return @@ -429,14 +411,16 @@ def enable_dragonfly(self, arg): df_dir = self.status.getDragonflyDirectory() if df_dir: logging.info('==== instal and/or update dragonfly2====\n') + params = ["--upgrade"] try: - do_pip( "install", *self.extra_pip_options,"--upgrade", "dragonfly2") - except subprocess.CalledProcessError: + self.pip_package("dragonfly2", params, self.do_pip_with_pre) + except subprocess.CalledProcessError: logging.info('====\ncould not pip install --upgrade dragonfly2\n====\n') return else: + params = [] try: - do_pip( "install", *self.extra_pip_options, "dragonfly2") + self.pip_package("dragonfly2",params, self.do_pip_with_pre) except subprocess.CalledProcessError: logging.info('====\ncould not pip install dragonfly2\n====\n') return @@ -468,15 +452,18 @@ def enable_vocola(self, arg): voc_dir = self.status.getVocolaDirectory() if voc_dir: - logging.info('==== instal and/or update vocola2====\n') - # try: - # do_pip("install",*self.extra_pip_options, "--upgrade", "vocola2") - # except subprocess.CalledProcessError: - # logging.info('====\ncould not pip install --upgrade vocola2\n====\n') - # return + logging.info('==== install --update vocola2====\n') + params = ["--upgrade"] + try: + self.pip_package("vocola2", params, self.do_pip_with_pre) + except subprocess.CalledProcessError: + logging.info('====\ncould not pip install --upgrade vocola2\n====\n') + return else: + logging.info('==== install vocola2====\n') + params = [] try: - do_pip("install",*self.extra_pip_options, "vocola2") + self.pip_package("vocola2",params, self.do_pip_with_pre) except subprocess.CalledProcessError: logging.info('====\ncould not pip install vocola2\n====\n') return @@ -770,6 +757,7 @@ def printPythonPath(self): logging.info(pformat(sys.path)) + def isValidDir(path): """return the path, as str, if valid directory From 7e58e6f4d889736b0bfb9e0fecd075bda7207be4 Mon Sep 17 00:00:00 2001 From: Quintijn Hoogenboom Date: Thu, 9 Apr 2026 13:32:31 +0200 Subject: [PATCH 21/24] new: test_natlinkconfig.py, testing am_elevated and want_elevated functions, together with forcing (do_f and do_F) --- .../configure/natlinkconfig_cli.py | 104 +++++++++---- .../configure/natlinkconfigfunctions.py | 11 +- tests/test_callbackhandler.py | 2 +- tests/test_natlinkconfig.py | 143 ++++++++++++++++++ 4 files changed, 222 insertions(+), 38 deletions(-) create mode 100644 tests/test_natlinkconfig.py diff --git a/src/natlinkcore/configure/natlinkconfig_cli.py b/src/natlinkcore/configure/natlinkconfig_cli.py index 1e58551..fb7cd27 100644 --- a/src/natlinkcore/configure/natlinkconfig_cli.py +++ b/src/natlinkcore/configure/natlinkconfig_cli.py @@ -15,7 +15,7 @@ # packages that should go with the do_p or do_P command, upgrading with pip # is packages is already there: todoDoug -packages_to_pip = ['natlinkcore', 'dragonfly', 'unimacro', 'vocola2', 'caster'] +packages_to_pip = ['natlinkcore', 'dragonfly', 'unimacro', 'vocola2'] ## 'caster' wanted here??] appname="natlink" logdir = Path(user_log_dir(appname=appname,ensure_exists=True)) logfilename=logdir/"cli_log.txt" @@ -87,7 +87,7 @@ def _main(Options=None): else: print('options should not come here') cli.usage() - + return cli class CLI(cmd.Cmd): @@ -101,7 +101,7 @@ def __init__(self, Config=None,extra_pip_options=None): self.info = "type 'u' for usage" self.Config = None self.message = '' - self.accept_pip_commands = False + self.accept_pip_commands = None # if __name__ == "__main__": # print("Type 'u' for usage ") @@ -209,11 +209,11 @@ def do_j(self, arg): self.Config.printPythonPath() def do_p(self, arg): - """do pip according to runtime options or NOT in --pre mode + """do pip packages + + Pass as parameter one of natlinkcore, dragonfly, unimacro, vocola2 or dtactions, - upgrade all installed packages of the list natlinkcore, dragonfly, unimacro vocola2. - - Other packages (dtactions) should be in the dependencies of above. + or "all" to do them all. Check if elevated mode is required and prompt the user to restart in the correct mode if necessary. @@ -221,58 +221,87 @@ def do_p(self, arg): """ result = self.check_elevated_mode() if not result: - return - - do_pre = arg == '--pre' or '' ## todoDoug - for package in packages_to_pip: ## global variable - try: - import package - except ImportError: - print(f'package not installed: "{package}", do not upgrade') - continue - params = [] - self.Config.pip_package(package, *params, do_pre) - - def do_P(self, arg): - """Upgrade pip packags with --pre mode on + return False + # packages_to_pip is global, see at top of thist file. + if not arg: + print(f'Please pass one of the packages {packages_to_pip}, or "all" to do them all.') + return False - upgrade all installed packages of the list natlinkcore, dragonfly, unimacro vocola2. + wanted = arg.lower() + if wanted == "all": + packageslist = packages_to_pip + elif wanted in packages_to_pip: + packageslist = [wanted] + else: + print(f'Please pass one of the packages {packages_to_pip}, or "all" to do them all.') + print(f'You passed: {arg}') + return False - Other packages (dtactions) should be in the dependencies of above. + for package in packageslist: ## global variable + logging.info(f'==== instal with --update {package} ===\n') + params = ["--upgrade"] + # self.config.do_pip_with_pre set to False for the moment. Not implemented + result = self.Config.pip_package(package, params, False) + if result: + logging.info(f' ===\nsuccesfully pip installed with --upgrade: {package}\n====\n') + else: + logging.info(f' ===\nCould not pip install --upgrade: {package}\n====\n') + return result + def do_P(self, arg): + """Upgrade pip packags with --pre mode on + + Not implemented here, because --pre does not not exist as a pip option. + Send a message instead... + """ - self.do_p("--pre") + L = ['The command "P", pip with development updates, does not work as was expected.','', + 'If you need this option, go to "pypi.org", find your package,' + 'click on Releases, and on the wanted "pre" release.', '', + 'You will then find the pip command, which you', + 'can paste into "Windows Powershell" or "Command Prompt".', '' + ] + print('\n'.join(L)) + if self.want_elevated(): + print('Please start one of these programs in Elevated mode ("Run as Administrator"') + else: + print('You can start one of these programs in Normal mode (NOT as Administator)') + # self.do_p("--pre") def check_elevated_mode(self): """Check if you are in correct mode, for doing pip commands """ - if self.accept_pip_commands is not None: - return self.accept_pip_commands if self.pip_force_elevated is not None: - self.accept_pip_commands = self.pip_force_elevated + # set to True of False by the "f" or "F" command... + self.accept_pip_commands = None + if self.accept_pip_commands is not None: return self.accept_pip_commands + + - I_am_elevated = self.am_elevated() + I_am_elevated = self.am_elevated() I_want_elevated = self.want_elevated() print(f'check_elevated_mode: I_am_elevated: {I_am_elevated}, I_want_elevated: {I_want_elevated}') if I_am_elevated is I_want_elevated: self.accept_pip_commands = True return True + # TODO QH + self.accept_pip_commands = False if I_want_elevated: message = '\n'.join(["You need elevated mode for this function.", "Please quit this process and restart the config program in elevated mode.", "", "The checking can be wrong.", - 'If you still want to proceed in this process, use the command "F" to do so']) + 'If you still want to proceed in this process (normal mode), use the command "f" to do so']) print(message) return False # need non elevated, normal mode - message = '\n'.join(["You do not need elevated mode for this function.", + message = '\n'.join(["You do NOT need elevated mode for this function.", "Please quit this process and restart the config program in normal mode.", "", 'The checking can be wrong.', - 'If you want to proceed in this process, use the command "f" to do so']) + 'If you want to proceed in this "elevated" process, use the command "F" to do so']) print(message) return False @@ -285,16 +314,25 @@ def want_elevated(self): r"""check if sys.executable has "\Users" in its path, then not elevated. """ + if self.pip_force_elevated in (True, False): + # option "f" or "F" has been used: + return self.pip_force_elevated exestr = sys.executable - return exestr.lower().lower().find(r'\users') == -1 + return exestr.lower().find(r'\users') == -1 + def do_f(self, arg): """normal mode for pip commands only needed if the default check does not give the wanted result """ + self.accept_pip_commands = None self.pip_force_elevated = False def do_F(self, arg): + """want elevated mode for pip commands + only needed if the default check does not give the wanted result + """ + self.accept_pip_commands = None self.pip_force_elevated = True def help_f(self): @@ -321,7 +359,7 @@ def help_p(self): like natlinkcore, dragonfly, vocola2, unimacro. Other modules will follow as dependencies. (lower case) "p" will take only major releases, -(upper case) "P" will also take so called "pre" releases. +(upper case) "P" will also take so called "pre" releases. This function does NOT WORK at the moment. If you need elevated mode, and are not, of vice versa, the program will prompt you to start in the correct mode. diff --git a/src/natlinkcore/configure/natlinkconfigfunctions.py b/src/natlinkcore/configure/natlinkconfigfunctions.py index 7d9b0c2..eb002ff 100644 --- a/src/natlinkcore/configure/natlinkconfigfunctions.py +++ b/src/natlinkcore/configure/natlinkconfigfunctions.py @@ -8,7 +8,7 @@ # Quintijn Hoogenboom, January 2008 (...), August 2022 # -#pylint:disable=C0302, W0702, R0904, C0116, W0613, R0914, R0912, R1732, W1514, W0107, W1203 +#pylint:disable=C0302, W0702, R0904, C0116, W0613, R0914, R0912, R1732, W1514, W0107, W1203, W1309 """With the functions in this module Natlink can be configured. These functions are called in different ways: @@ -369,25 +369,28 @@ def enable_unimacro(self, arg): self.pip_package("unimacro", params, self.do_pip_with_pre) except subprocess.CalledProcessError: logging.info('====\ncould not pip install --upgrade unimacro\n====\n') - return + return False else: params = [] try: self.pip_package("unimacro", params, self.do_pip_with_pre) except subprocess.CalledProcessError: logging.info('====\ncould not pip install unimacro\n====\n') - return + return False self.status.refresh() # refresh status uni_dir = self.status.getUnimacroDirectory() self.setDirectory('UnimacroUserDirectory', arg, section='unimacro') unimacro_user_dir = self.config_get('unimacro', 'unimacrouserdirectory') if not unimacro_user_dir: - return + logging.warning(f' strange error, installing unimacro seemed to word,' + ' but cannot find unimacro_user_dir in the inifile "{natlink.ini}"') + return False uniGrammarsDir = r'unimacro\unimacrogrammars' self.setDirectory('unimacrodirectory','unimacro') #always unimacro self.setDirectory('unimacrogrammarsdirectory', uniGrammarsDir) + return True def disable_unimacro(self, arg=None): """disable unimacro, do not expect arg diff --git a/tests/test_callbackhandler.py b/tests/test_callbackhandler.py index f5b4549..1beba2c 100644 --- a/tests/test_callbackhandler.py +++ b/tests/test_callbackhandler.py @@ -1,4 +1,4 @@ -#pylint:disable= C0114, C0115, C0116, W0401, W0614, W0621, W0108. W0212, R0201 +#pylint:disable= C0114, C0115, C0116, W0401, W0614, W0621, W0108. W0212 import pytest diff --git a/tests/test_natlinkconfig.py b/tests/test_natlinkconfig.py new file mode 100644 index 0000000..1365796 --- /dev/null +++ b/tests/test_natlinkconfig.py @@ -0,0 +1,143 @@ +#pylint:disable= C0114, C0116, W0401, W0614, W0621, W0108, W0212, C3001,C0413 + +from pathlib import Path +import sys +import os +import sysconfig +import pytest + +thisDir = Path(__file__).parent +configDir = os.path.normpath(thisDir/'../src/natlinkcore/configure') +sys.path.append(configDir) +print(f'sys.path: {sys.path}') + +import natlinkconfig_cli +import natlinkconfigfunctions + +@pytest.fixture +def cli(): + """return the (non interactive) cli + """ + _cli = natlinkconfig_cli._main() + return _cli + + +def test_run_natlinkconfig_cli(): + _nc = natlinkconfigfunctions.NatlinkConfig() + _config = _nc.Config + _doc_path = _nc.documents_path + _home_path = _nc.home_path + _natlinkconfig_path = _nc.natlinkconfig_path + print(f'natlinkconfig_path: {_natlinkconfig_path}') + _cli = natlinkconfig_cli._main() + print(f'natlinkconfig_cli: {_cli}') + + pass + +def test_config_cli(cli, capsys): + """test the basics of the natlinkconfig_cli program + """ + cli.do_i('dummy') + output = capsys.readouterr().out.rstrip() + + assert output.find('NatlinkDirectory') > 0 + +def test_check_elevated_mode_tt(cli, monkeypatch): + """try the variants of am_elevated and want_elevated + result True + """ + def return_true(): + return True + def return_false(): + return False + monkeypatch.setattr(cli, 'am_elevated', return_true) + monkeypatch.setattr(cli, 'want_elevated', return_true) + result = cli.check_elevated_mode() + assert result is True + +def test_check_elevated_mode_ff(cli, monkeypatch): + """try the variants of am_elevated and want_elevated + result True + """ + def return_true(): + return True + def return_false(): + return False + monkeypatch.setattr(cli, 'am_elevated', return_false) + monkeypatch.setattr(cli, 'want_elevated', return_false) + result = cli.check_elevated_mode() + assert result is True + +def test_check_elevated_mode_tf(cli, monkeypatch): + """try the variants of am_elevated and want_elevated + result False + """ + def return_true(): + return True + def return_false(): + return False + monkeypatch.setattr(cli, 'am_elevated', return_true) + monkeypatch.setattr(cli, 'want_elevated', return_false) + result = cli.check_elevated_mode() + assert result is False + +def test_check_elevated_mode_ft(cli, monkeypatch): + """try the variants of am_elevated and want_elevated + result False + """ + def return_true(): + return True + def return_false(): + return False + monkeypatch.setattr(cli, 'am_elevated', return_false) + monkeypatch.setattr(cli, 'want_elevated', return_true) + result = cli.check_elevated_mode() + assert result is False + +def test_check_elevated_mode_with_do_F(cli, monkeypatch): + """try the variants of am_elevated overridden by do_F + result is then True (after first a False) + """ + def return_true(): + return True + def return_false(): + return False + monkeypatch.setattr(cli, 'am_elevated', return_false) + monkeypatch.setattr(cli, 'want_elevated', return_true) + result = cli.check_elevated_mode() + assert result is False + # now force accepting am_elevated: + cli.do_F('dummy') + monkeypatch.setattr(cli, 'am_elevated', return_true) + result = cli.check_elevated_mode() + assert result is True + +def test_check_elevated_mode_with_do_f(cli, monkeypatch): + """try the variants of am_elevated overridden by do_F + result is then True (after first a False) + """ + def return_true(): + return True + def return_false(): + return False + monkeypatch.setattr(cli, 'am_elevated', return_true) + monkeypatch.setattr(cli, 'want_elevated', return_false) + result = cli.check_elevated_mode() + assert result is False + # now force accepting am_elevated to false, non elevated + cli.do_f('dummy') + monkeypatch.setattr(cli, 'am_elevated', return_false) + result = cli.check_elevated_mode() + assert result is True + + + +def _main(): + """run pytest for this module + """ + pytest.main(['test_natlinkconfig.py']) + + +if __name__ == "__main__": + _main() + From 9a5307673190a83657dcdbce5bc68c5dd81e88a8 Mon Sep 17 00:00:00 2001 From: Quintijn Hoogenboom Date: Thu, 9 Apr 2026 19:34:22 +0200 Subject: [PATCH 22/24] detail in explanatory text of cli program --- src/natlinkcore/configure/natlinkconfig_cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/natlinkcore/configure/natlinkconfig_cli.py b/src/natlinkcore/configure/natlinkconfig_cli.py index fb7cd27..724cdf6 100644 --- a/src/natlinkcore/configure/natlinkconfig_cli.py +++ b/src/natlinkcore/configure/natlinkconfig_cli.py @@ -162,7 +162,7 @@ def usage(self): you can put your Dragonfly scripts (UserDirectory can also be used) [Install/configure] -f/F - Force elevated mode for installing packages (F), or force normal mode (f). +f/F - Force (thinking this process is) in elevated mode for installing packages (F), or force normal mode (f). [UserDirectory] From 7dab8ff247c8cd4de56ad571ea92002db53b5c9a Mon Sep 17 00:00:00 2001 From: Quintijn Hoogenboom Date: Fri, 17 Apr 2026 10:08:37 +0200 Subject: [PATCH 23/24] tagged and goto release 5.4.2 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7931a08..1eeaca1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "flit_core.buildapi" [project] name = "natlinkcore" authors = [{name = "Quintijn Hoogenboom (maintainer)", email = "q.hoogenboom@antenna.nl"}] -version="5.4.2.dev7" +version="5.4.2" dynamic = [ "description"] requires-python = ">=3.10" readme = "readme.md" From 7bcddf574c3703be04b4a8cb175807bb776f1a7d Mon Sep 17 00:00:00 2001 From: Quintijn Hoogenboom Date: Sun, 19 Apr 2026 11:19:36 +0200 Subject: [PATCH 24/24] try to fix AttributeError of setMessagesWindow --- pyproject.toml | 2 +- src/natlinkcore/loader.py | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 1eeaca1..93f8d04 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "flit_core.buildapi" [project] name = "natlinkcore" authors = [{name = "Quintijn Hoogenboom (maintainer)", email = "q.hoogenboom@antenna.nl"}] -version="5.4.2" +version="5.4.3" dynamic = [ "description"] requires-python = ">=3.10" readme = "readme.md" diff --git a/src/natlinkcore/loader.py b/src/natlinkcore/loader.py index 2b0f4ec..5be9e36 100644 --- a/src/natlinkcore/loader.py +++ b/src/natlinkcore/loader.py @@ -561,11 +561,18 @@ def start(self) -> None: self.trigger_load() natlink.setBeginCallback(self.on_begin_callback) natlink.setChangeCallback(self.on_change_callback) - natlink.setMessageWindow(self.on_message_window_callback) + try: + natlink.setMessageWindow(self.on_message_window_callback) + except AttributeError: + pass + def finish(self) -> None: # reverse changes made by start() - natlink.setMessageWindow(None) + try: + natlink.setMessageWindow(None) + except AttributeError: + pass natlink.setChangeCallback(None) natlink.setBeginCallback(None) self.unload_all_loaded_modules()