Add multiple language support for Rule Filters Simplified words.txt (or words.toml)
At the moment the simplified rule filters don't take into account which language DNS has loaded. For example, it would be beneficial to state that the replacement sauce -> up shall be done when an English profile is loaded and sauce -> hoch when a German profile is loaded. This would make it convenient to switch between different language profiles.
Related #233 #386
I'm trying to tackle this issue but I am running into problem.
I expanded the syntax of words.txt such that sections of different languages are supported. I successfully modified this part of gfilter.py which parses words.txt such that when I start DNS and select an English profile the english section of words.txt is considered and when I start DNS and select a German profile the german section of words.txt is considered. Simplified Rule Filters work regarding this "DNS isn't running, start DNS and chose a profile of your choice" scenario. However, please ignore this scenario for the moment.
What I'm struggling with is the scenario of changing the profile without restarting Dargon. You do so by clicking Profile -> Open User Profile in the DNS GUI. To reproduce this you don't need a German profile, two English profiles work as well.
When a different profile is chosen the _caster.py module is reloaded. However, rules are added by executing code within modules by importing them. When reloading _caster.py these modules are already imported, they aren't reloaded automatically. Therefore rules aren't reloaded as well, they stay the same. However, I need them to change depending on the loaded profile's language.
As far as I can tell there are two ways rules are are added in Caster:
- By putting them into a grammar and calling
grammar.load()(e.g. here) - By calling the
add_global_rule()/add_app_rule()/add_selfmodrule()functions oncontrol.nexus().merger(e.g. here)
For the moment let's concentrate on 1). My idea was to unload all grammars, get all rules, run the run_on() function from gfilter, assmble and reload grammars. To achieve this consider the following lines of code
import dragonfly.engines.backend_natlink as natlink
if natlink.is_engine_available():
grammars_list = natlink.get_engine().grammars
for grammar in grammars_list:
grammar.unload()
for rule in grammar._rules:
grammar.remove_rule(rule)
if isinstance(rule, MergeRule):
print(rule)
#gfilter.run_on(rule)
grammar.add_rule(rule)
grammar.load()
I added at this position in _caster.py. When I start DNS and English profile no. 1 I get the follwing result in the Natlink Window:
HMCRule(HMCRule)
DragonRule(dragon)
GridControlRule(rainbow)
HMCSettingsRule(HMCSettingsRule)
IERule(explorer)
FoxitRule(Foxit Reader)
FoxitRule(Foxit Reader)
GitterRule(gitter)
LaunchRule(LaunchRule)
HMCDirectoryRule(HMCDirectoryRule)
NPPRule(notepad plus plus)
NPPRule(notepad plus plus)
FirefoxRule(firefox)
FirefoxRule(firefox)
HMCConfirmRule(HMCConfirmRule)
MainRule(MainRule)
Again(repeat that)
Again(repeat that)
FileDialogueRule(filedialogue)
FileDialogueRule(filedialogue)
HMCHistoryRule(HMCHistoryRule)
GridControlRule(Douglas)
Now if I switch to Englisch profile no. 2 from within the GUI I get the following output:
MergeRule(Merged57Mh)
MergeRule(Merged57Mh)
MergeRule(Merged57Mh)
MergeRule(Merged57Mh)
LaunchRule(LaunchRule)
DragonRule(dragon)
BringRule(BringRule)
HMCDirectoryRule(HMCDirectoryRule)
IERule(explorer)
MergeRule(Merged59MG)
HMCSettingsRule(HMCSettingsRule)
GitterRule(gitter)
MainRule(MainRule)
Again(repeat that)
Again(repeat that)
HMCRule(HMCRule)
HMCConfirmRule(HMCConfirmRule)
If you compare the outputs you can see that they differ. E.g. the GitterRule rule appears in both. However, the FirefoxRule appears in only one. Because of that GitterRule would be modified successfully by apllying the run_on function when you switch profiles, but FirefoxRule wouldn't because it's not accessible in the first place. Do you have any idea why I get different outputs?
Fundamentally what you're actually tackling is reloading of grammars without restarting Dragon NaturallySpeaking. Which would inherently solve the issue with switching profiles.
The above issue is something beyond my abilities to really tackle. However I do have a few thoughts.
- I see duplicate grammars being loaded in your first column. Which means your function is being run twice I think. I recommend installing the
dragonfly2 0.15.0or greater.
I use the following code in configdebug.txt
import threading
from castervoice.lib.imports import *
from dragonfly.engines import get_engine
from dragonfly.rpc.server import RPCServer
import json
import requests
#RPC server
token = 'wP3inO2ymf6pDOASu6y3HWVOgdoi9rq5XEBvJt/jxZE='
server = RPCServer(security_token=token)
def rpc_client():
url = "http://127.0.0.1:50051/jsonrpc"
headers = {'content-type': 'application/json'}
payload = {
# Method to call.
"method": "list_grammars",
"params": {"security_token": "wP3inO2ymf6pDOASu6y3HWVOgdoi9rq5XEBvJt/jxZE="},
"jsonrpc": "2.0",
"id": 0,
}
response = requests.post(url, data=json.dumps(payload), headers=headers).json()
return response
def start_sever():
server.start()
def unload_server():
server.stop()
def list_active_grammars():
def f(): # Prints active grammars
response = rpc_client()
print (response)
grammars = response["result"]
for grammar in grammars:
for rule in grammar["rules"]:
if rule["exported"] and rule["active"] == True:
print (rule["name"])
t = threading.Thread(target=f)
t.start()
def list_specific_grammars():
def f(): # Prints active grammars
response = rpc_client()
grammars = response["result"]
for grammar in grammars:
for rule in grammar["rules"]:
if rule["exported"]:
if "Core: DevDevelopmentHelpRule" in rule["name"]:
print rule["name"]
print rule
t = threading.Thread(target=f)
t.start()
def list_all_grammars():
def f(): # Prints all grammars
response = rpc_client()
grammars = response["result"]
for grammar in grammars:
for rule in grammar["rules"]:
if rule["exported"]:
print(rule["name"])
t = threading.Thread(target=f)
t.start()
def generate_token():
from dragonfly.rpc.security import generate_security_token
print(generate_security_token())
cmd.map = {
# Spoken-form -> -> -> Action object
"some command goes here": R(Pause("100"), rdescript="test command"),
"list specific grammar":
R(Function(list_specific_grammars), rdescript=" List Specific Grammars RPC Sever"),
"list all grammars":
R(Function(list_all_grammars), rdescript=" List All Grammars RPC Sever"),
"list active grammars":
R(Function(list_active_grammars), rdescript=" List Active Grammars RPC Sever"),
"generate token":
R(Function(generate_token), rdescript="Generate Security Token for RPC Sever"),
"start rpc":
R(Function(start_sever), rdescript="Start RPC Sever"),
"(unload | stop) rpc":
R(Function(unload_server), rdescript="Stop RPC Sever"),
# more...
}
cmd.extras = [
IntegerRefST("n", 1, 1000),
]
cmd.defaults = {
"n": 1,
}
Output of list_all_grammars
lyx
FlashDevelopRule
command prompt
Foxit Reader
M S V C
sikuli server
HMCConfirmRule
kdiff3
HMCDirectoryRule
firefox
emacs
jet brains
HMCHistoryRule
filedialogue
LaunchRule
excel
Repeater102
Merged94Mh
Douglas
microsoft word
MappingRule
explorer
rstudio
dragon
NavigationNon
_anonrule_000_Rule
MainRule
BringRule
repeat that
Alias
legion
eclipse
EclipseCCR
visualstudio
outlook
Repeater105
Merged100MV
Repeater104
Merged98MG
ssms
sql developer
sublime
rainbow
StackTest
DevelopmentHelp
Experimental
HMCRule
gitter
notepad plus plus
atom
githubdesktop
fman
Repeater103
Merged96MF
typora
acrobat
chrome
totalcmd
HMCSettingsRule
totalcmd sync dirs
- A low-tech solution is I could create a reboot script that can load profiles automatically by command.
so you could say reboot Dragon
profile name 1orprofile name 2. Not as ideal or quick but functional by voice at least.
I hope this is helpful and perhaps others might have more insights as well.
Thank you for your feedback and your offer. However, another workaround came to my mind which could work for the time being. I could use words.txt like this:
english command -> (english command | german command)
This would increase complexity of rules but should be quick.
I can't replace all specs in their entirety, e.g.
zoom in [<n>] -> (zoom in | zoomen) [<n>]
doesn't work because of [<n>], but
zoom in -> (zoom in | zoomen)
does. I'll test how this approach will work out.
Neat, also take a look at https://github.com/dictation-toolbox/Caster/blob/c69094a3750368bd71b1f93cf24dd56479325779/caster/user/filters/examples/scen4.py#L27
The workaround I described above doesn't work reliably when switchting profiles from within Dragon. I'll have to investigate why. It works reliable when DNS is started from scratch and different profile / language is selected.
Therefore in the meantime I'd like to return to
A low-tech solution is I could create a reboot script that can load profiles automatically by command. so you could say reboot Dragon profile name 1 or profile name 2. Not as ideal or quick but functional by voice at least.
and ask if this is still an option?
@seekM Yes that's still an option. I'm working on the unified reboot script. It's slow because I'm busy with academics. Once I had the basics for the unified reboot script I'll add features such as this.
As a side note I've been in contact with David who will work on reloading grammars which should be done by mid July.
@seekM
Transformers have been relegated only to simplified transformers(previously simplified filter rules). They didn't really work out in practice at least for the end-user modifications. It took as much or more effort to write these transformers as it did to create a whole new grammar. Transformers would need a higher level of abstraction to be and user-friendly and efficient.
As for switching profiles that would have to be a new function for restarting Dragon is no longer needed. We can actually restart Caster with all its grammars and modules by command leaving Dragon itself untouched.