From 4ef3195765ff4321ffae86d5041145fd503ec5b7 Mon Sep 17 00:00:00 2001 From: Doug Ransom Date: Tue, 30 Jul 2024 08:18:13 -0700 Subject: [PATCH] Squash last 4 --- .../DefaultConfig/_config_instructions.py | 82 +++++++----- src/natlinkcore/config.py | 67 +++++----- .../configure/natlinkconfig_cli.py | 11 +- .../configure/natlinkconfigfunctions.py | 119 +++++++++++------- src/natlinkcore/loader.py | 4 +- src/natlinkcore/natlinkstatus.py | 26 ++-- src/natlinkcore/readwritefile.py | 5 +- tests/mock_readwritefile/cp1252.txt | 2 +- .../{latin1 accented.txt => latin1.txt} | 0 tests/test_config.py | 14 +-- tests/test_readwritefile.py | 82 ++++++++++-- 11 files changed, 270 insertions(+), 142 deletions(-) rename tests/mock_readwritefile/{latin1 accented.txt => latin1.txt} (100%) diff --git a/src/natlinkcore/DefaultConfig/_config_instructions.py b/src/natlinkcore/DefaultConfig/_config_instructions.py index 1aa564e..3dbf035 100644 --- a/src/natlinkcore/DefaultConfig/_config_instructions.py +++ b/src/natlinkcore/DefaultConfig/_config_instructions.py @@ -4,30 +4,54 @@ """ import os import os.path -from natlinkcore import getThisDir +from natlinkcore import config -join, expanduser, getenv = os.path.join, os.path.expanduser, os.getenv +join, expanduser, getenv, normpath = os.path.join, os.path.expanduser, os.getenv, os.path.normpath isfile, isdir = os.path.isfile, os.path.isdir home = expanduser('~') -thisFile = __file__ -_this_dir, this_filename = __file__.rsplit('\\', 1) -thisDir = getThisDir(__file__) -if _this_dir != thisDir: - print(f'Working with symlinks! Point to the sitepackages directory: "{_this_dir}", but edit in "{thisDir}"') -this_dir = thisDir +this_dir, this_filename = __file__.rsplit('\\', 1) +if this_dir.find('\\site-packages\\') == -1: + print(f'Working with symlinks! Working from directory: "{this_dir}"\n') + +natlink_settingsdir = getenv("NATLINK_SETTINGSDIR") +natlink_userdir = getenv("NATLINK_USERDIR") + +if natlink_settingsdir: + natlink_settingsdir = normpath(natlink_settingsdir) + natlink_settings_path = config.expand_path(natlink_settingsdir) + if os.path.isdir(natlink_settings_path): + if natlink_settingsdir == natlink_settings_path: + print(f'You specified for NATLINK_SETTINGSDIR: "{natlink_settingsdir}"') + else: + print(f'You specified for NATLINK_SETTINGSDIR: "{natlink_settingsdir}", which expands to: "{natlink_settings_path}".') + +if natlink_userdir: + natlink_user_path = config.expand_path(natlink_userdir) + + if natlink_settingsdir: + print(f'WARNING: You also specified NATLINK_USERDIR to "{natlink_userdir}", this setting is ignored.') + else: + print(f'WARNING: The setting of environment variable NATLINK_USERDIR (to "{natlink_userdir}") is obsolete,') + print('please change this setting to NATLINK_SETTINGSDIR') + + + + print(f'\n\n' - f'\nThis is the file "{this_filename}" from directory "{this_dir}"' - f'\nThis directory holds the default "natlink.ini" file, when it is not found the default or configured directory.' + f'\nThis is the file "{this_filename}" from directory "{this_dir}".' + f'\nThis directory holds the default "natlink.ini" file, when it is not found in the default or configured directory.' f'\n\n' rf'The default directory is: "~\.natlink", with "~" being your HOME directory: "{home}".' f'\tSo: "{home}\\.natlink"' f"\n" f'\nThere is also a custom way to configure the directory of your "natlink.ini" file:' f'\n' - f'\nSpecify the environment variable "NATLINK_USERDIR", which should point to an existing directory.' + f'\nSpecify the environment variable "NATLINK_SETTINGSDIR", which should point to an existing directory.' + f'\nthat ends with ".natlink".' + f'\n' f'\nNote: this directory may NOT be a shared directory, like Dropbox or OneDrive.' - f'\nseveral directories to be configured may be shared however, but others must be local, which is hopefully ' + f'\nSeveral directories to be configured may be shared however, but others must be local, which is hopefully ' f'\nensured well enough in the config program.' f'\n' f'\nWhen this directory does not hold the file "natlink.ini",\nyou can copy it from "{this_dir}",' @@ -38,27 +62,23 @@ f'\n\tthe additional options in order to get started with using Natlink.' f'\n' ) -natlink_userdir = getenv("NATLINK_USERDIR") -ysfnu=f'You specified "NATLINK_USERDIR" to "{natlink_userdir}"' -msg="" -if natlink_userdir: - if isdir(natlink_userdir): - msg=f'{ysfnu}".' - f'\nSo Natlink should not startup with these messages. ' - f'\nProbably you do not have a proper "natlink.ini" file into this path.' - f'\nPlease run the configure program of Natlink:' +if natlink_settingsdir: + if isdir(natlink_settings_path): + if natlink_settingsdir == natlink_settings_path: + print(f'You specified for NATLINK_SETTINGSDIR: "{natlink_settingsdir}"') + else: + print(f'You specified for NATLINK_SETTINGSDIR: "{natlink_settingsdir}", which expands to: "{natlink_settings_path}".') else: - msg=f'{ysfnu}, but this is not a valid directory path' + print(f'You specified for NATLINK_SETTINGSDIR: "{natlink_settingsdir}"') + print('This directory should exist! Please create manually or run the config program ') else: - msg=f'\nYou did not set "NATLINK_USERDIR" on your windows system' - f'\nThe config file "natlink.ini" should now be in "{home}\\.natlink\\natlink.ini"\n' - f'\nSo... please copy the file "{this_dir}\\natlink.ini" into this location, or (BETTER)' - f'\nrun the configure program of Natlink:' + print('The default config directory NATLINK_SETTINGSDIR should hold a valid config file "natlink.ini"') -msg+= '\n' -'\nPlease try to run the config program (Command Line Interface) by running ***"natlinkconfig"***' -'\n\tfrom the Windows command prompt.' -"\n" -'\nAfter all these steps, (re)start Dragon and good luck...\n\n\n' +msg = '\n'.join(['', +'Please try to run the config program (Command Line Interface) by running', +'***"Configure Natlink via GUI"*** or ***"Configure Natlink via CLI"***', +'\tfrom the Windows command prompt.', +'', +'After all these steps, (re)start Dragon and good luck...', '', '']) print(msg) diff --git a/src/natlinkcore/config.py b/src/natlinkcore/config.py index c804bc9..d4c2cd1 100644 --- a/src/natlinkcore/config.py +++ b/src/natlinkcore/config.py @@ -133,8 +133,13 @@ def from_first_found_file(cls, files: Iterable[str]) -> 'NatlinkConfig': for fn in files: if not isfile(fn): continue - if config.read(fn): - return cls.from_config_parser(config, config_path=fn) + try: + if config.read(fn): + return cls.from_config_parser(config, config_path=fn) + except Exception as exc: + mess = 'Error reading config file, %s\nPlease try to correct'% exc + os.startfile(fn) + raise OSError(mess) from exc # should not happen, because of DefaultConfig (was InstallTest) raise NoGoodConfigFoundException('No natlink config file found, please run configure natlink program\n\t(***configurenatlink***)') @@ -178,9 +183,9 @@ def expand_path(input_path: str) -> str: dir_path = normpath(dir_path) if isdir(dir_path): return dir_path - print(f'no valid directory found with "natlink_userdir": "{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}"') + 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\\'): @@ -204,16 +209,19 @@ def expand_path(input_path: str) -> str: else: package_trunk, rest = input_path, '' # find path for package. not an alternative way without loading the package is to use importlib.util.findspec. - try: - pack = __import__(package_trunk) - package_path = pack.__path__[-1] - if rest: - dir_expanded = str(Path(package_path)/rest) - return dir_expanded - return package_path - - except ModuleNotFoundError: - pass + + # first check for exclude "C:" as trunk: + if package_trunk and package_trunk[-1] != ":": + try: + pack = __import__(package_trunk) + package_path = pack.__path__[-1] + if rest: + dir_expanded = str(Path(package_path)/rest) + return dir_expanded + return package_path + + except (ModuleNotFoundError, OSError): + pass env_expanded = expandvars(input_path) # print(f'env_expanded: "{env_expanded}", from envvar: "{input_path}"') @@ -222,25 +230,22 @@ def expand_path(input_path: str) -> str: def expand_natlink_settingsdir(): """not with envvariables, but special: - if NATLINK_USERDIR is set: return this, but... it should end with ".natlink" - if NATLINK_USERDIR is NOT set: return Path.home()/'.natlink' - """ - normpath = os.path.normpath - nud = os.getenv('natlink_userdir') or str(Path.home()/'.natlink') - nud = normpath(expand_path(nud)) - if not nud.endswith('.natlink'): - raise ValueError(f'expand_natlink_settingsdir: directory "{nud}" should end with ".natlink"\n\tprobably you did not set the windows environment variable "NATLINK_USERDIR" incorrect, let it end with ".natlink".\n\tNote: if this ".natlink" directory does not exist yet, it will be created there.') - return nud - -def expand_natlink_settingsdir(): - """not with envvariables, but special: - - if NATLINK_SETTINGsDIR is set: return this, but... it should end with ".natlink" + if NATLINK_SETTINGSDIR is set: return this, but... it should end with ".natlink" if NATLINK_SETTINGSDIR is NOT set: return Path.home()/'.natlink' """ normpath = os.path.normpath - nsd = os.getenv('natlink_settingsdir') or str(Path.home()/'.natlink') + nsd = os.getenv('natlink_settingsdir') + if nsd: + if not os.path.isdir(nsd): + # this one should not happen, because .natlink is automatically created when it does not exist yet... + raise OSError(f'Environment variable "NATLINK_SETTINGSDIR" should hold a valid directory, ending with ".natlink", not: "{nsd}"\n\tCreate your directory likewise or remove this environment variable, and go back to the default directory (~\\.natlink)\n') + + if not normpath(nsd).endswith('.natlink'): + raise ValueError(f'Environment variable "NATLINK_SETTINGSDIR" should end with ".natlink", not: "{nsd}"\n\tCreate your directory likewise or remove this environment variable, returning to the default directory (~\\.natlink)\n') + else: + nsd = str(Path.home()/'.natlink') + nsd = normpath(expand_path(nsd)) - if not nsd.endswith('.natlink'): - raise ValueError(f'expand_natlink_settingsdir: directory "{nsd}" should end with ".natlink"\n\tprobably you did not set the windows environment variable "NATLINK_SETTINGSDIR" incorrect, let it end with ".natlink".\n\tNote: if this ".natlink" directory does not exist yet, it will be created there.') + # if not nsd.endswith('.natlink'): + # raise ValueError(f'expand_natlink_settingsdir: directory "{nsd}" should end with ".natlink"\n\tprobably you did not set the windows environment variable "NATLINK_SETTINGSDIR" incorrect, let it end with ".natlink".\n\tNote: if this ".natlink" directory does not exist yet, it will be created there.') return nsd diff --git a/src/natlinkcore/configure/natlinkconfig_cli.py b/src/natlinkcore/configure/natlinkconfig_cli.py index 559f633..5182ccb 100644 --- a/src/natlinkcore/configure/natlinkconfig_cli.py +++ b/src/natlinkcore/configure/natlinkconfig_cli.py @@ -5,6 +5,7 @@ import os import os.path import logging +import configparser from pathlib import Path from natlinkcore.configure import extensions_and_folders from natlinkcore.configure import natlinkconfigfunctions @@ -165,9 +166,13 @@ def usage(self): # info---------------------------------------------------------- def do_i(self, arg): self.Config.status.__init__() - S = self.Config.status.getNatlinkStatusString() - S = S + '\n\nIf you changed things, you must restart Dragon' - print(S) + try: + S = self.Config.status.getNatlinkStatusString() + S = S + '\n\nIf you changed things, you must restart Dragon' + print(S) + except configparser.NoSectionError: + print('No directories specified (yet). \nPlease specify one or more directories that Natlink should visit for grammar files') + def do_I(self, arg): # inifile natlinkstatus.ini settings: self.Config.status.__init__() diff --git a/src/natlinkcore/configure/natlinkconfigfunctions.py b/src/natlinkcore/configure/natlinkconfigfunctions.py index b831926..be6a243 100644 --- a/src/natlinkcore/configure/natlinkconfigfunctions.py +++ b/src/natlinkcore/configure/natlinkconfigfunctions.py @@ -25,7 +25,10 @@ import configparser import logging -from natlinkcore import natlinkstatus +try: + from natlinkcore import natlinkstatus +except OSError: + print('error when starting natlinkconfigfunctions') from natlinkcore import config from natlinkcore import loader from natlinkcore import readwritefile @@ -72,7 +75,7 @@ def get_check_config_locations(self): """check the location/locations as given by the loader """ - config_path, fallback_path = loader.config_locations() + config_path, fallback_path = loader.config_locations() if not isfile(config_path): config_dir = Path(config_path).parent @@ -84,27 +87,47 @@ def get_check_config_locations(self): def check_config(self): """check config_file for possibly unwanted settings """ + # ensure the [directories] section is present: + try: + sect = self.Config['directories'] + except KeyError: + self.Config.add_section('directories') + self.config_write() + + self.config_remove(section='directories', option='default_config') + # check for default options missing: + # ret = config.NatlinkConfig.get_default_config() + # for ret_sect in ret.sections(): + # if self.Config.has_section(ret_sect): + # continue + # for ret_opt in self.Config[section].keys(): + # ret_value = ret[ret_sect][ret_opt] + # print(f'fix default section/key: "ret_sect", "ret_opt" to "ret_value"') + # change default unimacrogrammarsdirectory: section = 'directories' option = 'unimacrogrammarsdirectory' old_prefix = 'natlink_user' new_prefix = 'unimacro' - value = self.Config[section][option] - if value and value.find('natlink_user') == 0: - value = value.replace(old_prefix,new_prefix) - self.config_set(section, option, value) - logging.info(f'changed in "natlink.ini", section "directories", unimacro setting "{option}" to value: "{value}"') + try: + value = self.Config[section][option] + if value and value.find('natlink_user') == 0: + value = value.replace(old_prefix,new_prefix) + self.config_set(section, option, value) + logging.info(f'changed in "natlink.ini", section "directories", unimacro setting "{option}" to value: "{value}"') + pass + except KeyError: pass if loader.had_msg_error: logging.error('The environment variable "NATLINK_USERDIR" has been changed to "NATLINK_SETTINGSDIR" by the user, but has a conclicting value') - logging.error('Please remove "NATLINK_USERDIR", in the windows environment variables, dialog User variables, and restart your program') + logging.error('Please remove "NATLINK_USERDIR", in the windows "environment variables", dialog User variables, and restart your program') if loader.had_msg_warning: - logging.error('The key of the environment variable "NATLINK_USERDIR" should be changed to "NATLINK_SETTINGSDIR"') - logging.error('You can do so in "windows environment variables", dialog "User variables". Then your program') + logging.error('The key of the environment variable "NATLINK_USERDIR" should be changed to "NATLINK_SETTINGSDIR".') + logging.error('You can do so in windows "environment variables", dialog "User variables".') # for key, value in self.Config[section].items(): @@ -419,7 +442,7 @@ def enable_vocola(self, arg): if not vocola_user_dir: return # vocGrammarsDir = self.status.getVocolaGrammarsDirectory() - vocGrammarsDir = r'natlink_userdir\vocolagrammars' + vocGrammarsDir = r'natlink_settings\vocolagrammars' self.setDirectory('vocoladirectory','vocola2') #always vocola2 self.setDirectory('vocolagrammarsdirectory', vocGrammarsDir) self.copyUnimacroIncludeFile() @@ -440,11 +463,11 @@ def copyUnimacroIncludeFile(self): """ uscFile = 'Unimacro.vch' # also remove usc.vch from VocolaUserDirectory - unimacroDir = Path(self.status.getUnimacroDirectory()) - fromFolder = Path(unimacroDir)/'Vocola_compatibility' + dtactionsDir = Path(self.status.getDtactionsDirectory()) + fromFolder = Path(dtactionsDir)/'Vocola_compatibility' toFolder = Path(self.status.getVocolaUserDirectory()) - if not unimacroDir.is_dir(): - mess = f'copyUnimacroIncludeFile: unimacroDir "{str(unimacroDir)}" is not a directory' + if not dtactionsDir.is_dir(): + mess = f'copyUnimacroIncludeFile: dtactionsDir "{str(dtactionsDir)}" is not a directory' logging.warning(mess) return fromFile = fromFolder/uscFile @@ -495,7 +518,7 @@ def removeUnimacroIncludeFile(self): mess = f'copyUnimacroIncludeFile: Could not remove previous version of "{str(toFile)}"' logging.warning(mess) - def includeUnimacroVchLineInVocolaFiles(self, subDirectory=None): + def includeUnimacroVchLineInVocolaFiles(self, toFolder=None): """include the Unimacro wrapper support line into all Vocola command files as a side effect, set the variable for Unimacro in Vocola support: @@ -505,55 +528,66 @@ def includeUnimacroVchLineInVocolaFiles(self, subDirectory=None): oldUscFile = 'usc.vch' ## reInclude = re.compile(r'^include\s+.*unimacro.vch;$', re.MULTILINE) ## reOldInclude = re.compile(r'^include\s+.*usc.vch;$', re.MULTILINE) - + # also remove includes of usc.vch - toFolder = self.status.getVocolaUserDirectory() + vocUserDir = self.status.getVocolaUserDirectory() + toFolder = toFolder or vocUserDir + subDirectory = toFolder != vocUserDir if subDirectory: - toFolder = os.path.join(toFolder, subDirectory) includeLine = f'include ..\\{uscFile};\n' + oldIncludeLines = [f'include {oldUscFile};', + f'include ..\\{oldUscFile};', + f'include {uscFile};' + ] else: includeLine = f'include {uscFile};\n' - oldIncludeLines = [f'include {oldUscFile};', - f'include ..\\{oldUscFile};', - f'include {uscFile.lower()};', - f'include ..\\{uscFile.lower()};', - ] + oldIncludeLines = [f'include {oldUscFile};', + f'include ..\\{oldUscFile};', + f'include ..\\{uscFile};' + ] if not os.path.isdir(toFolder): - mess = f'cannot find Vocola command files directory, not a valid path: {toFolder}' + if subDirectory: + mess = f'cannot find Vocola command files in sub directory, not a valid path: {toFolder}' + else: + mess = f'cannot find Vocola command files in irectory, not a valid path: {toFolder}' logging.warning(mess) return mess + nFiles = 0 for f in os.listdir(toFolder): - F = os.path.join(toFolder, f) if f.endswith(".vcl"): + F = os.path.join(toFolder, f) changed = 0 correct = 0 Output = [] for line in open(F, 'r'): - if line.strip() == includeLine.strip(): + if line.strip().lower() == includeLine.strip().lower(): correct = 1 for oldLine in oldIncludeLines: - if line.strip() == oldLine: - changed = 1 + if line.strip().lower() == oldLine.lower(): + changed += 1 break else: Output.append(line) if changed or not correct: - # changes were made: + # print(f'{F}: wrong lines: {changed}, had correct line: {bool(correct)}') # changes were made: if not correct: + # print(f'\tinclude: "{includeLine.strip()}"') Output.insert(0, includeLine) open(F, 'w').write(''.join(Output)) nFiles += 1 - elif len(f) == 3 and os.path.isdir(F): + elif len(f) == 3: # subdirectory, recursive - self.includeUnimacroVchLineInVocolaFiles(F) + self.includeUnimacroVchLineInVocolaFiles(toFolder=os.path.join(toFolder, f)) mess = f'changed {nFiles} files in {toFolder}' logging.warning(mess) return True - def removeUnimacroVchLineInVocolaFiles(self, subDirectory=None): + def removeUnimacroVchLineInVocolaFiles(self, toFolder=None): """remove the Unimacro wrapper support line into all Vocola command files + + toFolder set with recursive calls... """ uscFile = 'Unimacro.vch' oldUscFile = 'usc.vch' @@ -561,9 +595,8 @@ def removeUnimacroVchLineInVocolaFiles(self, subDirectory=None): ## reOldInclude = re.compile(r'^include\s+.*usc.vch;$', re.MULTILINE) # also remove includes of usc.vch - if subDirectory: - # for recursive call language subfolders: - toFolder = subDirectory + if toFolder: + pass # for recursive call language subfolders: else: toFolder = self.status.getVocolaUserDirectory() @@ -573,9 +606,6 @@ def removeUnimacroVchLineInVocolaFiles(self, subDirectory=None): f'include ..\\{uscFile};', f'include ../{oldUscFile};', f'include ../{uscFile};', - f'include {uscFile.lower()};', - f'include ..\\{uscFile.lower()};', - f'include ../{uscFile.lower()};' ] @@ -591,7 +621,7 @@ def removeUnimacroVchLineInVocolaFiles(self, subDirectory=None): Output = [] for line in open(F, 'r'): for oldLine in oldIncludeLines: - if line.strip() == oldLine: + if line.strip().lower() == oldLine.lower(): changed = 1 break else: @@ -600,9 +630,9 @@ def removeUnimacroVchLineInVocolaFiles(self, subDirectory=None): # had break, so changes were made: open(F, 'w').write(''.join(Output)) nFiles += 1 - elif len(f) == 3 and os.path.isdir(F): - self.removeUnimacroVchLineInVocolaFiles(F) - self.disableVocolaTakesUnimacroActions() + elif len(f) == 3: + self.removeUnimacroVchLineInVocolaFiles(toFolder=os.path.join(toFolder, f)) + # self.disableVocolaTakesUnimacroActions() mess = f'removed include lines from {nFiles} files in {toFolder}' logging.warning(mess) @@ -645,6 +675,9 @@ def disableVocolaTakesUnimacroActions(self): def openConfigFile(self): """open the natlink.ini config file """ + assert self.config_path and os.path.isfile(self.config_path) + # logging.warning(f'openConfigFile, no valid config_path specified: "{self.config_path}"') + # return False os.startfile(self.config_path) logging.info(f'opened "{self.config_path}" in a separate window') return True diff --git a/src/natlinkcore/loader.py b/src/natlinkcore/loader.py index 172e1a3..ad60f6b 100644 --- a/src/natlinkcore/loader.py +++ b/src/natlinkcore/loader.py @@ -506,10 +506,10 @@ def set_user_language(self, args: Any = None): if args: self.user, self.profile = args self.language = self.get_user_language(self.profile) - self.logger.debug(f'set_user_language, user: "{self.user}", profile: "{self.profile}", language: "{self.language}"') + # self.logger.debug(f'set_user_language, user: "{self.user}", profile: "{self.profile}", language: "{self.language}"') else: self.user, self.profile = '', '' - self.logger.warning('set_user_language, cannot get input for get_user_language, set to "enx",\n\tprobably Dragon is not running or you are preforming pytests') + # self.logger.warning('set_user_language, cannot get input for get_user_language, set to "enx",\n\tprobably Dragon is not running or you are preforming pytests') self.language = 'enx' def start(self) -> None: diff --git a/src/natlinkcore/natlinkstatus.py b/src/natlinkcore/natlinkstatus.py index c980770..8cd2512 100644 --- a/src/natlinkcore/natlinkstatus.py +++ b/src/natlinkcore/natlinkstatus.py @@ -73,10 +73,9 @@ the Unimacro user directory getUnimacroDataDirectory: get the directory where Unimacro grammars can store data, this should be per computer, and is set - into the natlink_user area + into the natlink_settingsdir area -getUnimacroGrammarsDirectory: get the directory where Unimacro grammars are (by default) located in a sub directory 'UnimacroGrammars' of the - UnimacroDirectory +getUnimacroGrammarsDirectory: get the directory where Unimacro grammars are (by default) located in the python site-packagers directory 'unimacro' getVocolaDirectory: get the directory where the Vocola system is. When cloned from git, in Vocola, relative to the Core directory. Otherwise (when pipped) in some site-packages directory. It holds (and should hold) the @@ -88,7 +87,7 @@ getVocolaGrammarsDirectory: get the directory, where the compiled Vocola grammars are/will be. This will be the `CompiledGrammars` subdirectory of `~/.vocolaGrammars` or - `%NATLINK_USERDIR%/.vocola`. + `%NATLINK_SETTINGSDIR%/.vocola`. getVocolaTakesLanguages: additional settings for Vocola @@ -363,11 +362,11 @@ def getNatlinkIni(self): raise OSError(f'getNatlinkIni: not a valid file: "{path}"') return path - def getNatlink_Userdir(self): + def getNatlink_Settingsdir(self): """get the directory where "natlink.ini" should be stored This must be a local directory, default `~`, but can be changed by - setting `NATLINK_USERDIR` to for example `~/Documents`. + setting `NATLINK_SETTINGSDIR` to for example `~/Documents/.natlink`. Other directories that are created and checked by packages, and should be local, can be stored here, for example `VocolaGrammarsDirectory` (VocolaGrammars) and @@ -375,8 +374,9 @@ def getNatlink_Userdir(self): """ natlink_ini_path = Path(self.getNatlinkIni()) - natlink_user_dir = natlink_ini_path.parent - return str(natlink_user_dir) + natlink_settings_dir = natlink_ini_path.parent + + return str(natlink_settings_dir) def getUnimacroUserDirectory(self): isdir, abspath = os.path.isdir, os.path.abspath @@ -435,6 +435,8 @@ def getUnimacroGrammarsDirectory(self): key = 'unimacrogrammarsdirectory' value = self.natlinkmain.getconfigsetting(section='directories', option=key) + if not value: + return "" um_grammars_dir = natlinkcore.config.expand_path(value) self.UnimacroGrammarsDirectory = um_grammars_dir @@ -450,9 +452,9 @@ def getUnimacroDataDirectory(self): if self.UnimacroDataDirectory is not None: return self.UnimacroDataDirectory - natlink_user_dir = self.getNatlink_Userdir() + natlink_settings_dir = self.getNatlink_Settingsdir() - um_data_dir = Path(natlink_user_dir)/'UnimacroData' + um_data_dir = Path(natlink_settings_dir)/'UnimacroData' if not um_data_dir.is_dir(): um_data_dir.mkdir() um_data_dir = str(um_data_dir) @@ -753,7 +755,7 @@ def getNatlinkStatusDict(self): for key in ['DNSIniDir', 'WindowsVersion', 'DNSVersion', 'PythonVersion', - 'DNSName', 'NatlinkIni', 'Natlink_Userdir', + 'DNSName', 'NatlinkIni', 'Natlink_Settingsdir', 'UnimacroDirectory', 'UnimacroUserDirectory', 'UnimacroGrammarsDirectory', 'UnimacroDataDirectory', 'VocolaDirectory', 'VocolaUserDirectory', 'VocolaGrammarsDirectory', 'VocolaTakesLanguages', 'VocolaTakesUnimacroActions', @@ -797,7 +799,7 @@ def getNatlinkStatusString(self): # Natlink:: L.append('') - for key in ['NatlinkDirectory', 'NatlinkcoreDirectory', 'InstallVersion', 'NatlinkIni', 'Natlink_Userdir']: + for key in ['NatlinkDirectory', 'NatlinkcoreDirectory', 'InstallVersion', 'NatlinkIni', 'Natlink_Settingsdir']: self.appendAndRemove(L, D, key) ## Dragonfly: diff --git a/src/natlinkcore/readwritefile.py b/src/natlinkcore/readwritefile.py index d34ed0b..0ff4415 100644 --- a/src/natlinkcore/readwritefile.py +++ b/src/natlinkcore/readwritefile.py @@ -38,8 +38,6 @@ class ReadWriteFile: `encodings` and `encoding` can be overridden at creation of an instance. `encodings` must then be a list of possible encodings - `encoding` is then - when `encoding` is a str, `encodings` is set to a list only containing this encoding the default `encodings` are: `['ascii', 'utf-8', 'cp1252', 'latin-1']` @@ -47,6 +45,9 @@ class ReadWriteFile: When the encoding is 'ascii' and at write time, non ascii characters are present, care is taken to encode the output to another encoding, most often (default) 'utf-8'. + + When you need a 'utf-16le' encoding (for 'nsapps.ini' of Dragon), + pass `encodings = ['utf16-le']` when creating the instance. """ def __init__(self, encodings=None): self.input_path = '' diff --git a/tests/mock_readwritefile/cp1252.txt b/tests/mock_readwritefile/cp1252.txt index 8d169a0..78617b2 100644 --- a/tests/mock_readwritefile/cp1252.txt +++ b/tests/mock_readwritefile/cp1252.txt @@ -1 +1 @@ -cp1252 café +cp1252 euro: € \ No newline at end of file diff --git a/tests/mock_readwritefile/latin1 accented.txt b/tests/mock_readwritefile/latin1.txt similarity index 100% rename from tests/mock_readwritefile/latin1 accented.txt rename to tests/mock_readwritefile/latin1.txt diff --git a/tests/test_config.py b/tests/test_config.py index 098bacd..73d5a59 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -18,7 +18,7 @@ def sample_config(sample_name) -> 'NatlinkConfig': """ load a config file from the config files subfolder """ - sample_ini= (p.Path(__file__).parent) / "config_files" / sample_name + sample_ini= str((p.Path(__file__).parent) / "config_files" / sample_name ) test_config = NatlinkConfig.from_file(sample_ini) return test_config @@ -55,7 +55,7 @@ def mock_syspath(monkeypatch): def mock_userdir(monkeypatch): mock_folder=p.WindowsPath(os.path.dirname(__file__)) / "mock_userdir" / ".natlink" print(f"Mock Userdir Folder {mock_folder}") - monkeypatch.setenv("natlink_userdir",str(mock_folder)) + monkeypatch.setenv("natlink_settingsdir",str(mock_folder)) def test_settings_1(mock_syspath,settings1): @@ -148,11 +148,11 @@ def test_expand_path(mock_syspath,mock_userdir): assert not os.path.isdir(result) # assume FakeGrammars is a valid directory: - result = expand_path('natlink_userdir/FakeGrammars') + result = expand_path('natlink_settingsdir/FakeGrammars') assert os.path.isdir(result) # invalid directory - result = expand_path('natlink_userdir/invalid_dir') + result = expand_path('natlink_settingsdir/invalid_dir') assert not os.path.isdir(result) # try package @@ -174,12 +174,6 @@ def test_expand_path(mock_syspath,mock_userdir): result = expand_path('/natlinkcore') assert not os.path.isdir(result) - result = expand_path('unimacro') - assert os.path.isdir(result) - - result = expand_path('unimacro/unimacrogrammars') - assert os.path.isdir(result) - def test_config_locations(): diff --git a/tests/test_readwritefile.py b/tests/test_readwritefile.py index ed4879b..74a9303 100644 --- a/tests/test_readwritefile.py +++ b/tests/test_readwritefile.py @@ -52,7 +52,7 @@ def test_accented_characters_write_file(tmp_path): # newFile = join(testDir, 'output-accented.txt') testDir = tmp_path / testFolderName testDir.mkdir() - newFile = testDir/"outut-accented.txt" + newFile = testDir/"output-accented.txt" text = 'caf\xe9' rwfile = ReadWriteFile(encodings=['ascii']) # optional encoding # this is with default errors='xmlcharrefreplace': @@ -89,7 +89,7 @@ def test_other_encodings_write_file(tmp_path): testDir = tmp_path / testFolderName testDir.mkdir() - oldFile = mock_readwritefiledir/'latin1 accented.txt' + oldFile = mock_readwritefiledir/'latin1.txt' rwfile = ReadWriteFile(encodings=['latin1']) # optional encoding text = rwfile.readAnything(oldFile) @@ -133,16 +133,51 @@ def test_nsapps_utf16(tmp_path): assert encoding2 == 'utf-16le' def test_latin1_cp1252_write_file(tmp_path): + """have one latin-1 file and one that is specific cp1252 (euro sign) + + Currently both return cp1252, as is is hard to distinguish them and cp1252 is more general + """ testDir = tmp_path / testFolderName testDir.mkdir() - _newFile = testDir/ 'latin1.txt' - _newFile = testDir/'cp1252.txt' - assert False, "QH TODO" + mock_files_list = os.listdir(mock_readwritefiledir) + + assert 'latin1.txt' in mock_files_list + assert 'cp1252.txt' in mock_files_list + + rwfilelatin1 = ReadWriteFile() + rwfilecp1252 = ReadWriteFile() + latin1_path = mock_readwritefiledir/'latin1.txt' + cp1252_path = mock_readwritefiledir/'cp1252.txt' + + rwfilelatin1.readAnything(latin1_path) + + assert rwfilelatin1.bom == '' + assert rwfilelatin1.encoding == 'cp1252' + + rwfilecp1252.readAnything(cp1252_path) + assert rwfilecp1252.bom == '' + assert rwfilecp1252.encoding == 'cp1252' + + # TODO (QH) to be done, these encodings do not take all characters, # and need special attention. # (as long as the "fallback" is utf-8, all write files should go well!) +# def test_latin1_cp1252_write_file(tmp_path): +# """ TODO (QH) to be done, these encodings do not take all characters, +# and need special attention. latin1 and cp1252 are hard to be distinguished +# For now, cp1252 (holding more (some special characters like the euro sign and quotes)) +# is favored over latin1. +# (as long as the "fallback" is utf-8, all write files should go well!) +# """ +# testDir = tmp_path / testFolderName +# testDir.mkdir() +# _newFile = testDir/ 'latin1.txt' +# _newFile = testDir/'cp1252.txt' +# assert False, "QH TODO" + + def test_read_write_file(tmp_path): listdir, join, splitext = os.listdir, os.path.join, os.path.splitext testDir = tmp_path / testFolderName @@ -151,11 +186,15 @@ def test_read_write_file(tmp_path): assert len(mock_files_list) > 0 for F in mock_files_list: + encodings = None + if F.startswith('nsapps'): + encodings = ['utf-16le'] + continue # utf16-le is not caught by the standard function, but needs its own encoding if not F.startswith('output-'): Fout = 'output-' + F #read the file from the mock folder F_path = mock_readwritefiledir / F - rwfile = ReadWriteFile() + rwfile = ReadWriteFile(encodings=encodings) text = rwfile.readAnything(F_path) trunk, _ext = splitext(F) Fout = trunk + ".txt" @@ -172,13 +211,42 @@ def test_read_write_file(tmp_path): raise ValueError(f'old: "{F_path}", new: "{Fout_path}", differ at pos {i}: Old: "{o}", new: "{n}", partold (i:i+2): "{parto}", partnew: "{partn}"') def test_acoustics_ini(tmp_path): + """this is a utf-8 file with a bom mark. Try also writing back! + """ + testDir = tmp_path / testFolderName + testDir.mkdir() + + F='acoustic.ini' F_path = mock_readwritefiledir/F rwfile = ReadWriteFile() config_text = rwfile.readAnything(F_path) Config = configparser.ConfigParser() Config.read_string(config_text) - assert Config.get('Acoustics', '2 2') == '2_2' + assert Config.get('Acoustics', '2 2') == '2_2' + + newFile1 = 'output1' + F + newPath1 = testDir/newFile1 + rwfile.writeAnything(newPath1, config_text) + + assert filecmp.cmp(F_path, newPath1) + + rwfile2 = ReadWriteFile() + text2 = rwfile2.readAnything(newPath1) + bom2 = rwfile2.bom + encoding2 = rwfile2.encoding + + tRaw = rwfile.rawText + tRaw2 = rwfile2.rawText + + assert tRaw2 == tRaw + assert text2[0:5] == '[Base' + assert bom2 == [239, 187, 191] + assert encoding2 == 'utf-8' + + + + @pytest.mark.parametrize("F", ['originalnatlink.ini', 'natlinkconfigured.ini']) def test_config_ini(tmp_path,F):