rewrite hangouts to use intents instead of commands (#16220)
* rewrite hangouts to use intents instead of commands * small fixes * remove configured_hangouts check and CONFIG_SCHEMA * Lint * add import from .config_flowpull/16232/head
parent
6f0c30ff84
commit
45649824ca
|
@ -11,6 +11,7 @@ import voluptuous as vol
|
|||
|
||||
from homeassistant import core
|
||||
from homeassistant.components import http
|
||||
from homeassistant.components.conversation.util import create_matcher
|
||||
from homeassistant.components.http.data_validator import (
|
||||
RequestDataValidator)
|
||||
from homeassistant.components.cover import (INTENT_OPEN_COVER,
|
||||
|
@ -74,7 +75,7 @@ def async_register(hass, intent_type, utterances):
|
|||
if isinstance(utterance, REGEX_TYPE):
|
||||
conf.append(utterance)
|
||||
else:
|
||||
conf.append(_create_matcher(utterance))
|
||||
conf.append(create_matcher(utterance))
|
||||
|
||||
|
||||
async def async_setup(hass, config):
|
||||
|
@ -91,7 +92,7 @@ async def async_setup(hass, config):
|
|||
if conf is None:
|
||||
conf = intents[intent_type] = []
|
||||
|
||||
conf.extend(_create_matcher(utterance) for utterance in utterances)
|
||||
conf.extend(create_matcher(utterance) for utterance in utterances)
|
||||
|
||||
async def process(service):
|
||||
"""Parse text into commands."""
|
||||
|
@ -146,39 +147,6 @@ async def async_setup(hass, config):
|
|||
return True
|
||||
|
||||
|
||||
def _create_matcher(utterance):
|
||||
"""Create a regex that matches the utterance."""
|
||||
# Split utterance into parts that are type: NORMAL, GROUP or OPTIONAL
|
||||
# Pattern matches (GROUP|OPTIONAL): Change light to [the color] {name}
|
||||
parts = re.split(r'({\w+}|\[[\w\s]+\] *)', utterance)
|
||||
# Pattern to extract name from GROUP part. Matches {name}
|
||||
group_matcher = re.compile(r'{(\w+)}')
|
||||
# Pattern to extract text from OPTIONAL part. Matches [the color]
|
||||
optional_matcher = re.compile(r'\[([\w ]+)\] *')
|
||||
|
||||
pattern = ['^']
|
||||
for part in parts:
|
||||
group_match = group_matcher.match(part)
|
||||
optional_match = optional_matcher.match(part)
|
||||
|
||||
# Normal part
|
||||
if group_match is None and optional_match is None:
|
||||
pattern.append(part)
|
||||
continue
|
||||
|
||||
# Group part
|
||||
if group_match is not None:
|
||||
pattern.append(
|
||||
r'(?P<{}>[\w ]+?)\s*'.format(group_match.groups()[0]))
|
||||
|
||||
# Optional part
|
||||
elif optional_match is not None:
|
||||
pattern.append(r'(?:{} *)?'.format(optional_match.groups()[0]))
|
||||
|
||||
pattern.append('$')
|
||||
return re.compile(''.join(pattern), re.I)
|
||||
|
||||
|
||||
async def _process(hass, text):
|
||||
"""Process a line of text."""
|
||||
intents = hass.data.get(DOMAIN, {})
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
"""Util for Conversation."""
|
||||
import re
|
||||
|
||||
|
||||
def create_matcher(utterance):
|
||||
"""Create a regex that matches the utterance."""
|
||||
# Split utterance into parts that are type: NORMAL, GROUP or OPTIONAL
|
||||
# Pattern matches (GROUP|OPTIONAL): Change light to [the color] {name}
|
||||
parts = re.split(r'({\w+}|\[[\w\s]+\] *)', utterance)
|
||||
# Pattern to extract name from GROUP part. Matches {name}
|
||||
group_matcher = re.compile(r'{(\w+)}')
|
||||
# Pattern to extract text from OPTIONAL part. Matches [the color]
|
||||
optional_matcher = re.compile(r'\[([\w ]+)\] *')
|
||||
|
||||
pattern = ['^']
|
||||
for part in parts:
|
||||
group_match = group_matcher.match(part)
|
||||
optional_match = optional_matcher.match(part)
|
||||
|
||||
# Normal part
|
||||
if group_match is None and optional_match is None:
|
||||
pattern.append(part)
|
||||
continue
|
||||
|
||||
# Group part
|
||||
if group_match is not None:
|
||||
pattern.append(
|
||||
r'(?P<{}>[\w ]+?)\s*'.format(group_match.groups()[0]))
|
||||
|
||||
# Optional part
|
||||
elif optional_match is not None:
|
||||
pattern.append(r'(?:{} *)?'.format(optional_match.groups()[0]))
|
||||
|
||||
pattern.append('$')
|
||||
return re.compile(''.join(pattern), re.I)
|
|
@ -11,28 +11,56 @@ import voluptuous as vol
|
|||
from homeassistant import config_entries
|
||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
|
||||
from homeassistant.helpers import dispatcher
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
from .config_flow import configured_hangouts
|
||||
from .const import (
|
||||
CONF_BOT, CONF_COMMANDS, CONF_REFRESH_TOKEN, DOMAIN,
|
||||
CONF_BOT, CONF_INTENTS, CONF_REFRESH_TOKEN, DOMAIN,
|
||||
EVENT_HANGOUTS_CONNECTED, EVENT_HANGOUTS_CONVERSATIONS_CHANGED,
|
||||
MESSAGE_SCHEMA, SERVICE_SEND_MESSAGE,
|
||||
SERVICE_UPDATE)
|
||||
SERVICE_UPDATE, CONF_SENTENCES, CONF_MATCHERS,
|
||||
CONF_ERROR_SUPPRESSED_CONVERSATIONS, INTENT_SCHEMA, TARGETS_SCHEMA)
|
||||
|
||||
# We need an import from .config_flow, without it .config_flow is never loaded.
|
||||
from .config_flow import HangoutsFlowHandler # noqa: F401
|
||||
|
||||
|
||||
REQUIREMENTS = ['hangups==0.4.5']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: vol.Schema({
|
||||
vol.Optional(CONF_INTENTS, default={}): vol.Schema({
|
||||
cv.string: INTENT_SCHEMA
|
||||
}),
|
||||
vol.Optional(CONF_ERROR_SUPPRESSED_CONVERSATIONS, default=[]):
|
||||
[TARGETS_SCHEMA]
|
||||
})
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
|
||||
async def async_setup(hass, config):
|
||||
"""Set up the Hangouts bot component."""
|
||||
config = config.get(DOMAIN, {})
|
||||
hass.data[DOMAIN] = {CONF_COMMANDS: config.get(CONF_COMMANDS, [])}
|
||||
from homeassistant.components.conversation import create_matcher
|
||||
|
||||
if configured_hangouts(hass) is None:
|
||||
hass.async_add_job(hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={'source': config_entries.SOURCE_IMPORT}
|
||||
))
|
||||
config = config.get(DOMAIN)
|
||||
if config is None:
|
||||
return True
|
||||
|
||||
hass.data[DOMAIN] = {CONF_INTENTS: config.get(CONF_INTENTS),
|
||||
CONF_ERROR_SUPPRESSED_CONVERSATIONS:
|
||||
config.get(CONF_ERROR_SUPPRESSED_CONVERSATIONS)}
|
||||
|
||||
for data in hass.data[DOMAIN][CONF_INTENTS].values():
|
||||
matchers = []
|
||||
for sentence in data[CONF_SENTENCES]:
|
||||
matchers.append(create_matcher(sentence))
|
||||
|
||||
data[CONF_MATCHERS] = matchers
|
||||
|
||||
hass.async_add_job(hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={'source': config_entries.SOURCE_IMPORT}
|
||||
))
|
||||
|
||||
return True
|
||||
|
||||
|
@ -47,7 +75,8 @@ async def async_setup_entry(hass, config):
|
|||
bot = HangoutsBot(
|
||||
hass,
|
||||
config.data.get(CONF_REFRESH_TOKEN),
|
||||
hass.data[DOMAIN][CONF_COMMANDS])
|
||||
hass.data[DOMAIN][CONF_INTENTS],
|
||||
hass.data[DOMAIN][CONF_ERROR_SUPPRESSED_CONVERSATIONS])
|
||||
hass.data[DOMAIN][CONF_BOT] = bot
|
||||
except GoogleAuthError as exception:
|
||||
_LOGGER.error("Hangouts failed to log in: %s", str(exception))
|
||||
|
@ -62,6 +91,10 @@ async def async_setup_entry(hass, config):
|
|||
hass,
|
||||
EVENT_HANGOUTS_CONVERSATIONS_CHANGED,
|
||||
bot.async_update_conversation_commands)
|
||||
dispatcher.async_dispatcher_connect(
|
||||
hass,
|
||||
EVENT_HANGOUTS_CONVERSATIONS_CHANGED,
|
||||
bot.async_handle_update_error_suppressed_conversations)
|
||||
|
||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP,
|
||||
bot.async_handle_hass_stop)
|
||||
|
|
|
@ -4,7 +4,6 @@ import logging
|
|||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.notify import ATTR_MESSAGE, ATTR_TARGET
|
||||
from homeassistant.const import CONF_NAME
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
_LOGGER = logging.getLogger('homeassistant.components.hangouts')
|
||||
|
@ -18,17 +17,18 @@ CONF_BOT = 'bot'
|
|||
|
||||
CONF_CONVERSATIONS = 'conversations'
|
||||
CONF_DEFAULT_CONVERSATIONS = 'default_conversations'
|
||||
CONF_ERROR_SUPPRESSED_CONVERSATIONS = 'error_suppressed_conversations'
|
||||
|
||||
CONF_COMMANDS = 'commands'
|
||||
CONF_WORD = 'word'
|
||||
CONF_EXPRESSION = 'expression'
|
||||
|
||||
EVENT_HANGOUTS_COMMAND = 'hangouts_command'
|
||||
CONF_INTENTS = 'intents'
|
||||
CONF_INTENT_TYPE = 'intent_type'
|
||||
CONF_SENTENCES = 'sentences'
|
||||
CONF_MATCHERS = 'matchers'
|
||||
|
||||
EVENT_HANGOUTS_CONNECTED = 'hangouts_connected'
|
||||
EVENT_HANGOUTS_DISCONNECTED = 'hangouts_disconnected'
|
||||
EVENT_HANGOUTS_USERS_CHANGED = 'hangouts_users_changed'
|
||||
EVENT_HANGOUTS_CONVERSATIONS_CHANGED = 'hangouts_conversations_changed'
|
||||
EVENT_HANGOUTS_MESSAGE_RECEIVED = 'hangouts_message_received'
|
||||
|
||||
CONF_CONVERSATION_ID = 'id'
|
||||
CONF_CONVERSATION_NAME = 'name'
|
||||
|
@ -59,20 +59,10 @@ MESSAGE_SCHEMA = vol.Schema({
|
|||
vol.Required(ATTR_MESSAGE): [MESSAGE_SEGMENT_SCHEMA]
|
||||
})
|
||||
|
||||
COMMAND_SCHEMA = vol.All(
|
||||
INTENT_SCHEMA = vol.All(
|
||||
# Basic Schema
|
||||
vol.Schema({
|
||||
vol.Exclusive(CONF_WORD, 'trigger'): cv.string,
|
||||
vol.Exclusive(CONF_EXPRESSION, 'trigger'): cv.is_regex,
|
||||
vol.Required(CONF_NAME): cv.string,
|
||||
vol.Required(CONF_SENTENCES): vol.All(cv.ensure_list, [cv.string]),
|
||||
vol.Optional(CONF_CONVERSATIONS): [TARGETS_SCHEMA]
|
||||
}),
|
||||
# Make sure it's either a word or an expression command
|
||||
cv.has_at_least_one_key(CONF_WORD, CONF_EXPRESSION)
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: vol.Schema({
|
||||
vol.Optional(CONF_COMMANDS, default=[]): [COMMAND_SCHEMA]
|
||||
})
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
"""The Hangouts Bot."""
|
||||
import logging
|
||||
import re
|
||||
|
||||
from homeassistant.helpers import dispatcher
|
||||
from homeassistant.helpers import dispatcher, intent
|
||||
|
||||
from .const import (
|
||||
ATTR_MESSAGE, ATTR_TARGET, CONF_CONVERSATIONS, CONF_EXPRESSION, CONF_NAME,
|
||||
CONF_WORD, DOMAIN, EVENT_HANGOUTS_COMMAND, EVENT_HANGOUTS_CONNECTED,
|
||||
EVENT_HANGOUTS_CONVERSATIONS_CHANGED, EVENT_HANGOUTS_DISCONNECTED)
|
||||
ATTR_MESSAGE, ATTR_TARGET, CONF_CONVERSATIONS, DOMAIN,
|
||||
EVENT_HANGOUTS_CONNECTED, EVENT_HANGOUTS_CONVERSATIONS_CHANGED,
|
||||
EVENT_HANGOUTS_DISCONNECTED, EVENT_HANGOUTS_MESSAGE_RECEIVED,
|
||||
CONF_MATCHERS, CONF_CONVERSATION_ID,
|
||||
CONF_CONVERSATION_NAME)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -15,20 +16,34 @@ _LOGGER = logging.getLogger(__name__)
|
|||
class HangoutsBot:
|
||||
"""The Hangouts Bot."""
|
||||
|
||||
def __init__(self, hass, refresh_token, commands):
|
||||
def __init__(self, hass, refresh_token, intents, error_suppressed_convs):
|
||||
"""Set up the client."""
|
||||
self.hass = hass
|
||||
self._connected = False
|
||||
|
||||
self._refresh_token = refresh_token
|
||||
|
||||
self._commands = commands
|
||||
self._intents = intents
|
||||
self._conversation_intents = None
|
||||
|
||||
self._word_commands = None
|
||||
self._expression_commands = None
|
||||
self._client = None
|
||||
self._user_list = None
|
||||
self._conversation_list = None
|
||||
self._error_suppressed_convs = error_suppressed_convs
|
||||
self._error_suppressed_conv_ids = None
|
||||
|
||||
dispatcher.async_dispatcher_connect(
|
||||
self.hass, EVENT_HANGOUTS_MESSAGE_RECEIVED,
|
||||
self._async_handle_conversation_message)
|
||||
|
||||
def _resolve_conversation_id(self, obj):
|
||||
if CONF_CONVERSATION_ID in obj:
|
||||
return obj[CONF_CONVERSATION_ID]
|
||||
if CONF_CONVERSATION_NAME in obj:
|
||||
conv = self._resolve_conversation_name(obj[CONF_CONVERSATION_NAME])
|
||||
if conv is not None:
|
||||
return conv.id_
|
||||
return None
|
||||
|
||||
def _resolve_conversation_name(self, name):
|
||||
for conv in self._conversation_list.get_all():
|
||||
|
@ -38,89 +53,100 @@ class HangoutsBot:
|
|||
|
||||
def async_update_conversation_commands(self, _):
|
||||
"""Refresh the commands for every conversation."""
|
||||
self._word_commands = {}
|
||||
self._expression_commands = {}
|
||||
self._conversation_intents = {}
|
||||
|
||||
for command in self._commands:
|
||||
if command.get(CONF_CONVERSATIONS):
|
||||
for intent_type, data in self._intents.items():
|
||||
if data.get(CONF_CONVERSATIONS):
|
||||
conversations = []
|
||||
for conversation in command.get(CONF_CONVERSATIONS):
|
||||
if 'id' in conversation:
|
||||
conversations.append(conversation['id'])
|
||||
elif 'name' in conversation:
|
||||
conversations.append(self._resolve_conversation_name(
|
||||
conversation['name']).id_)
|
||||
command['_' + CONF_CONVERSATIONS] = conversations
|
||||
for conversation in data.get(CONF_CONVERSATIONS):
|
||||
conv_id = self._resolve_conversation_id(conversation)
|
||||
if conv_id is not None:
|
||||
conversations.append(conv_id)
|
||||
data['_' + CONF_CONVERSATIONS] = conversations
|
||||
else:
|
||||
command['_' + CONF_CONVERSATIONS] = \
|
||||
data['_' + CONF_CONVERSATIONS] = \
|
||||
[conv.id_ for conv in self._conversation_list.get_all()]
|
||||
|
||||
if command.get(CONF_WORD):
|
||||
for conv_id in command['_' + CONF_CONVERSATIONS]:
|
||||
if conv_id not in self._word_commands:
|
||||
self._word_commands[conv_id] = {}
|
||||
word = command[CONF_WORD].lower()
|
||||
self._word_commands[conv_id][word] = command
|
||||
elif command.get(CONF_EXPRESSION):
|
||||
command['_' + CONF_EXPRESSION] = re.compile(
|
||||
command.get(CONF_EXPRESSION))
|
||||
for conv_id in data['_' + CONF_CONVERSATIONS]:
|
||||
if conv_id not in self._conversation_intents:
|
||||
self._conversation_intents[conv_id] = {}
|
||||
|
||||
for conv_id in command['_' + CONF_CONVERSATIONS]:
|
||||
if conv_id not in self._expression_commands:
|
||||
self._expression_commands[conv_id] = []
|
||||
self._expression_commands[conv_id].append(command)
|
||||
self._conversation_intents[conv_id][intent_type] = data
|
||||
|
||||
try:
|
||||
self._conversation_list.on_event.remove_observer(
|
||||
self._handle_conversation_event)
|
||||
self._async_handle_conversation_event)
|
||||
except ValueError:
|
||||
pass
|
||||
self._conversation_list.on_event.add_observer(
|
||||
self._handle_conversation_event)
|
||||
self._async_handle_conversation_event)
|
||||
|
||||
def _handle_conversation_event(self, event):
|
||||
def async_handle_update_error_suppressed_conversations(self, _):
|
||||
"""Resolve the list of error suppressed conversations."""
|
||||
self._error_suppressed_conv_ids = []
|
||||
for conversation in self._error_suppressed_convs:
|
||||
conv_id = self._resolve_conversation_id(conversation)
|
||||
if conv_id is not None:
|
||||
self._error_suppressed_conv_ids.append(conv_id)
|
||||
|
||||
async def _async_handle_conversation_event(self, event):
|
||||
from hangups import ChatMessageEvent
|
||||
if event.__class__ is ChatMessageEvent:
|
||||
self._handle_conversation_message(
|
||||
event.conversation_id, event.user_id, event)
|
||||
if isinstance(event, ChatMessageEvent):
|
||||
dispatcher.async_dispatcher_send(self.hass,
|
||||
EVENT_HANGOUTS_MESSAGE_RECEIVED,
|
||||
event.conversation_id,
|
||||
event.user_id, event)
|
||||
|
||||
def _handle_conversation_message(self, conv_id, user_id, event):
|
||||
async def _async_handle_conversation_message(self,
|
||||
conv_id, user_id, event):
|
||||
"""Handle a message sent to a conversation."""
|
||||
user = self._user_list.get_user(user_id)
|
||||
if user.is_self:
|
||||
return
|
||||
message = event.text
|
||||
|
||||
_LOGGER.debug("Handling message '%s' from %s",
|
||||
event.text, user.full_name)
|
||||
message, user.full_name)
|
||||
|
||||
event_data = None
|
||||
intents = self._conversation_intents.get(conv_id)
|
||||
if intents is not None:
|
||||
is_error = False
|
||||
try:
|
||||
intent_result = await self._async_process(intents, message)
|
||||
except (intent.UnknownIntent, intent.IntentHandleError) as err:
|
||||
is_error = True
|
||||
intent_result = intent.IntentResponse()
|
||||
intent_result.async_set_speech(str(err))
|
||||
|
||||
if intent_result is None:
|
||||
is_error = True
|
||||
intent_result = intent.IntentResponse()
|
||||
intent_result.async_set_speech(
|
||||
"Sorry, I didn't understand that")
|
||||
|
||||
message = intent_result.as_dict().get('speech', {})\
|
||||
.get('plain', {}).get('speech')
|
||||
|
||||
if (message is not None) and not (
|
||||
is_error and conv_id in self._error_suppressed_conv_ids):
|
||||
await self._async_send_message(
|
||||
[{'text': message, 'parse_str': True}],
|
||||
[{CONF_CONVERSATION_ID: conv_id}])
|
||||
|
||||
async def _async_process(self, intents, text):
|
||||
"""Detect a matching intent."""
|
||||
for intent_type, data in intents.items():
|
||||
for matcher in data.get(CONF_MATCHERS, []):
|
||||
match = matcher.match(text)
|
||||
|
||||
pieces = event.text.split(' ')
|
||||
cmd = pieces[0].lower()
|
||||
command = self._word_commands.get(conv_id, {}).get(cmd)
|
||||
if command:
|
||||
event_data = {
|
||||
'command': command[CONF_NAME],
|
||||
'conversation_id': conv_id,
|
||||
'user_id': user_id,
|
||||
'user_name': user.full_name,
|
||||
'data': pieces[1:]
|
||||
}
|
||||
else:
|
||||
# After single-word commands, check all regex commands in the room
|
||||
for command in self._expression_commands.get(conv_id, []):
|
||||
match = command['_' + CONF_EXPRESSION].match(event.text)
|
||||
if not match:
|
||||
continue
|
||||
event_data = {
|
||||
'command': command[CONF_NAME],
|
||||
'conversation_id': conv_id,
|
||||
'user_id': user_id,
|
||||
'user_name': user.full_name,
|
||||
'data': match.groupdict()
|
||||
}
|
||||
if event_data is not None:
|
||||
self.hass.bus.fire(EVENT_HANGOUTS_COMMAND, event_data)
|
||||
|
||||
response = await self.hass.helpers.intent.async_handle(
|
||||
DOMAIN, intent_type,
|
||||
{key: {'value': value} for key, value
|
||||
in match.groupdict().items()}, text)
|
||||
return response
|
||||
|
||||
async def async_connect(self):
|
||||
"""Login to the Google Hangouts."""
|
||||
|
@ -163,10 +189,12 @@ class HangoutsBot:
|
|||
conversations = []
|
||||
for target in targets:
|
||||
conversation = None
|
||||
if 'id' in target:
|
||||
conversation = self._conversation_list.get(target['id'])
|
||||
elif 'name' in target:
|
||||
conversation = self._resolve_conversation_name(target['name'])
|
||||
if CONF_CONVERSATION_ID in target:
|
||||
conversation = self._conversation_list.get(
|
||||
target[CONF_CONVERSATION_ID])
|
||||
elif CONF_CONVERSATION_NAME in target:
|
||||
conversation = self._resolve_conversation_name(
|
||||
target[CONF_CONVERSATION_NAME])
|
||||
if conversation is not None:
|
||||
conversations.append(conversation)
|
||||
|
||||
|
@ -200,8 +228,8 @@ class HangoutsBot:
|
|||
users_in_conversation = []
|
||||
for user in conv.users:
|
||||
users_in_conversation.append(user.full_name)
|
||||
conversations[str(i)] = {'id': str(conv.id_),
|
||||
'name': conv.name,
|
||||
conversations[str(i)] = {CONF_CONVERSATION_ID: str(conv.id_),
|
||||
CONF_CONVERSATION_NAME: conv.name,
|
||||
'users': users_in_conversation}
|
||||
|
||||
self.hass.states.async_set("{}.conversations".format(DOMAIN),
|
||||
|
|
|
@ -290,11 +290,11 @@ async def test_http_api_wrong_data(hass, aiohttp_client):
|
|||
def test_create_matcher():
|
||||
"""Test the create matcher method."""
|
||||
# Basic sentence
|
||||
pattern = conversation._create_matcher('Hello world')
|
||||
pattern = conversation.create_matcher('Hello world')
|
||||
assert pattern.match('Hello world') is not None
|
||||
|
||||
# Match a part
|
||||
pattern = conversation._create_matcher('Hello {name}')
|
||||
pattern = conversation.create_matcher('Hello {name}')
|
||||
match = pattern.match('hello world')
|
||||
assert match is not None
|
||||
assert match.groupdict()['name'] == 'world'
|
||||
|
@ -302,7 +302,7 @@ def test_create_matcher():
|
|||
assert no_match is None
|
||||
|
||||
# Optional and matching part
|
||||
pattern = conversation._create_matcher('Turn on [the] {name}')
|
||||
pattern = conversation.create_matcher('Turn on [the] {name}')
|
||||
match = pattern.match('turn on the kitchen lights')
|
||||
assert match is not None
|
||||
assert match.groupdict()['name'] == 'kitchen lights'
|
||||
|
@ -313,7 +313,7 @@ def test_create_matcher():
|
|||
assert match is None
|
||||
|
||||
# Two different optional parts, 1 matching part
|
||||
pattern = conversation._create_matcher('Turn on [the] [a] {name}')
|
||||
pattern = conversation.create_matcher('Turn on [the] [a] {name}')
|
||||
match = pattern.match('turn on the kitchen lights')
|
||||
assert match is not None
|
||||
assert match.groupdict()['name'] == 'kitchen lights'
|
||||
|
@ -325,13 +325,13 @@ def test_create_matcher():
|
|||
assert match.groupdict()['name'] == 'kitchen light'
|
||||
|
||||
# Strip plural
|
||||
pattern = conversation._create_matcher('Turn {name}[s] on')
|
||||
pattern = conversation.create_matcher('Turn {name}[s] on')
|
||||
match = pattern.match('turn kitchen lights on')
|
||||
assert match is not None
|
||||
assert match.groupdict()['name'] == 'kitchen light'
|
||||
|
||||
# Optional 2 words
|
||||
pattern = conversation._create_matcher('Turn [the great] {name} on')
|
||||
pattern = conversation.create_matcher('Turn [the great] {name} on')
|
||||
match = pattern.match('turn the great kitchen lights on')
|
||||
assert match is not None
|
||||
assert match.groupdict()['name'] == 'kitchen lights'
|
||||
|
|
Loading…
Reference in New Issue