diff --git a/mycroft/skills/core.py b/mycroft/skills/core.py index 255d54040c..a61dce2f4d 100644 --- a/mycroft/skills/core.py +++ b/mycroft/skills/core.py @@ -60,12 +60,29 @@ def load_vocab_from_file(path, vocab_type, emitter): 'alias_of': entity})) +def load_regex_from_file(path, emitter): + if(path.endswith('.rx')): + with open(path, 'r') as reg_file: + for line in reg_file.readlines(): + re.compile(line.strip()) + emitter.emit( + Message("register_vocab", + metadata={'regex': line.strip()})) + + def load_vocabulary(basedir, emitter): for vocab_type in os.listdir(basedir): load_vocab_from_file( join(basedir, vocab_type), splitext(vocab_type)[0], emitter) +def load_regex(basedir, emitter): + for regex_type in os.listdir(basedir): + if regex_type.endswith(".rx"): + load_regex_from_file( + join(basedir, regex_type), emitter) + + def create_intent_envelope(intent): return Message(None, metadata=intent.__dict__, context={}) @@ -225,6 +242,9 @@ class MycroftSkill(object): def load_vocab_files(self, vocab_dir): load_vocabulary(vocab_dir, self.emitter) + def load_regex_files(self, regex_dir): + load_regex(regex_dir, self.emitter) + def __handle_stop(self, event): self.stop_time = time.time() self.stop() diff --git a/mycroft/skills/date_time/__init__.py b/mycroft/skills/date_time/__init__.py index bf072f2951..a79a4397a6 100644 --- a/mycroft/skills/date_time/__init__.py +++ b/mycroft/skills/date_time/__init__.py @@ -37,9 +37,7 @@ class TimeSkill(MycroftSkill): def initialize(self): self.load_vocab_files(join(dirname(__file__), 'vocab', 'en-us')) - - self.register_regex("in (?P<Location>.*)") - self.register_regex("at (?P<Location>.*)") + self.load_regex_files(join(dirname(__file__), 'regex', 'en-us')) intent = IntentBuilder("TimeIntent").require( "TimeKeyword").optionally("Location").build() diff --git a/mycroft/skills/date_time/regex/en-us/location.rx b/mycroft/skills/date_time/regex/en-us/location.rx new file mode 100644 index 0000000000..b931a11a71 --- /dev/null +++ b/mycroft/skills/date_time/regex/en-us/location.rx @@ -0,0 +1 @@ +(at|in) (?P<Location>.*) \ No newline at end of file diff --git a/mycroft/skills/desktop_launcher/__init__.py b/mycroft/skills/desktop_launcher/__init__.py index 53bbb2cb49..7cd9116c41 100644 --- a/mycroft/skills/desktop_launcher/__init__.py +++ b/mycroft/skills/desktop_launcher/__init__.py @@ -16,7 +16,7 @@ # along with Mycroft Core. If not, see <http://www.gnu.org/licenses/>. -import os +from os.path import dirname, join import sys import urllib2 import webbrowser @@ -48,9 +48,8 @@ class DesktopLauncherSkill(MycroftSkill): logger.error("Could not import gio") return - vocab_dir = os.path.join(os.path.dirname(__file__), 'vocab', 'en-us') - - self.load_vocab_files(vocab_dir) + self.load_vocab_files(join(dirname(__file__), 'vocab', self.lang)) + self.load_regex_files(join(dirname(__file__), 'regex', self.lang)) tokenizer = EnglishTokenizer() for app in gio.app_info_get_all(): @@ -71,10 +70,6 @@ class DesktopLauncherSkill(MycroftSkill): else: self.appmap[tokenized_name] = entry - self.register_regex("for (?P<SearchTerms>.*)") - self.register_regex("for (?P<SearchTerms>.*) on") - self.register_regex("(?P<SearchTerms>.*) on") - launch_intent = IntentBuilder( "LaunchDesktopApplicationIntent").require("LaunchKeyword").require( "Application").build() diff --git a/mycroft/skills/desktop_launcher/regex/en-us/search.terms.rx b/mycroft/skills/desktop_launcher/regex/en-us/search.terms.rx new file mode 100644 index 0000000000..e4d0a6d182 --- /dev/null +++ b/mycroft/skills/desktop_launcher/regex/en-us/search.terms.rx @@ -0,0 +1,3 @@ +for (?P<SearchTerms>.*) +for (?P<SearchTerms>.*) on +(?P<SearchTerms>.*) on \ No newline at end of file diff --git a/mycroft/skills/dial_call/__init__.py b/mycroft/skills/dial_call/__init__.py index 510d09472a..fae4335e38 100644 --- a/mycroft/skills/dial_call/__init__.py +++ b/mycroft/skills/dial_call/__init__.py @@ -44,19 +44,13 @@ class DialCallSkill(MycroftSkill): 'sean': '34567890'} # TODO - Use API def initialize(self): - self.load_vocab_files(join(dirname(__file__), 'vocab', 'en-us')) - - prefixes = ['call', 'phone'] # TODO - i10n - self.__register_prefixed_regex(prefixes, "(?P<Contact>.*)") + self.load_vocab_files(join(dirname(__file__), 'vocab', self.lang)) + self.load_regex_files(join(dirname(__file__), 'regex', self.lang)) intent = IntentBuilder("DialCallIntent").require( "DialCallKeyword").require("Contact").build() self.register_intent(intent, self.handle_intent) - def __register_prefixed_regex(self, prefixes, suffix_regex): - for prefix in prefixes: - self.register_regex(prefix + ' ' + suffix_regex) - def handle_intent(self, message): try: contact = message.metadata.get("Contact").lower() diff --git a/mycroft/skills/dial_call/regex/en-us/contact.rx b/mycroft/skills/dial_call/regex/en-us/contact.rx new file mode 100644 index 0000000000..bf3aabd60c --- /dev/null +++ b/mycroft/skills/dial_call/regex/en-us/contact.rx @@ -0,0 +1 @@ +(call|phone) (?P<Contact>.*) \ No newline at end of file diff --git a/mycroft/skills/send_sms/__init__.py b/mycroft/skills/send_sms/__init__.py index 9550d9049d..a6f2b3a10c 100644 --- a/mycroft/skills/send_sms/__init__.py +++ b/mycroft/skills/send_sms/__init__.py @@ -41,20 +41,13 @@ class SendSMSSkill(MycroftSkill): 'sean': '34567890'} # TODO - Use API def initialize(self): - self.load_vocab_files(join(dirname(__file__), 'vocab', 'en-us')) - - prefixes = ['tell', 'text', 'message'] # TODO - i10n - self.__register_prefixed_regex( - prefixes, "(?P<Contact>\w*) (?P<Message>.*)") + self.load_vocab_files(join(dirname(__file__), 'vocab', self.lang)) + self.load_regex_files(join(dirname(__file__), 'regex', self.lang)) intent = IntentBuilder("SendSMSIntent").require( "SendSMSKeyword").require("Contact").require("Message").build() self.register_intent(intent, self.handle_intent) - def __register_prefixed_regex(self, prefixes, suffix_regex): - for prefix in prefixes: - self.register_regex(prefix + ' ' + suffix_regex) - def handle_intent(self, message): try: contact = message.metadata.get("Contact").lower() diff --git a/mycroft/skills/send_sms/regex/en-us/contact.message.rx b/mycroft/skills/send_sms/regex/en-us/contact.message.rx new file mode 100644 index 0000000000..bf1805af1c --- /dev/null +++ b/mycroft/skills/send_sms/regex/en-us/contact.message.rx @@ -0,0 +1 @@ +(tell|text|message) (?P<Contact>\w*) (?P<Message>.*) \ No newline at end of file diff --git a/mycroft/skills/send_sms/regex/en-us/message.rx b/mycroft/skills/send_sms/regex/en-us/message.rx new file mode 100644 index 0000000000..9cc97856a3 --- /dev/null +++ b/mycroft/skills/send_sms/regex/en-us/message.rx @@ -0,0 +1 @@ +(tell|text|message) \ No newline at end of file diff --git a/mycroft/skills/spelling/__init__.py b/mycroft/skills/spelling/__init__.py index 3920609e35..9c5eb0888a 100644 --- a/mycroft/skills/spelling/__init__.py +++ b/mycroft/skills/spelling/__init__.py @@ -35,20 +35,13 @@ class SpellingSkill(MycroftSkill): super(SpellingSkill, self).__init__(name="SpellingSkill") def initialize(self): - self.load_vocab_files(join(dirname(__file__), 'vocab', 'en-us')) - - prefixes = [ - 'spell', 'spell the word', 'spelling of', 'spelling of the word'] - self.__register_prefixed_regex(prefixes, "(?P<Word>\w+)") + self.load_vocab_files(join(dirname(__file__), 'vocab', self.lang)) + self.load_regex_files(join(dirname(__file__), 'regex', self.lang)) intent = IntentBuilder("SpellingIntent").require( "SpellingKeyword").require("Word").build() self.register_intent(intent, self.handle_intent) - def __register_prefixed_regex(self, prefixes, suffix_regex): - for prefix in prefixes: - self.register_regex(prefix + ' ' + suffix_regex) - def handle_intent(self, message): word = message.metadata.get("Word") self.emitter.once("recognizer_loop:audio_output_start", diff --git a/mycroft/skills/spelling/regex/en-us/word.rx b/mycroft/skills/spelling/regex/en-us/word.rx new file mode 100644 index 0000000000..b740999b96 --- /dev/null +++ b/mycroft/skills/spelling/regex/en-us/word.rx @@ -0,0 +1 @@ +(spell the word|spelling of the word|spelling of|spell) (?P<Word>\w+) \ No newline at end of file diff --git a/mycroft/skills/stock/__init__.py b/mycroft/skills/stock/__init__.py index 2106e960e8..13e642d2dc 100644 --- a/mycroft/skills/stock/__init__.py +++ b/mycroft/skills/stock/__init__.py @@ -15,7 +15,7 @@ # You should have received a copy of the GNU General Public License # along with Mycroft Core. If not, see <http://www.gnu.org/licenses/>. -from os.path import dirname +from os.path import dirname, join import requests import xml.etree.ElementTree as ET @@ -33,9 +33,7 @@ class StockSkill(MycroftSkill): def initialize(self): self.load_data_files(dirname(__file__)) - - self.register_regex("price of (?P<Company>.*)") - self.register_regex("is (?P<Company>.*) trading at") + self.load_regex_files(join(dirname(__file__), 'regex', self.lang)) stock_price_intent = IntentBuilder("StockPriceIntent")\ .require("StockPriceKeyword")\ diff --git a/mycroft/skills/stock/regex/en-us/company.rx b/mycroft/skills/stock/regex/en-us/company.rx new file mode 100644 index 0000000000..0093c5fb08 --- /dev/null +++ b/mycroft/skills/stock/regex/en-us/company.rx @@ -0,0 +1,2 @@ +price of (?P<Company>.*) +is (?P<Company>.*) trading at \ No newline at end of file diff --git a/mycroft/skills/volume/__init__.py b/mycroft/skills/volume/__init__.py index a4f13d7b24..1dd07d868f 100644 --- a/mycroft/skills/volume/__init__.py +++ b/mycroft/skills/volume/__init__.py @@ -18,7 +18,7 @@ import time from alsaaudio import Mixer -from os.path import dirname +from os.path import dirname, join from adapt.intent import IntentBuilder from mycroft.skills.core import MycroftSkill @@ -39,7 +39,7 @@ class VolumeSkill(MycroftSkill): def initialize(self): self.load_data_files(dirname(__file__)) - self.register_regex("(?P<VolumeAmount>\d+)") + self.load_regex_files(join(dirname(__file__), 'regex', self.lang)) self.__build_set_volume() def __build_set_volume(self): diff --git a/mycroft/skills/volume/regex/en-us/volume.amount.rx b/mycroft/skills/volume/regex/en-us/volume.amount.rx new file mode 100644 index 0000000000..22a23b0451 --- /dev/null +++ b/mycroft/skills/volume/regex/en-us/volume.amount.rx @@ -0,0 +1 @@ +(?P<VolumeAmount>\d+) \ No newline at end of file diff --git a/mycroft/skills/weather/__init__.py b/mycroft/skills/weather/__init__.py index 178a1e149d..f558f906dc 100644 --- a/mycroft/skills/weather/__init__.py +++ b/mycroft/skills/weather/__init__.py @@ -17,7 +17,7 @@ from adapt.intent import IntentBuilder -from os.path import dirname +from os.path import dirname, join from pyowm.exceptions.api_call_error import APICallError from mycroft.identity import IdentityManager @@ -42,8 +42,7 @@ class WeatherSkill(MycroftSkill): def initialize(self): self.load_data_files(dirname(__file__)) - self.register_regex("at (?P<Location>.*)") - self.register_regex("in (?P<Location>.*)") + self.load_regex_files(join(dirname(__file__), 'regex', self.lang)) self.__build_current_intent() self.__build_next_hour_intent() self.__build_next_day_intent() diff --git a/mycroft/skills/weather/regex/en-us/location.rx b/mycroft/skills/weather/regex/en-us/location.rx new file mode 100644 index 0000000000..b931a11a71 --- /dev/null +++ b/mycroft/skills/weather/regex/en-us/location.rx @@ -0,0 +1 @@ +(at|in) (?P<Location>.*) \ No newline at end of file diff --git a/mycroft/skills/wiki/__init__.py b/mycroft/skills/wiki/__init__.py index 880d199c0c..15bfbd3abb 100644 --- a/mycroft/skills/wiki/__init__.py +++ b/mycroft/skills/wiki/__init__.py @@ -46,20 +46,13 @@ class WikipediaSkill(MycroftSkill): 'FeedbackSearch.dialog')) def initialize(self): - self.load_vocab_files(join(dirname(__file__), 'vocab', 'en-us')) - - prefixes = ['wiki', 'wikipedia', 'tell me about', 'tell us about', - 'what does wikipedia say about'] # TODO - i10n - self.__register_prefixed_regex(prefixes, "(?P<ArticleTitle>.*)") + self.load_vocab_files(join(dirname(__file__), 'vocab', self.lang)) + self.load_regex_files(join(dirname(__file__), 'regex', self.lang)) intent = IntentBuilder("WikipediaIntent").require( "WikipediaKeyword").require("ArticleTitle").build() self.register_intent(intent, self.handle_intent) - def __register_prefixed_regex(self, prefixes, suffix_regex): - for prefix in prefixes: - self.register_regex(prefix + ' ' + suffix_regex) - def handle_intent(self, message): try: title = message.metadata.get("ArticleTitle") diff --git a/mycroft/skills/wiki/regex/en-us/article.title.rx b/mycroft/skills/wiki/regex/en-us/article.title.rx new file mode 100644 index 0000000000..f5c357b71a --- /dev/null +++ b/mycroft/skills/wiki/regex/en-us/article.title.rx @@ -0,0 +1 @@ +(tell us about|tell me about|what does wikipedia say about|wiki|wikipedia) (?P<ArticleTitle>.*) \ No newline at end of file diff --git a/test/regex_test/invalid/invalid.rx b/test/regex_test/invalid/invalid.rx new file mode 100644 index 0000000000..8e2f0bef13 --- /dev/null +++ b/test/regex_test/invalid/invalid.rx @@ -0,0 +1 @@ +[ \ No newline at end of file diff --git a/test/regex_test/invalid/none.rx b/test/regex_test/invalid/none.rx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/regex_test/valid/multiple.rx b/test/regex_test/valid/multiple.rx new file mode 100644 index 0000000000..c901b68e10 --- /dev/null +++ b/test/regex_test/valid/multiple.rx @@ -0,0 +1,2 @@ +(?P<MultipleTest1>.*) +(?P<MultipleTest2>.*) \ No newline at end of file diff --git a/test/regex_test/valid/single.rx b/test/regex_test/valid/single.rx new file mode 100644 index 0000000000..d2e5e3ffea --- /dev/null +++ b/test/regex_test/valid/single.rx @@ -0,0 +1 @@ +(?P<SingleTest>.*) \ No newline at end of file diff --git a/test/skills/core.py b/test/skills/core.py new file mode 100644 index 0000000000..f42a6c786b --- /dev/null +++ b/test/skills/core.py @@ -0,0 +1,92 @@ +import unittest +from re import error +from os.path import join, dirname, abspath + +from mycroft.skills.core import load_regex_from_file, load_regex +from mycroft.util.log import getLogger + +__author__ = 'eward' +logger = getLogger(__name__) + + +class MockEmitter(object): + def __init__(self): + self.reset() + + def emit(self, message): + self.types.append(message.message_type) + self.results.append(message.metadata['regex']) + + def get_types(self): + return self.types + + def get_results(self): + return self.results + + def reset(self): + self.types = [] + self.results = [] + + +class MycroftSkillTest(unittest.TestCase): + emitter = MockEmitter() + path = abspath(join(dirname(__file__), '../regex_test')) + + def check_load_from_file(self, filename, regex_list=[]): + load_regex_from_file(join(self.path, filename), self.emitter) + self.check_emitter(self.emitter, regex_list) + + def check_load(self, path, regex_list=[]): + load_regex(path, self.emitter) + self.check_emitter(self.emitter, regex_list) + + def check_emitter(self, emitter, regex_list): + for regex_type in emitter.get_types(): + self.assertEquals(regex_type, 'register_vocab') + if not regex_list: + self.assertEquals(emitter.get_results(), regex_list) + for value in regex_list: + self.assertTrue(value in emitter.get_results()) + self.emitter.reset() + + def test_load_regex_from_file_single(self): + self.check_load_from_file('valid/single.rx', + ['(?P<SingleTest>.*)']) + + def test_load_regex_from_file_multiple(self): + self.check_load_from_file('valid/multiple.rx', + ['(?P<MultipleTest1>.*)', + '(?P<MultipleTest2>.*)']) + + def test_load_regex_from_file_none(self): + self.check_load_from_file('invalid/none.rx') + + def test_load_regex_from_file_invalid(self): + try: + self.check_load_from_file('invalid/invalid.rx') + except error as e: + self.assertEquals(e.__str__(), + 'unexpected end of regular expression') + + def test_load_regex_from_file_does_not_exist(self): + try: + self.check_load_from_file('does_not_exist.rx') + except IOError as e: + self.assertEquals(e.strerror, 'No such file or directory') + + def test_load_regex_full(self): + self.check_load(join(self.path, 'valid'), + ['(?P<MultipleTest1>.*)', + '(?P<MultipleTest2>.*)', + '(?P<SingleTest>.*)']) + + def test_load_regex_empty(self): + self.check_load(join(dirname(__file__), + 'wolfram_alpha')) + + def test_load_regex_fail(self): + try: + self.check_load(join(dirname(__file__), + 'regex_test_fail')) + except OSError as e: + self.assertEquals(e.strerror, 'No such file or directory')