OrderAnalyser.py 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. # coding: utf8
  2. import re
  3. from collections import Counter
  4. from core.Utils import Utils
  5. from core.Models import Order
  6. from core.NeuroneLauncher import NeuroneLauncher
  7. import logging
  8. logging.basicConfig()
  9. logger = logging.getLogger("kalliope")
  10. class OrderAnalyser:
  11. """
  12. This Class is used to compare the incoming message to the Signal/Order sentences.
  13. """
  14. def __init__(self, order, main_controller=None, brain=None):
  15. """
  16. Class used to load brain and run neuron attached to the received order
  17. :param order: spelt order
  18. :param main_controller
  19. :param brain: loaded brain
  20. """
  21. self.main_controller = main_controller
  22. self.order = order
  23. if isinstance(self.order, str):
  24. self.order = order.decode('utf-8')
  25. self.brain = brain
  26. logger.debug("OrderAnalyser, Received order: %s" % self.order)
  27. def start(self):
  28. """
  29. This method matches the incoming messages to the signals/order sentences provided in the Brain
  30. """
  31. # create a dict of synapses that have been launched
  32. launched_synapses = self._get_matching_synapse_list(self.brain.synapses, self.order)
  33. if not launched_synapses:
  34. Utils.print_info("No synapse match the captured order: %s" % self.order)
  35. else:
  36. for synapse in launched_synapses:
  37. params = self._get_synapse_params(synapse, self.order)
  38. for neuron in synapse.neurons:
  39. self._start_neuron(neuron, params)
  40. # return the list of launched synapse
  41. return launched_synapses
  42. @classmethod
  43. def _get_matching_synapse_list(cls, all_synapses_list, order_to_match):
  44. """
  45. Class method to return all the matching synapses with the order from the complete of synapses.
  46. :param all_synapses_list: the complete list of all synapses
  47. :param order_to_match: the order to match
  48. :type order_to_match: str
  49. :return: the list of matching synapses
  50. """
  51. matching_synapses_list = list()
  52. for synapse in all_synapses_list:
  53. for signal in synapse.signals:
  54. if type(signal) == Order:
  55. if cls._spelt_order_match_brain_order_via_table(signal.sentence, order_to_match):
  56. matching_synapses_list.append(synapse)
  57. logger.debug("Order found! Run neurons: %s" % synapse.neurons)
  58. Utils.print_success("Order matched in the brain. Running synapse \"%s\"" % synapse.name)
  59. return matching_synapses_list
  60. @classmethod
  61. def _get_synapse_params(cls, synapse, order_to_check):
  62. """
  63. Class method to get all params coming from a synapse. Returns a dict of key/value.
  64. :param synapse: the synapse to check
  65. :param order_to_check: the order to match
  66. :type order_to_check: str
  67. :return: the dict key/value
  68. """
  69. params = dict()
  70. for signal in synapse.signals:
  71. if cls._is_containing_bracket(signal.sentence):
  72. params = cls._associate_order_params_to_values(order_to_check, signal.sentence)
  73. logger.debug("Parameters for order: %s" % params)
  74. return params
  75. @classmethod
  76. def _start_neuron(cls, neuron, params):
  77. """
  78. Associate params and Starts a neuron.
  79. :param neuron: the neuron to start
  80. :param params: the params to check and associate to the neuron args.
  81. """
  82. problem_in_neuron_found = False
  83. if isinstance(neuron.parameters, dict):
  84. # print neuron.parameters
  85. if "args" in neuron.parameters:
  86. logger.debug("The neuron waits for parameter")
  87. # check that the user added parameters to his order
  88. if params is None:
  89. # we don't raise an error and break the program but we don't run the neuron
  90. problem_in_neuron_found = True
  91. Utils.print_danger("Error: The neuron %s is waiting for argument. "
  92. "Argument found in bracket in the given order" % neuron.name)
  93. else:
  94. # we add wanted arguments the existing neuron parameter dict
  95. for arg in neuron.parameters["args"]:
  96. if arg in params:
  97. logger.debug("Parameter %s added to the current parameter "
  98. "of the neuron: %s" % (arg, neuron.name))
  99. neuron.parameters[arg] = params[arg]
  100. else:
  101. # we don't raise an error and break the program but
  102. # we don't run the neuron
  103. problem_in_neuron_found = True
  104. Utils.print_danger("Error: Argument \"%s\" not found in the"
  105. " order" % arg)
  106. # if no error detected, we run the neuron
  107. if not problem_in_neuron_found:
  108. NeuroneLauncher.start_neurone(neuron)
  109. else:
  110. Utils.print_danger("A problem has been found in the Synapse.")
  111. @classmethod
  112. def _associate_order_params_to_values(cls, order, order_to_check):
  113. """
  114. Associate the variables from the order to the incoming user order
  115. :param order_to_check: the order to check incoming from the brain
  116. :type order_to_check: str
  117. :param order: the order from user
  118. :type order: str
  119. :return: the dict corresponding to the key / value of the params
  120. """
  121. pattern = '\s+(?=[^\{\{\}\}]*\}\})'
  122. # Remove white spaces (if any) between the variable and the double brace then split
  123. list_word_in_order = re.sub(pattern, '', order_to_check).split()
  124. # get the order, defined by the first words before {{
  125. # /!\ Could be empty if order starts with double brace
  126. the_order = order_to_check[:order_to_check.find('{{')]
  127. # remove sentence before order which are sentences not matching anyway
  128. truncate_user_sentence = order[order.find(the_order):]
  129. truncate_list_word_said = truncate_user_sentence.split()
  130. # make dict var:value
  131. dict_var = dict()
  132. for idx, ow in enumerate(list_word_in_order):
  133. if cls._is_containing_bracket(ow):
  134. # remove bracket and grab the next value / stop value
  135. var_name = ow.replace("{{", "").replace("}}", "")
  136. stop_value = cls._get_next_value_list(list_word_in_order[idx:])
  137. if stop_value is None:
  138. dict_var[var_name] = " ".join(truncate_list_word_said)
  139. break
  140. for word_said in truncate_list_word_said:
  141. if word_said == stop_value:
  142. break
  143. if var_name in dict_var:
  144. dict_var[var_name] += " " + word_said
  145. truncate_list_word_said = truncate_list_word_said[1:]
  146. else:
  147. dict_var[var_name] = word_said
  148. truncate_list_word_said = truncate_list_word_said[1:]
  149. return dict_var
  150. @staticmethod
  151. def _is_containing_bracket(sentence):
  152. """
  153. Return True if the text in <sentence> contains brackets
  154. :param sentence:
  155. :return:
  156. """
  157. # print "sentence to test %s" % sentence
  158. pattern = r"{{|}}"
  159. # prog = re.compile(pattern)
  160. check_bool = re.search(pattern, sentence)
  161. if check_bool is not None:
  162. return True
  163. return False
  164. @staticmethod
  165. def _get_next_value_list(list_to_check):
  166. ite = list_to_check.__iter__()
  167. next(ite, None)
  168. return next(ite, None)
  169. @classmethod
  170. def _spelt_order_match_brain_order_via_table(cls, order_to_analyse, user_said):
  171. """
  172. return true if all string that are in the sentence are present in the order to test
  173. :param order_to_analyse: String order to test
  174. :param user_said: String to compare to the order
  175. :return: True if all string are present in the order
  176. """
  177. list_word_user_said = user_said.split()
  178. split_order_without_bracket = cls._get_split_order_without_bracket(order_to_analyse)
  179. # if all words in the list of what the user said in in the list of word in the order
  180. return cls._counter_subset(split_order_without_bracket, list_word_user_said)
  181. @staticmethod
  182. def _get_split_order_without_bracket(order):
  183. """
  184. Get an order with bracket inside like: "hello my name is {{ name }}.
  185. return a list of string without bracket like ["hello", "my", "name", "is"]
  186. :param order: sentence to split
  187. :return: list of string without bracket
  188. """
  189. pattern = r"((?:{{\s*)[\w\.]+(?:\s*}}))"
  190. # find everything like {{ word }}
  191. matches = re.findall(pattern, order)
  192. for match in matches:
  193. order = order.replace(match, "")
  194. # then split
  195. split_order = order.split()
  196. return split_order
  197. @staticmethod
  198. def _counter_subset(list1, list2):
  199. """
  200. check if the number of occurrences matches
  201. :param list1:
  202. :param list2:
  203. :return:
  204. """
  205. c1, c2 = Counter(list1), Counter(list2)
  206. for k, n in c1.items():
  207. if n > c2[k]:
  208. return False
  209. return True