OrderAnalyser.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. # coding: utf8
  2. import re
  3. import collections
  4. from collections import Counter
  5. from kalliope.core.Utils.Utils import Utils
  6. from kalliope.core.ConfigurationManager import SettingLoader
  7. from kalliope.core.Models import Order
  8. from kalliope.core.NeuronLauncher import NeuronLauncher
  9. import logging
  10. logging.basicConfig()
  11. logger = logging.getLogger("kalliope")
  12. class OrderAnalyser:
  13. """
  14. This Class is used to compare the incoming message to the Signal/Order sentences.
  15. """
  16. def __init__(self, order, brain=None):
  17. """
  18. Class used to load brain and run neuron attached to the received order
  19. :param order: spelt order
  20. :param brain: loaded brain
  21. """
  22. sl = SettingLoader()
  23. self.settings = sl.settings
  24. self.order = order
  25. if isinstance(self.order, str):
  26. self.order = order.decode('utf-8')
  27. self.brain = brain
  28. logger.debug("OrderAnalyser, Received order: %s" % self.order)
  29. def start(self, synapses_to_run=None, external_order=None):
  30. """
  31. This method matches the incoming messages to the signals/order sentences provided in the Brain.
  32. Note: we use named tuples:
  33. tuple_synapse_order = collections.namedtuple('tuple_synapse_matchingOrder',['synapse', 'order'])
  34. """
  35. synapse_order_tuple = collections.namedtuple('tuple_synapse_matchingOrder', ['synapse', 'order'])
  36. synapses_order_tuple_list = list()
  37. if synapses_to_run is not None and external_order is not None:
  38. for synapse in synapses_to_run:
  39. synapses_order_tuple_list.append(synapse_order_tuple(synapse=synapse,
  40. order=external_order))
  41. # if list of synapse is not provided, let's find one
  42. else: # synapses_to_run is None or external_order is None:
  43. # create a dict of synapses that have been launched
  44. logger.debug("[orderAnalyser.start]-> No Synapse provided, let's find one")
  45. synapses_order_tuple_list = self._find_synapse_to_run(brain=self.brain,
  46. settings=self.settings,
  47. order=self.order)
  48. # retrieve params
  49. synapses_launched = list()
  50. for tuple in synapses_order_tuple_list:
  51. logger.debug("[orderAnalyser.start]-> Grab the params")
  52. params = self._get_params_from_order(tuple.order, self.order)
  53. # Start a neuron list with params
  54. self._start_list_neurons(list_neurons=tuple.synapse.neurons,
  55. params=params)
  56. synapses_launched.append(tuple.synapse)
  57. # return the list of launched synapse
  58. return synapses_launched
  59. @classmethod
  60. def _find_synapse_to_run(cls, brain, settings, order):
  61. """
  62. Find the list of the synapse matching the order.
  63. Note: we use named tuples:
  64. tuple_synapse_order = collections.namedtuple('tuple_synapse_matchingOrder',['synapse', 'order'])
  65. :param brain: the brain
  66. :param settings: the settings
  67. :param order: the provided order to match
  68. :return: the list of synapses launched (named tuples)
  69. """
  70. synapse_to_run = cls._get_matching_synapse_list(brain.synapses, order)
  71. if not synapse_to_run:
  72. Utils.print_info("No synapse match the captured order: %s" % order)
  73. if settings.default_synapse is not None:
  74. default_synapse = cls._get_default_synapse_from_sysnapses_list(brain.synapses,
  75. settings.default_synapse)
  76. if default_synapse is not None:
  77. logger.debug("Default synapse found %s" % default_synapse)
  78. Utils.print_info("Default synapse found: %s, running it" % default_synapse.name)
  79. tuple_synapse_order = collections.namedtuple('tuple_synapse_matchingOrder',
  80. ['synapse', 'order'])
  81. synapse_to_run.append(tuple_synapse_order(synapse=default_synapse,
  82. order=""))
  83. return synapse_to_run
  84. @classmethod
  85. def _get_matching_synapse_list(cls, all_synapses_list, order_to_match):
  86. """
  87. Class method to return all the matching synapses with the order from the complete of synapses.
  88. Note: we use named tuples:
  89. tuple_synapse_matchingOrder = collections.namedtuple('tuple_synapse_matchingOrder',['synapse', 'order'])
  90. :param all_synapses_list: the complete list of all synapses
  91. :param order_to_match: the order to match
  92. :type order_to_match: str
  93. :return: the list of matching synapses (named tuples)
  94. """
  95. tuple_synapse_order = collections.namedtuple('tuple_synapse_matchingOrder', ['synapse', 'order'])
  96. matching_synapses_list = list()
  97. for synapse in all_synapses_list:
  98. for signal in synapse.signals:
  99. if type(signal) == Order:
  100. if cls.spelt_order_match_brain_order_via_table(signal.sentence, order_to_match):
  101. matching_synapses_list.append(tuple_synapse_order(synapse=synapse,
  102. order=signal.sentence))
  103. logger.debug("Order found! Run neurons: %s" % synapse.neurons)
  104. Utils.print_success("Order matched in the brain. Running synapse \"%s\"" % synapse.name)
  105. return matching_synapses_list
  106. @classmethod
  107. def _get_params_from_order(cls, string_order, order_to_check):
  108. """
  109. Class method to get all params coming from a string order. Returns a dict of key/value.
  110. :param string_order: the string_order to check
  111. :param order_to_check: the order to match
  112. :type order_to_check: str
  113. :return: the dict key/value
  114. """
  115. params = dict()
  116. if cls._is_containing_bracket(string_order):
  117. params = cls._associate_order_params_to_values(order_to_check, string_order)
  118. logger.debug("Parameters for order: %s" % params)
  119. return params
  120. @classmethod
  121. def _start_list_neurons(cls, list_neurons, params):
  122. # start neurons
  123. for neuron in list_neurons:
  124. cls._start_neuron(neuron, params)
  125. @staticmethod
  126. def _start_neuron(neuron, params):
  127. """
  128. Associate params and Starts a neuron.
  129. :param neuron: the neuron to start
  130. :param params: the params to check and associate to the neuron args.
  131. """
  132. problem_in_neuron_found = False
  133. if isinstance(neuron.parameters, dict):
  134. # print neuron.parameters
  135. if "args" in neuron.parameters:
  136. logger.debug("The neuron waits for parameter")
  137. # check that the user added parameters to his order
  138. if params is None:
  139. # we don't raise an error and break the program but we don't run the neuron
  140. problem_in_neuron_found = True
  141. Utils.print_danger("Error: The neuron %s is waiting for argument. "
  142. "Argument found in bracket in the given order" % neuron.name)
  143. else:
  144. # we add wanted arguments the existing neuron parameter dict
  145. for arg in neuron.parameters["args"]:
  146. if arg in params:
  147. logger.debug("Parameter %s added to the current parameter "
  148. "of the neuron: %s" % (arg, neuron.name))
  149. neuron.parameters[arg] = params[arg]
  150. else:
  151. # we don't raise an error and break the program but
  152. # we don't run the neuron
  153. problem_in_neuron_found = True
  154. Utils.print_danger("Error: Argument \"%s\" not found in the"
  155. " order" % arg)
  156. # if no error detected, we run the neuron
  157. if not problem_in_neuron_found:
  158. NeuronLauncher.start_neuron(neuron)
  159. else:
  160. Utils.print_danger("A problem has been found in the Synapse.")
  161. @classmethod
  162. def _associate_order_params_to_values(cls, order, order_to_check):
  163. """
  164. Associate the variables from the order to the incoming user order
  165. :param order_to_check: the order to check incoming from the brain
  166. :type order_to_check: str
  167. :param order: the order from user
  168. :type order: str
  169. :return: the dict corresponding to the key / value of the params
  170. """
  171. logger.debug("[OrderAnalyser._associate_order_params_to_values] user order: %s, "
  172. "order to check: %s" % (order, order_to_check))
  173. pattern = '\s+(?=[^\{\{\}\}]*\}\})'
  174. # Remove white spaces (if any) between the variable and the double brace then split
  175. list_word_in_order = re.sub(pattern, '', order_to_check).split()
  176. # get the order, defined by the first words before {{
  177. # /!\ Could be empty if order starts with double brace
  178. the_order = order_to_check[:order_to_check.find('{{')]
  179. # remove sentence before order which are sentences not matching anyway
  180. truncate_user_sentence = order[order.find(the_order):]
  181. truncate_list_word_said = truncate_user_sentence.split()
  182. # make dict var:value
  183. dict_var = dict()
  184. for idx, ow in enumerate(list_word_in_order):
  185. if cls._is_containing_bracket(ow):
  186. # remove bracket and grab the next value / stop value
  187. var_name = ow.replace("{{", "").replace("}}", "")
  188. stop_value = cls._get_next_value_list(list_word_in_order[idx:])
  189. if stop_value is None:
  190. dict_var[var_name] = " ".join(truncate_list_word_said)
  191. break
  192. for word_said in truncate_list_word_said:
  193. if word_said == stop_value:
  194. break
  195. if var_name in dict_var:
  196. dict_var[var_name] += " " + word_said
  197. truncate_list_word_said = truncate_list_word_said[1:]
  198. else:
  199. dict_var[var_name] = word_said
  200. truncate_list_word_said = truncate_list_word_said[1:]
  201. return dict_var
  202. @staticmethod
  203. def _is_containing_bracket(sentence):
  204. """
  205. Return True if the text in <sentence> contains brackets
  206. :param sentence:
  207. :return:
  208. """
  209. # print "sentence to test %s" % sentence
  210. pattern = r"{{|}}"
  211. # prog = re.compile(pattern)
  212. check_bool = re.search(pattern, sentence)
  213. if check_bool is not None:
  214. return True
  215. return False
  216. @staticmethod
  217. def _get_next_value_list(list_to_check):
  218. ite = list_to_check.__iter__()
  219. next(ite, None)
  220. return next(ite, None)
  221. @classmethod
  222. def spelt_order_match_brain_order_via_table(cls, order_to_analyse, user_said):
  223. """
  224. return true if all string that are in the sentence are present in the order to test
  225. :param order_to_analyse: String order to test
  226. :param user_said: String to compare to the order
  227. :return: True if all string are present in the order
  228. """
  229. list_word_user_said = user_said.split()
  230. split_order_without_bracket = cls._get_split_order_without_bracket(order_to_analyse)
  231. # if all words in the list of what the user said in in the list of word in the order
  232. return cls._counter_subset(split_order_without_bracket, list_word_user_said)
  233. @staticmethod
  234. def _get_split_order_without_bracket(order):
  235. """
  236. Get an order with bracket inside like: "hello my name is {{ name }}.
  237. return a list of string without bracket like ["hello", "my", "name", "is"]
  238. :param order: sentence to split
  239. :return: list of string without bracket
  240. """
  241. pattern = r"((?:{{\s*)[\w\.]+(?:\s*}}))"
  242. # find everything like {{ word }}
  243. matches = re.findall(pattern, order)
  244. for match in matches:
  245. order = order.replace(match, "")
  246. # then split
  247. split_order = order.split()
  248. return split_order
  249. @staticmethod
  250. def _counter_subset(list1, list2):
  251. """
  252. check if the number of occurrences matches
  253. :param list1:
  254. :param list2:
  255. :return:
  256. """
  257. c1, c2 = Counter(list1), Counter(list2)
  258. for k, n in c1.items():
  259. if n > c2[k]:
  260. return False
  261. return True
  262. @staticmethod
  263. def _get_default_synapse_from_sysnapses_list(all_synapses_list, default_synapse_name):
  264. """
  265. Static method to get the default synapse if it exists.
  266. :param all_synapses_list: the complete list of all synapses
  267. :param default_synapse_name: the synapse to find
  268. :return: the Synapse
  269. """
  270. default_synapse = None
  271. for synapse in all_synapses_list:
  272. if synapse.name == default_synapse_name:
  273. logger.debug("Default synapse found: %s" % synapse.name)
  274. default_synapse = synapse
  275. break
  276. if default_synapse is None:
  277. logger.debug("Default synapse not found")
  278. Utils.print_warning("Default synapse not found")
  279. return default_synapse