diff --git a/src/natlinkcore/configure/natlinkconfigfunctions.py b/src/natlinkcore/configure/natlinkconfigfunctions.py index 409c680..aa9c336 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, C0415, W0611 +#pylint:disable=C0302, W0702, R0904, C0116, W0613, R0914, R0912 """With the functions in this module Natlink can be configured. These functions are called in different ways: @@ -57,13 +57,6 @@ def get_check_config_locations(self): """ config_path, fallback_path = loader.config_locations() - if isfile(config_path): - with open(config_path, 'r', encoding='utf-8') as fp: - text = fp.read().strip() - if not text: - print(f'empty natlink.ini file: "{config_path}",\n\tremove, and go back to default') - os.remove(config_path) - if not isfile(config_path): config_dir = Path(config_path).parent if not config_dir.is_dir(): @@ -75,43 +68,7 @@ def check_config(self): """check config_file for possibly unwanted settings """ self.config_remove(section='directories', option='default_config') - keys = self.config_get('directories') - - ## check vocola: - if 'vocoladirectory' in keys and 'vocolagrammarsdirectory' in keys: - try: - import vocola2 - except ImportError: - # vocola has been gone, remove: - self.disable_vocola() - self.config_remove('vocola', 'vocolauserdirectory') - else: - ## just to be sure: - self.config_remove('vocola', 'vocolauserdirectory') - self.config_remove('directories', 'vocoladirectory') - self.config_remove('directories', 'vocolagrammarsdirectory') - if 'unimacrodirectory' in keys and 'unimacrogrammarsdirectory' in keys: - try: - import unimacro - except ImportError: - # unimacro has been gone, remove: - self.disable_unimacro() - self.config_remove('unimacro', 'unimacrouserdirectory') - else: - ## just to be sure: - self.config_remove('unimacro', 'unimacrouserdirectory') - self.config_remove('directories', 'unimacrodirectory') - self.config_remove('directories', 'unimacrogrammarsdirectory') - - - if 'dragonflyuserdirectory' in keys: - try: - import dragonfly - except ImportError: - # dragonfly has been gone, remove: - self.disable_dragonfly() - def getConfig(self): """return the config instance """ @@ -122,16 +79,14 @@ def getConfig(self): self.config_encoding = rwfile.encoding return _config - def config_get(self, section, option=None): - """get the section keys or a setting from the natlink ini file + def config_get(self, section, option): + """set a setting into the natlink ini file """ - if option: - try: - return self.Config.get(section, option) - except (configparser.NoSectionError, configparser.NoOptionError): - return None - return self.Config.options(section) + try: + return self.Config.get(section, option) + except (configparser.NoSectionError, configparser.NoOptionError): + return None def config_set(self, section, option, value): """set a setting into an inifile (possibly other than natlink.ini) @@ -152,7 +107,7 @@ def config_set(self, section, option, value): value = str(value) self.Config.set(section, option, str(value)) self.config_write() - self.status.__init__() + self.status = natlinkstatus.NatlinkStatus() return True def config_write(self): @@ -180,7 +135,7 @@ def config_remove(self, section, option): if section not in ['directories', 'settings', 'userenglish-directories', 'userspanish-directories']: self.Config.remove_section(section) self.config_write() - self.status.__init__() + self.status = natlinkstatus.NatlinkStatus() # def setUserDirectory(self, arg): # self.setDirectory('UserDirectory', arg) @@ -200,7 +155,7 @@ def setDirectory(self, option, dir_path, section=None): print('No valid directory specified') return - dir_path = dir_path.strip().replace('/', '\\') + dir_path = dir_path.strip() directory = createIfNotThere(dir_path, level_up=1) if not (directory and Path(directory).is_dir()): if directory is False: @@ -284,26 +239,32 @@ def clearFile(self, option, section): def setLogging(self, logginglevel): """Sets the natlink logging output - logginglevel (str) -- CRITICAL, FATAL, ERROR, WARNING, INFO, DEBUG - - This one is used in the natlinkconfig_gui + logginglevel (str) -- Critical, Fatal, Error, Warning, Info, Debug """ - key = 'log_level' - section = 'settings' - value = logginglevel.upper() - old_value = self.config_get(section, key) + # Config.py handles log level str upper formatting from ini + value = logginglevel.title() + old_value = self.config_get('settings', "log_level") if old_value == value: print(f'setLogging, setting is already "{old_value}"') return True - if value in ["CRITICAL", "FATAL", "ERROR", "WARNING", "INFO", "DEBUG"]: + if value in ["Critical", "Fatal", "Error", "Warning", "Info", "Debug"]: print(f'setLogging, setting logging to: "{value}"') - self.config_set(section, key, value) + self.config_set('settings', "log_level", value) if old_value is not None: - self.config_set('previous settings', key, old_value) + self.config_set('previous settings', "log_level", old_value) return True - print(f'Invalid value for setLogging: "{value}"') return False + def disableDebugOutput(self): + """disables the Natlink debug output + """ + key = 'log_level' + # section = 'settings' + old_value = self.config_get('previous settings', key) + if old_value: + self.config_set('settings', key, old_value) + self.config_set('settings', key, 'INFO') + def enable_unimacro(self, arg): unimacro_user_dir = self.status.getUnimacroUserDirectory() if unimacro_user_dir and isdir(unimacro_user_dir): @@ -313,7 +274,7 @@ def enable_unimacro(self, arg): uni_dir = self.status.getUnimacroDirectory() if uni_dir: - print('==== install and/or update unimacro====\n') + print('==== instal and/or update unimacro====\n') try: subprocess.check_call([sys.executable, "-m", "pip", "install", "--upgrade", "unimacro"]) except subprocess.CalledProcessError: @@ -359,7 +320,7 @@ def enable_vocola(self, arg): voc_dir = self.status.getVocolaDirectory() if voc_dir: - print('==== install and/or update vocola2====\n') + print('==== instal and/or update vocola2====\n') try: subprocess.check_call([sys.executable, "-m", "pip", "install", "--upgrade", "vocola2"]) except subprocess.CalledProcessError: @@ -394,41 +355,6 @@ def disable_vocola(self, arg=None): self.config_remove('directories', 'vocola') self.config_remove('directories', 'vocoladirectory') #could still be there... - def enable_dragonfly(self, arg): - """enable dragonfly, by setting arg (prompting if False), and other settings - """ - key = 'dragonflyuserdirectory' - dragonfly_user_dir = self.status.getDragonflyUserDirectory() - if dragonfly_user_dir and isdir(dragonfly_user_dir): - print(f'dragonflyUserDirectory is already defined: "{dragonfly_user_dir}"\n\tto change, first clear (option "D") and then set again') - print('\nWhen you want to upgrade dragonfly (dragonfly2), also first clear ("D"), then choose this option ("d") again.\n') - return - - dfl_prev_dir = self.config_get('previous settings', key) - if dfl_prev_dir: - - print('==== install and/or update dragonfly2====\n') - try: - subprocess.check_call([sys.executable, "-m", "pip", "install", "--upgrade", "dragonfly2"]) - except subprocess.CalledProcessError: - print('====\ncould not pip install --upgrade dragonfly2\n====\n') - return - else: - try: - subprocess.check_call([sys.executable, "-m", "pip", "install", "dragonfly2"]) - except subprocess.CalledProcessError: - print('====\ncould not pip install dragonfly2\n====\n') - return - self.status.refresh() # refresh status - - self.setDirectory(key, arg) - - def disable_dragonfly(self, arg=None): - """disable dragonfly, arg not needed/used - """ - key = 'dragonflyuserdirectory' - self.clearDirectory(key) - def copyUnimacroIncludeFile(self): """copy Unimacro include file into Vocola user directory @@ -525,20 +451,20 @@ def includeUnimacroVchLineInVocolaFiles(self, subDirectory=None): changed = 0 correct = 0 Output = [] - rwfile = readwritefile.ReadWriteFile() - lines = rwfile.readAnything(F).split('\n') - for line in lines: + for line in open(F, 'r'): if line.strip() == includeLine.strip(): correct = 1 - if line.strip() in oldIncludeLines: - changed = 1 - continue - Output.append(line) + for oldLine in oldIncludeLines: + if line.strip() == oldLine: + changed = 1 + break + else: + Output.append(line) if changed or not correct: # changes were made: if not correct: Output.insert(0, includeLine) - rwfile.writeAnything(F, Output) + open(F, 'w').write(''.join(Output)) nFiles += 1 elif len(f) == 3 and os.path.isdir(F): # subdirectory, recursive @@ -584,10 +510,7 @@ def removeUnimacroVchLineInVocolaFiles(self, subDirectory=None): if f.endswith(".vcl"): changed = 0 Output = [] - rwfile = readwritefile.ReadWriteFile() - lines = rwfile.readAnything(F).split('\n') - - for line in lines: + for line in open(F, 'r'): for oldLine in oldIncludeLines: if line.strip() == oldLine: changed = 1 @@ -596,10 +519,11 @@ def removeUnimacroVchLineInVocolaFiles(self, subDirectory=None): Output.append(line) if changed: # had break, so changes were made: - rwfile.writeAnything(F, Output) + open(F, 'w').write(''.join(Output)) nFiles += 1 elif len(f) == 3 and os.path.isdir(F): self.removeUnimacroVchLineInVocolaFiles(F) + self.disableVocolaTakesUnimacroActions() mess = f'removed include lines from {nFiles} files in {toFolder}' print(mess) @@ -749,3 +673,4 @@ def createIfNotThere(path_name, level_up=None): _home_path = _nc.home_path _natlinkconfig_path = _nc.natlinkconfig_path print(f'natlinkconfig_path: {_natlinkconfig_path}') + pass diff --git a/src/natlinkcore/natlinkpydebug.py b/src/natlinkcore/natlinkpydebug.py new file mode 100644 index 0000000..f5ecb24 --- /dev/null +++ b/src/natlinkcore/natlinkpydebug.py @@ -0,0 +1,86 @@ +""" +code to help with debugging including: +- enable python debuggers to attach. +currently on DAP debuggers are supported. +https://microsoft.github.io/debug-adapter-protocol/ +There are several, Microsoft Visual Studio COde is known to work. +There are several, Microsoft Visual Studio COde is known to work. + +If you know how to add support for another debugger please add it. + +Written by Doug Ransom, 2021 +""" +#pylint:disable=C0116, W0703 +import os +import debugpy +from natlinkcore import natlinkstatus + +__status = natlinkstatus.NatlinkStatus() +__natLinkPythonDebugPortEnviornmentVar= "NatlinkPyDebugPort" +__natLinkPythonDebugOnStartupVar="NatlinkPyDebugStartup" + +__pyDefaultPythonExecutor = "python.exe" +__debug_started=False +default_debugpy_port=7474 +__debugpy_debug_port=default_debugpy_port +__debugger="not configured" +dap="DAP" + +#bring a couple functions from DAP and export from our namespace +dap_is_client_connected=debugpy.is_client_connected +dap_breakpoint = debugpy.breakpoint + +def dap_info(): + return f""" +Debugger: {__debugger} DAP Port:{__debugpy_debug_port} IsClientConnected: {dap_is_client_connected()} Default DAP Port {default_debugpy_port} +Debug Started:{__debug_started} +""" + +def start_dap(): + #pylint:disable=W0603 + global __debug_started,__debugpy_debug_port,__debugger + if __debug_started: + print(f"DAP already started with debugpy for port {__debugpy_debug_port}") + return + try: + + if __natLinkPythonDebugPortEnviornmentVar in os.environ: + natLinkPythonPortStringVal = os.environ[__natLinkPythonDebugPortEnviornmentVar] + __debugpy_debug_port = int(natLinkPythonPortStringVal) + print(f"Starting debugpy on port {natLinkPythonPortStringVal}") + + python_exec = __pyDefaultPythonExecutor #for now, only the python in system path can be used for natlink and this module + print(f"Python Executable (required for debugging): '{python_exec}'") + debugpy.configure(python=f"{python_exec}") + debugpy.listen(__debugpy_debug_port) + print(f"debugpy listening on port {__debugpy_debug_port}") + __debug_started = True + __debugger = dap + + if __natLinkPythonDebugOnStartupVar in os.environ: + dos_str=os.environ[__natLinkPythonDebugOnStartupVar] + dos=len(dos_str)==1 and dos_str in "YyTt" + + if dos: + print(f"Waiting for DAP debugger to attach now as {__natLinkPythonDebugOnStartupVar} is set to {dos_str}") + debugpy.wait_for_client() + + + except Exception as ee: + print(f""" + Exception {ee} while starting debug. Possible cause is incorrect python executable specified {python_exec} +""" ) + +def debug_check_on_startup(): + #pylint:disable=W0603 + global __debug_started,__debugpy_debug_port,__debugger + debug_instructions = f"{__status.getCoreDirectory()}\\debugging python instructions.docx" + print(f"Instructions for attaching a python debugger are in {debug_instructions} ") + if __natLinkPythonDebugPortEnviornmentVar in os.environ: + start_dap() + + + + + + diff --git a/src/natlinkcore/natlinkstatus.py b/src/natlinkcore/natlinkstatus.py index 816fe14..260243f 100644 --- a/src/natlinkcore/natlinkstatus.py +++ b/src/natlinkcore/natlinkstatus.py @@ -68,6 +68,9 @@ getUnimacroDataDirectory: get the directory where Unimacro grammars can store data, this should be per computer, and is set into the natlink_user area +getUnimacroGrammarsDirectory: get the directory where Unimacro grammars are (by default) located in a sub directory 'UnimacroGrammars' of the + UnimacroDirectory + 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 grammar _vocola_main.py. @@ -154,6 +157,7 @@ def __init__(self): self.UnimacroUserDirectory = None # self.UnimacroGrammarsDirectory = None self.UnimacroDataDirectory = None + self.UnimacroGrammarsDirectory = None ## Vocola: self.VocolaUserDirectory = None self.VocolaDirectory = None @@ -404,7 +408,27 @@ def getUnimacroDirectory(self): return "" self.UnimacroDirectory = unimacro.__path__[-1] return self.UnimacroDirectory + + def getUnimacroGrammarsDirectory(self): + """return the path to the UnimacroGrammarDirectory + + This is the directory UnimacroGrammars below the unimacro directory (most in site-packages) + is normally got via `pip install unimacro` + Can be changed manually in "natlink.ini" as "unimacrogrammarsdirectory = dir-of-your-choice". (section: [unimacro]) + + Note: unimacro grammars can also be put into other "[directories]" in your natlink.ini file. + + """ + if self.UnimacroGrammarsDirectory is not None: + return self.UnimacroGrammarsDirectory + + key = 'unimacrogrammarsdirectory' + value = self.natlinkmain.getconfigsetting(section='directories', option=key) + um_grammars_dir = natlinkcore.config.expand_path(value) + + self.UnimacroGrammarsDirectory = um_grammars_dir + return um_grammars_dir def getUnimacroDataDirectory(self): """return the path to the directory where grammars can store data. @@ -426,34 +450,6 @@ def getUnimacroDataDirectory(self): return um_data_dir - # def getUnimacroGrammarsDirectory(self): - # """return the path to the directory where (part of) the ActiveGrammars of Unimacro are located. - # - # By default in the UnimacroGrammars subdirectory of site-packages/unimacro, but look in natlink.ini file... - # - # """ - # isdir, abspath = os.path.isdir, os.path.abspath - # if self.UnimacroGrammarsDirectory is not None: - # return self.UnimacroGrammarsDirectory - # key = 'unimacrogrammarsdirectory' - # value = self.natlinkmain.getconfigsetting(section="directories", option=key) - # if not value: - # self.UnimacroGrammarsDirectory = '' - # return '' - # if isdir(value): - # self.UnimacroGrammarDirectory = value - # return abspath(value) - # - # expanded = config.expand_path(value) - # if expanded and isdir(expanded): - # self.UnimacroGrammarDirectory = abspath(expanded) - # return self.UnimacroGrammarDirectory - # - # # check_natlinkini = - # self.UnimacroGrammarsDirectory = '' - # - # return '' - # def getNatlinkDirectory(self): """return the path of the NatlinkDirectory, where the _natlink_core.pyd package (C++ code) is """ @@ -595,14 +591,10 @@ def getVocolaGrammarsDirectory(self): if self.VocolaGrammarsDirectory is not None: return self.VocolaGrammarsDirectory - natlink_user_dir = self.getNatlink_Userdir() - - voc_grammars_dir = Path(natlink_user_dir)/'VocolaGrammars' - if not voc_grammars_dir.is_dir(): - voc_grammars_dir.mkdir() - voc_grammars_dir = str(voc_grammars_dir) + key = 'vocolagrammarsdirectory' + value = self.natlinkmain.getconfigsetting(section='directories', option=key) + voc_grammars_dir = natlinkcore.config.expand_path(value) self.VocolaGrammarsDirectory = voc_grammars_dir - return voc_grammars_dir def getAhkUserDir(self): @@ -807,7 +799,7 @@ def getNatlinkStatusString(self): ## Unimacro: if D['unimacroIsEnabled']: self.appendAndRemove(L, D, 'unimacroIsEnabled', "---Unimacro is enabled") - for key in ('UnimacroUserDirectory', 'UnimacroDirectory', 'UnimacroDataDirectory'): + for key in ('UnimacroUserDirectory', 'UnimacroDirectory', 'UnimacroDataDirectory', 'UnimacroGrammarsDirectory'): self.appendAndRemove(L, D, key) else: self.appendAndRemove(L, D, 'unimacroIsEnabled', "---Unimacro is disabled") diff --git a/tests/buttonclicktest.py b/tests/buttonclicktest.py new file mode 100644 index 0000000..ded28b0 --- /dev/null +++ b/tests/buttonclicktest.py @@ -0,0 +1,27 @@ +### buttonClick seems to cause an "ESP" runtime error with Dragon 16. +# via unimacroutils it runs via natlink.execScript + +# via natlinkutils, the code runs (the right click is performed), but afterwards +# the "ESP" error is hit. + +# When Dragon is running, it freezes, and must be closed with the windows task manager + + +import natlink +from natlinkcore import natlinkutils +from dtactions.unimacro import unimacroutils + +if __name__ == "__main__": + try: + natlink.natConnect() + print('try a buttonclick') + unimacroutils.buttonClick('left', 2) + print('after the buttonClick') + + print('now via natlinkutils.buttonClick (right click)') + print('the code runs, but the "ESP" error window appears.') + natlinkutils.buttonClick('right', 1) + print('after the natlinkutils.buttonClick') + finally: + natlink.natDisconnect() +