Skip to content

Commit

Permalink
Improve import natlink (#41)
Browse files Browse the repository at this point in the history
* added module natlinktimer (multiplexing natlink.setTimerCallback), and added natlinkstatus: getUnimacroDataDirectory
* test script: test_natlinktimer.py
* working on natlinktimer, add callback at mic off in loader
* improve natlinktimer, when mic switches off!
Co-authored-by: Doug Ransom <[email protected]>
  • Loading branch information
quintijn authored Dec 22, 2022
1 parent 80747f6 commit 9097699
Show file tree
Hide file tree
Showing 16 changed files with 874 additions and 29 deletions.
4 changes: 4 additions & 0 deletions build_natlinkcore.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#powershell to run the tests, then build the python package.
$ErrorActionPreference = "Stop"
pytest --capture=tee-sys
flit build --format sdist
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,12 @@ testpaths= [

[project.scripts]
natlinkconfig_cli = "natlinkcore.configure.natlinkconfig_cli:main_cli"
natlink_extensions = "natlinkcore.configure.natlink_extensions:main"
[project.gui-scripts]
natlinkconfig_gui = "natlinkcore.configure.natlinkconfig_gui:main_gui"

[project.entry-points.natlink_extensions]
natlink_sample_macros = "natlinkcore.SampleMacros:locateme"

[project.urls]
Home = "https://github.com/dictation-toolbox/natlinkcore/"
2 changes: 2 additions & 0 deletions src/natlinkcore/SampleMacros/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def locateme():
return __path__[0]
2 changes: 1 addition & 1 deletion src/natlinkcore/SampleMacros/_debug_natlink.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"""
#pylint:disable=W0611, W0613, C0115, C0116

from natlink import _natlink_core as natlink
import natlink
from natlinkcore import natlinkpydebug as pd
from natlinkcore import natlinkutils
from natlinkcore import gramparser as gp
Expand Down
4 changes: 4 additions & 0 deletions src/natlinkcore/SampleMacros/_sample_callback.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,16 @@ def post_load_callback(self):
print('------- post_load_callback')
def on_mic_on_callback(self):
print('------- on_mic_on_callback')
def on_mic_off_callback(self):
print('------- on_mic_off_callback')
def on_begin_utterance_callback(self):
print('------- on_begin_utterance_callback')

thisGrammar = ThisGrammar()
thisGrammar.initialize()
natlinkmain.set_on_begin_utterance_callback(thisGrammar.on_begin_utterance_callback)
natlinkmain.set_on_mic_on_callback(thisGrammar.on_mic_on_callback)
natlinkmain.set_on_mic_off_callback(thisGrammar.on_mic_off_callback)
natlinkmain.set_pre_load_callback(thisGrammar.pre_load_callback)
natlinkmain.set_post_load_callback(thisGrammar.post_load_callback)

Expand All @@ -36,6 +39,7 @@ def unload():
if thisGrammar:
natlinkmain.delete_on_begin_utterance_callback(thisGrammar.on_begin_utterance_callback)
natlinkmain.delete_on_mic_on_callback(thisGrammar.on_mic_on_callback)
natlinkmain.delete_on_mic_off_callback(thisGrammar.on_mic_off_callback)
natlinkmain.delete_pre_load_callback(thisGrammar.pre_load_callback)
natlinkmain.delete_post_load_callback(thisGrammar.post_load_callback)
# extraneous deletes do not harm:
Expand Down
31 changes: 21 additions & 10 deletions src/natlinkcore/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from enum import IntEnum
from typing import List, Iterable, Dict
from pathlib import Path
from natlink import _natlink_core as natlink
import natlink

class NoGoodConfigFoundException(natlink.NatError):
pass
Expand Down Expand Up @@ -143,7 +143,7 @@ def expand_path(input_path: str) -> str:
Paths can be:
- the name of a python package, to be found along sys.path (typically in site-packages)
- the name of a python package, or a sub directory of a python package
- natlink_userdir/...: the directory where natlink.ini is is searched for, either %(NATLINK_USERDIR) or ~/.natlink
- ~/...: the home directory
- some environment variable: this environment variable is expanded.
Expand Down Expand Up @@ -180,15 +180,26 @@ def expand_path(input_path: str) -> str:
print(f'natlink_userdir does not expand to a valid directory: "{nud}"')
return normpath(nud)

if not (input_path.find('/') >= 0 or input_path.find('\\') >= 0):

# 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.
try:
pack = __import__(input_path)
except ModuleNotFoundError:
print(f'expand_path, package name "{input_path}" is not found')
return input_path
return pack.__path__[0]

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

env_expanded = expandvars(input_path)
# print(f'env_expanded: "{env_expanded}", from envvar: "{input_path}"')
return normpath(env_expanded)
Expand Down
1 change: 1 addition & 0 deletions src/natlinkcore/configure/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from natlinkcore.configure.natlink_extensions import extensions_and_folders
29 changes: 29 additions & 0 deletions src/natlinkcore/configure/natlink_extensions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""Command Line Program to List Advertised Natlink Exensions."""
import sys
from importlib.metadata import entry_points
import argparse

def extensions_and_folders():
discovered_extensions=entry_points(group='natlink_extensions')

for extension in discovered_extensions:
n=extension.name
ep=extension.value
try:
pathfn=extension.load()
path=pathfn()

except Exception as e:
path = e
yield n,path


def main():
parser=argparse.ArgumentParser(description="Enumerate natlink extension momdules.")
args=parser.parse_args()
for n,path in extensions_and_folders():
print(f"{n} {path}")
return 0

if '__main__' == __name__:
main()
12 changes: 11 additions & 1 deletion src/natlinkcore/configure/natlinkconfig_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import cmd
import os
import os.path
from natlinkcore.configure import extensions_and_folders

from natlinkcore.configure import natlinkconfigfunctions

Expand All @@ -18,7 +19,7 @@ def _main(Options=None):
"""
cli = CLI()
cli.Config = natlinkconfigfunctions.NatlinkConfig()
shortOptions = "DVNOHKaAiIxXbBuq"
shortOptions = "DVNOHKaAiIxXbBuqe"
shortArgOptions = "d:v:n:o:h:k:"
if Options:
if isinstance(Options, str):
Expand Down Expand Up @@ -129,6 +130,8 @@ def usage(self):
[AutoHotkey]
h/H - set/clear the AutoHotkey exe directory.
k/K - set/clear the User Directory for AutoHotkey scripts.
[Extensions]
e - give a list of python modules registered as extensions.
[Other]
u/usage - give this list
Expand All @@ -153,6 +156,13 @@ def do_j(self, arg):

self.Config.printPythonPath()

def do_e(self,arg):
print("extensions and folders for registered natlink extensions:")
ef=""
for n,f in extensions_and_folders():
ef+= f"\n{n} {f}"
print(ef)

def help_i(self):
print('-'*60)
print("""The command info (i) gives an overview of the settings that are
Expand Down
12 changes: 11 additions & 1 deletion src/natlinkcore/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from types import ModuleType
from typing import List, Dict, Set, Iterable, Any, Tuple, Callable

from natlink import _natlink_core as natlink
import natlink
from natlinkcore.config import LogLevel, NatlinkConfig, expand_path
from natlinkcore.readwritefile import ReadWriteFile
from natlinkcore.callbackhandler import CallbackHandler
Expand Down Expand Up @@ -65,6 +65,7 @@ def __init__(self, logger: Any=None, config: Any = None):
self._pre_load_callback = CallbackHandler('pre_load')
self._post_load_callback = CallbackHandler('post_load')
self._on_mic_on_callback = CallbackHandler('on_mic_on')
self._on_mic_off_callback = CallbackHandler('on_mic_off')
self._on_begin_utterance_callback = CallbackHandler('on_begin_utterance')
self.seen: Set[Path] = set() # start empty in trigger_load
self.bom = self.encoding = self.config_text = '' # getconfigsetting and writeconfigsetting
Expand All @@ -76,6 +77,9 @@ def set_on_begin_utterance_callback(self, func: Callable[[], None]) -> None:
def set_on_mic_on_callback(self, func: Callable[[], None]) -> None:
self._on_mic_on_callback.set(func)

def set_on_mic_off_callback(self, func: Callable[[], None]) -> None:
self._on_mic_off_callback.set(func)

def set_pre_load_callback(self, func: Callable[[], None]) -> None:
self._pre_load_callback.set(func)

Expand All @@ -88,6 +92,9 @@ def delete_on_begin_utterance_callback(self, func: Callable[[], None]) -> None:
def delete_on_mic_on_callback(self, func: Callable[[], None]) -> None:
self._on_mic_on_callback.delete(func)

def delete_on_mic_off_callback(self, func: Callable[[], None]) -> None:
self._on_mic_off_callback.delete(func)

def delete_pre_load_callback(self, func: Callable[[], None]) -> None:
self._pre_load_callback.delete(func)

Expand Down Expand Up @@ -401,6 +408,9 @@ def on_change_callback(self, change_type: str, args: Any) -> None:

if self.config.load_on_mic_on:
self.trigger_load()
elif change_type == 'mic' and args == 'off':
self.logger.debug('on_change_callback called with: "mic", "off"')
self._on_mic_off_callback.run()
else:
self.logger.debug(f'on_change_callback unhandled: change_type: "{change_type}", args: "{args}"')

Expand Down
48 changes: 34 additions & 14 deletions src/natlinkcore/natlinkstatus.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,18 @@
Also users that have their own custom grammar files can use this user directory
getUnimacroDirectory: get the directory where the Unimacro system is.
When git cloned, relative to the Core directory, otherwise somewhere or in the site-packages (if pipped). This grammar will (and should) hold the _control.py grammar
and needs to be included in the load directories list of James' natlinkmain
This directory is normally in the site-packages area of Python (name "unimacro"), but can be
"linked" to your cloned source code when you installed the packages with "pip install -e ."
getUnimacroGrammarsDirectory: get the directory, where the user can put his Unimacro grammars. This directory will be
located in the `ActiveGrammars` subdirectory of the `~/.unimacro' or `%NATLINK_USERDIR%/.unimacro`).
getUnimacroUserDirectory: get the directory of Unimacro INI files, if not return '' or
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
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.
Expand Down Expand Up @@ -120,7 +123,6 @@
"esp": "may\xfas"}

thisDir, thisFile = os.path.split(__file__)
thisDirSymlink = natlinkcore.getThisDir(__file__)

class NatlinkStatus(metaclass=singleton.Singleton):
"""this class holds the Natlink status functions.
Expand All @@ -138,7 +140,7 @@ class NatlinkStatus(metaclass=singleton.Singleton):
'vocoladirectory', 'vocolagrammarsdirectory']

def __init__(self):
"""initialise all instance variables, in this singleton class, hoeinstance
"""initialise all instance variables, in this singleton class, (only one instance)
"""
self.natlinkmain = natlinkmain # global
self.DNSVersion = None
Expand All @@ -152,6 +154,7 @@ def __init__(self):
self.UnimacroDirectory = None
self.UnimacroUserDirectory = None
self.UnimacroGrammarsDirectory = None
self.UnimacroDataDirectory = None
## Vocola:
self.VocolaUserDirectory = None
self.VocolaDirectory = None
Expand All @@ -165,10 +168,9 @@ def __init__(self):
self.symlink_line = ''

if self.NatlinkDirectory is None:
self.NatlinkDirectory = natlink.__path__[0]
self.NatlinkcoreDirectory = thisDirSymlink # equal to thisDir if no symlinking is there.
if thisDirSymlink != thisDir:
self.symlink_line = f'NatlinkcoreDirectory is symlinked, for developing purposes.\n\tFiles seem to be in "{thisDirSymlink}",\n\tbut they can be edited in "{thisDir}".\n\tWhen debugging files from this directory, open and set breakpoints in files in the first (site-packages) directory!'
self.NatlinkDirectory = natlink.__path__[-1]
if len(natlinkcore.__path__) > 0:
self.symlink_line = 'NatlinkcoreDirectory is editable'

def refresh(self):
"""rerun the __init__, refreshing all variables
Expand Down Expand Up @@ -399,10 +401,30 @@ def getUnimacroDirectory(self):
except ImportError:
self.UnimacroDirectory = ""
return ""
self.UnimacroDirectory = str(Path(unimacro.__file__).parent)
self.UnimacroDirectory = unimacro.__path__[-1]
return self.UnimacroDirectory


def getUnimacroDataDirectory(self):
"""return the path to the directory where grammars can store data.
Expected in "UnimacroData" of the natlink user directory
(November 2022)
"""
if self.UnimacroDataDirectory is not None:
return self.UnimacroDataDirectory

natlink_user_dir = self.getNatlink_Userdir()

um_data_dir = Path(natlink_user_dir)/'UnimacroData'
if not um_data_dir.is_dir():
um_data_dir.mkdir()
um_data_dir = str(um_data_dir)
self.UnimacroDataDirectory = um_data_dir

return um_data_dir

def getUnimacroGrammarsDirectory(self):
"""return the path to the directory where the ActiveGrammars of Unimacro are located.
Expand Down Expand Up @@ -539,7 +561,6 @@ def getVocolaUserDirectory(self):
return ''

def getVocolaDirectory(self):
isdir, isfile, join, abspath = os.path.isdir, os.path.isfile, os.path.join, os.path.abspath
if self.VocolaDirectory is not None:
return self.VocolaDirectory

Expand All @@ -548,7 +569,7 @@ def getVocolaDirectory(self):
except ImportError:
self.VocolaDirectory = ''
return ''
self.VocolaDirectory = str(Path(vocola2.__file__).parent)
self.VocolaDirectory = vocola2.__path__[-1]
return self.VocolaDirectory


Expand Down Expand Up @@ -693,7 +714,7 @@ def getNatlinkStatusDict(self):
for key in ['DNSIniDir', 'WindowsVersion', 'DNSVersion',
'PythonVersion',
'DNSName', 'NatlinkIni', 'Natlink_Userdir',
'UnimacroDirectory', 'UnimacroUserDirectory', 'UnimacroGrammarsDirectory',
'UnimacroDirectory', 'UnimacroUserDirectory', 'UnimacroGrammarsDirectory', 'UnimacroDataDirectory',
'VocolaDirectory', 'VocolaUserDirectory', 'VocolaGrammarsDirectory',
'VocolaTakesLanguages', 'VocolaTakesUnimacroActions',
'UserDirectory',
Expand Down Expand Up @@ -726,7 +747,6 @@ def getNatlinkStatusString(self):
L = []
D = self.getNatlinkStatusDict()
if self.symlink_line:
L.append('--- warning:')
L.append(self.symlink_line)
L.append('--- properties:')
self.appendAndRemove(L, D, 'user')
Expand Down Expand Up @@ -766,7 +786,7 @@ def getNatlinkStatusString(self):
## Unimacro:
if D['unimacroIsEnabled']:
self.appendAndRemove(L, D, 'unimacroIsEnabled', "---Unimacro is enabled")
for key in ('UnimacroUserDirectory', 'UnimacroDirectory', 'UnimacroGrammarsDirectory'):
for key in ('UnimacroUserDirectory', 'UnimacroDirectory', 'UnimacroGrammarsDirectory', 'UnimacroDataDirectory'):
self.appendAndRemove(L, D, key)
else:
self.appendAndRemove(L, D, 'unimacroIsEnabled', "---Unimacro is disabled")
Expand Down
Loading

0 comments on commit 9097699

Please sign in to comment.