source: branches/active/character_customization/game/parpg/dialogueparsers.py @ 774

Revision 774, 27.7 KB checked in by aspidites, 9 years ago (diff)

Patch by Aspidites

  • removed shebang from all python files except launcher scripts
  • added shebang to pychan_designer
  • Property svn:eol-style set to native
RevLine 
[369]1#   This file is part of PARPG.
[680]2#
[369]3#   PARPG is free software: you can redistribute it and/or modify
4#   it under the terms of the GNU General Public License as published by
5#   the Free Software Foundation, either version 3 of the License, or
6#   (at your option) any later version.
[680]7#
[369]8#   PARPG is distributed in the hope that it will be useful,
9#   but WITHOUT ANY WARRANTY; without even the implied warranty of
10#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11#   GNU General Public License for more details.
[680]12#
[369]13#   You should have received a copy of the GNU General Public License
14#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
[680]15"""
16Contains classes for parsing and validating L{Dialogues<Dialogue>} and other
17dialogue-related data.
[369]18
[680]19@TODO Technomage 2010-11-13: Exception handling + validation needs work.
20    Currently YAML files are only crudely validated - the code assumes that
21    the file contains valid dialogue data, and if that assumption is
22    violated and causes the code to raise any TypeErrors, AttributeErrors or
23    ValueErrors the code then raises a DialogueFormatError with the
24    original (and mostly unhelpful) error message.
25@TODO Technomage 2010-11-13: Support reading and writing unicode.
26"""
[668]27import logging
28try:
29    from cStringIO import StringIO
30except ImportError:
31    from StringIO import StringIO
32from collections import Sequence
33try:
34    from collections import OrderedDict
35except ImportError:
36    # Python version 2.4-2.6 doesn't have the OrderedDict
[736]37    from parpg.common.ordereddict import OrderedDict
[668]38import re
39import textwrap
40
[369]41import yaml
42
[736]43from parpg import COPYRIGHT_HEADER
44from parpg.dialogue import (Dialogue, DialogueSection, DialogueResponse,
[699]45    DialogueGreeting)
[736]46from parpg.dialogueactions import DialogueAction
[369]47
[668]48def setup_logging():
49    """Set various logging parameters for this module."""
50    module_logger = logging.getLogger('dialogueparser')
51    if (__debug__):
52        module_logger.setLevel(logging.DEBUG)
53setup_logging()
[476]54
[668]55class DialogueFormatError(Exception):
56    """Exception thrown when the DialogueParser has encountered an error."""
[369]57
58
[668]59class AbstractDialogueParser(object):
[680]60    """
61    Abstract base class defining the interface for parsers responsible for
62    constructing a L{Dialogue} from its serialized representation.
63    """
[668]64    def load(self, stream):
[680]65        """
66        Parse a stream and attempt to construct a new L{Dialogue} instance from
67        its serialized representation.
68       
69        @param stream: open stream containing the serialized representation of
70            a Dialogue.
71        @type stream: BufferType
72        """
[668]73        raise NotImplementedError('AbstractDialogueParser subclasses must '
[680]74                                  'override the load method.')
[370]75   
[680]76    def dump(self, dialogue, stream):
77        """
78        Serialize a L{Dialogue} instance and dump it to an open stream.
79       
80        @param dialogue: dialogue to serialize.
81        @type dialogue: L{Dialogue}
82        @param stream: open stream into which the serialized L{Dialogue} should
83            be dumped.
84        @type stream: BufferType
85        """
86        raise NotImplementedError('AbstractDialogueParser subclasses must '
87                                  'override the dump method.')
88   
[668]89    def validate(self, stream):
[680]90        """
91        Parse a stream and verify that it contains a valid serialization of a
92        L{Dialogue instance}.
93       
94        @param stream: stream containing the serialized representation of a
95            L{Dialogue}
96        @type stream: BufferType
97        """
[668]98        raise NotImplementedError('AbstractDialogueParser subclasses must '
99                                  'override the validate method.')
[370]100
[369]101
[668]102class YamlDialogueParser(AbstractDialogueParser):
[680]103    """
104    L{AbstractDialogueParser} subclass responsible for parsing dialogues
105    serialized in YAML.
106    """
[668]107    logger = logging.getLogger('dialogueparser.OldYamlDialogueParser')
108   
109    def load(self, stream, loader_class=yaml.Loader):
[680]110        """
111        Parse a YAML stream and attempt to construct a new L{Dialogue}
112        instance.
113       
114        @param stream: stream containing the serialized YAML representation of
115            a L{Dialogue}.
116        @type stream: BufferType
117        @param loader_class: PyYAML loader class to use for reading the
118            serialization.
119        @type loader_class: yaml.BaseLoader subclass
120        """
[668]121        loader = loader_class(stream)
[685]122        try:
123            dialogue = \
124                self._constructDialogue(loader, loader.get_single_node())
125        except (AssertionError,) as error:
126            raise DialogueFormatError(str(error))
[668]127        return dialogue
128   
129    def dump(self, dialogue, output_stream, dumper_class=yaml.Dumper):
[680]130        """
131        Serialize a L{Dialogue} instance as YAML and dump it to an open stream.
132       
133        @param dialogue: dialogue to serialize.
134        @type dialogue: L{Dialogue}
135        @param stream: open stream into which the serialized L{Dialogue} should
136            be dumped.
137        @type stream: BufferType
138        @param dumper_class: PyYAML dumper class to use for formatting the
139            serialization.
140        @type dumper_class: yaml.BaseDumper subclass
141        """
[668]142        intermediate_stream = StringIO()
143        # KLUDE Technomage 2010-11-16: The "width" argument seems to be broken,
144        #     as it doesn't take into about current line indentation and fails
145        #     to correctly wrap at word boundaries.
146        dumper = dumper_class(intermediate_stream, default_flow_style=False,
147                              indent=4, width=99999, line_break='\n',
148                              allow_unicode=True, explicit_start=True,
149                              explicit_end=True, tags=False)
[680]150        dialogue_node = self._representDialogue(dumper, dialogue)
[668]151        dumper.open()
152        dumper.serialize(dialogue_node)
153        dumper.close()
154        file_contents = intermediate_stream.getvalue()
[369]155       
[668]156        file_contents = re.sub(r'(\n|\r|\r\n)(\s*)(GOTO: .*)', r'\1\2\3\1\2',
157                               file_contents)
158        lines = file_contents.splitlines()
[683]159        max_line_length = 76 # 79 - 3 chars for escaping newlines
[668]160        for i in range(len(lines)):
161            line = lines[i]
162            match = re.match(
163                r'^(\s*(?:-\s+)?)(SAY|REPLY|CONDITION):\s+"(.*)"$',
164                line
165            )
166            if (match and len(line) > max_line_length):
167                # Wrap long lines for readability.
168                initial_indent = len(match.group(1))
169                subsequent_indent = initial_indent + 4
170                text_wrapper = textwrap.TextWrapper(
171                    max_line_length,
172                    subsequent_indent=' ' * subsequent_indent,
173                    break_long_words=False,
174                    break_on_hyphens=False
175                )
176                new_lines = text_wrapper.wrap(line)
[683]177                new_lines = (
178                    new_lines[:1] + [re.sub(r'^(\s*) (.*)$', r'\1\ \2', l)
179                                     for l in new_lines[1:]]
180                )
[668]181                lines[i] = '\\\n'.join(new_lines)
[369]182       
[668]183        output_stream.write(COPYRIGHT_HEADER)
184        output_stream.write('\n'.join(lines))
185       
186   
[680]187    def _representDialogue(self, dumper, dialogue):
[668]188        dialogue_node = dumper.represent_dict({})
189        dialogue_dict = OrderedDict()
190        dialogue_dict['NPC_NAME'] = dialogue.npc_name
191        dialogue_dict['AVATAR_PATH'] = dialogue.avatar_path
[699]192        dialogue_dict['DEFAULT_GREETING'] = \
[698]193            self._representDialogueSection(dumper,
[699]194                                           dialogue.default_greeting)
[668]195        # NOTE Technomage 2010-11-16: Dialogue stores its sections in an
196        #     OrderedDict, so a round-trip load, dump, and load will preserve
197        #     the order of DialogueSections.
[699]198        if (len(dialogue.greetings) > 0):
199            greetings_list_node = dumper.represent_list([])
200            greetings_list = greetings_list_node.value
201            for greeting in dialogue.greetings:
202                greeting_node = \
203                    self._representRootDialogueSection(dumper, greeting)
204                greetings_list.append(greeting_node)
205            dialogue_dict['GREETINGS'] = greetings_list_node
[698]206        if (len(dialogue.setions) > 0):
207            sections_list_node = dumper.represent_list([])
208            sections_list = sections_list_node.value
209            for section in dialogue.sections.values():
210                section_node = self._representDialogueSection(dumper, section)
211                sections_list.append(section_node)
212            dialogue_dict['SECTIONS'] = sections_list_node
[668]213       
214        for key, value in dialogue_dict.items():
215            if (isinstance(key, yaml.Node)):
216                key_node = key
217            else:
218                key_node = dumper.represent_data(key)
219            if (isinstance(value, yaml.Node)):
220                value_node = value
221            else:
222                value_node = dumper.represent_data(value)
223            dialogue_node.value.append((key_node, value_node))
224        return dialogue_node
225   
[699]226    def _representRootDialogueSection(self, dumper, greeting):
227        greeting_node = dumper.represent_dict({})
228        greeting_dict = OrderedDict()
229        greeting_dict['ID'] = greeting.id
230        greeting_dict['CONDITION'] = dumper.represent_scalar(
[698]231            'tag:yaml.org,2002:str',
[699]232            greeting.condition,
[698]233            style='"'
234        )
[699]235        for key, value in greeting_dict.items():
[698]236            if (isinstance(key, yaml.Node)):
237                key_node = key
238            else:
239                key_node = dumper.represent_data(key)
240            if (isinstance(value, yaml.Node)):
241                value_node = value
242            else:
243                value_node = dumper.represent_data(value)
[699]244            greeting_node.value.append((key_node, value_node))
245        return greeting_node
[698]246   
[680]247    def _representDialogueSection(self, dumper, dialogue_section):
[668]248        section_node = dumper.represent_dict({})
249        section_dict = OrderedDict() # OrderedDict is required to preserve
250                                     # the order of attributes.
251        section_dict['ID'] = dialogue_section.id
252        # KLUDGE Technomage 2010-11-16: Hard-coding the tag like this could be
253        #     a problem when writing unicode.
254        section_dict['SAY'] = dumper.represent_scalar('tag:yaml.org,2002:str',
255                                                      dialogue_section.text,
256                                                      style='"')
257        actions_list_node = dumper.represent_list([])
258        actions_list = actions_list_node.value
259        for action in dialogue_section.actions:
[680]260            action_node = self._representDialogueAction(dumper, action)
[668]261            actions_list.append(action_node)
262        if (actions_list):
263            section_dict['ACTIONS'] = actions_list_node
264        responses_list_node = dumper.represent_list([])
265        responses_list = responses_list_node.value
266        for response in dialogue_section.responses:
[680]267            response_node = self._representDialogueResponse(dumper, response)
[668]268            responses_list.append(response_node)
269        section_dict['RESPONSES'] = responses_list_node
270       
271        for key, value in section_dict.items():
272            if (isinstance(key, yaml.Node)):
273                key_node = key
274            else:
275                key_node = dumper.represent_data(key)
276            if (isinstance(value, yaml.Node)):
277                value_node = value
278            else:
279                value_node = dumper.represent_data(value)
280            section_node.value.append((key_node, value_node))
281        return section_node
282   
[680]283    def _representDialogueResponse(self, dumper, dialogue_response):
[668]284        response_node = dumper.represent_dict({})
285        response_dict = OrderedDict()
286        # KLUDGE Technomage 2010-11-16: Hard-coding the tag like this could be
287        #     a problem when writing unicode.
288        response_dict['REPLY'] = dumper.represent_scalar(
289            'tag:yaml.org,2002:str',
290            dialogue_response.text,
291            style='"')
292        if (dialogue_response.condition is not None):
293            response_dict['CONDITION']  = dumper.represent_scalar(
294                'tag:yaml.org,2002:str',
295                dialogue_response.condition,
296                style='"'
297            )
298        actions_list_node = dumper.represent_list([])
299        actions_list = actions_list_node.value
300        for action in dialogue_response.actions:
[680]301            action_node = self._representDialogueAction(dumper, action)
[668]302            actions_list.append(action_node)
303        if (actions_list):
304            response_dict['ACTIONS'] = actions_list_node
305        response_dict['GOTO'] = dialogue_response.next_section_id
306       
307        for key, value in response_dict.items():
308            if (isinstance(key, yaml.Node)):
309                key_node = key
310            else:
311                key_node = dumper.represent_data(key)
312            if (isinstance(value, yaml.Node)):
313                value_node = value
314            else:
315                value_node = dumper.represent_data(value)
316            response_node.value.append((key_node, value_node))
317        return response_node
318   
[680]319    def _representDialogueAction(self, dumper, dialogue_action):
[668]320        action_node = dumper.represent_dict({})
321        action_dict = OrderedDict()
322        args, kwargs = dialogue_action.arguments
323        if (args and not kwargs):
324            arguments = list(args)
325        elif (kwargs and not args):
326            arguments = kwargs
327        else:
328            arguments = [list(args), kwargs]
329        action_dict[dialogue_action.keyword] = arguments
330       
331        for key, value in action_dict.items():
332            if (isinstance(key, yaml.Node)):
333                key_node = key
334            else:
335                key_node = dumper.represent_data(key)
336            if (isinstance(value, yaml.Node)):
337                value_node = value
338            else:
339                value_node = dumper.represent_data(value)
340            action_node.value.append((key_node, value_node))
341        return action_node
342   
[680]343    def _constructDialogue(self, loader, yaml_node):
[668]344        npc_name = None
345        avatar_path = None
[699]346        default_greeting = None
347        greetings = []
[668]348        sections = []
349       
350        try:
351            for key_node, value_node in yaml_node.value:
352                key = key_node.value
353                if (key == u'NPC_NAME'):
354                    npc_name = loader.construct_object(value_node)
355                elif (key == u'AVATAR_PATH'):
356                    avatar_path = loader.construct_object(value_node)
[699]357                elif (key == u'DEFAULT_GREETING'):
358                    default_greeting = \
[698]359                        self._constructDialogueSection(loader, value_node)
[699]360                elif (key == u'GREETINGS'):
361                    for greeting_node in value_node.value:
362                        greeting = self._constructRootDialogueSection(
[698]363                                loader,
[699]364                                greeting_node
[698]365                        )
[699]366                        greetings.append(
367                            greeting
[698]368                        )
[668]369                elif (key == u'SECTIONS'):
370                    for section_node in value_node.value:
[680]371                        dialogue_section = self._constructDialogueSection(
[668]372                            loader,
373                            section_node
374                        )
375                        sections.append(dialogue_section)
376        except (AttributeError, TypeError, ValueError) as e:
377            raise DialogueFormatError(e)
378       
379        dialogue = Dialogue(npc_name=npc_name, avatar_path=avatar_path,
[699]380                            default_greeting=default_greeting,
381                            greetings=greetings,
[690]382                            sections=sections)
[668]383        return dialogue
384   
[699]385    def _constructRootDialogueSection(self, loader, greeting_node):
[668]386        id = None
387        text = None
[698]388        condition = None
[668]389        responses = []
390        actions = []
[699]391        greeting = None
[698]392       
393        try:
[699]394            for key_node, value_node in greeting_node.value:
[698]395                key = key_node.value
396                if (key == u'ID'):
397                    id = loader.construct_object(value_node)
398                elif (key == u'SAY'):
399                    text = loader.construct_object(value_node)
400                elif (key == u'CONDITION'):
401                    condition = loader.construct_object(value_node)
402                elif (key == u'RESPONSES'):
403                    for response_node in value_node.value:
404                        dialogue_response = self._constructDialogueResponse(
405                            loader,
406                            response_node
407                        )
408                        responses.append(dialogue_response)
409                elif (key == u'ACTIONS'):
410                    for action_node in value_node.value:
411                        action = self._constructDialogueAction(loader,
412                                                             action_node)
413                        actions.append(action)
414        except (AttributeError, TypeError, ValueError) as e:
415            raise DialogueFormatError(e)
416        else:
[699]417            greeting = DialogueSection(id=id, text=text,
[698]418                                           condition=condition,
419                                           responses=responses,
420                                           actions=actions)
421       
[699]422        return greeting
[698]423   
424    def _constructDialogueSection(self, loader, section_node):
425        id_ = None
426        text = None
427        responses = []
428        actions = []
[668]429        dialogue_section = None
430       
431        try:
432            for key_node, value_node in section_node.value:
433                key = key_node.value
434                if (key == u'ID'):
[698]435                    id_ = loader.construct_object(value_node)
[668]436                elif (key == u'SAY'):
437                    text = loader.construct_object(value_node)
438                elif (key == u'RESPONSES'):
439                    for response_node in value_node.value:
[680]440                        dialogue_response = self._constructDialogueResponse(
[668]441                            loader,
442                            response_node
443                        )
444                        responses.append(dialogue_response)
445                elif (key == u'ACTIONS'):
446                    for action_node in value_node.value:
[680]447                        action = self._constructDialogueAction(loader,
[668]448                                                             action_node)
449                        actions.append(action)
450        except (AttributeError, TypeError, ValueError) as e:
451            raise DialogueFormatError(e)
452        else:
[698]453            dialogue_section = DialogueSection(id_=id_, text=text,
[668]454                                               responses=responses,
455                                               actions=actions)
456       
457        return dialogue_section
458   
[680]459    def _constructDialogueResponse(self, loader, response_node):
[668]460        text = None
461        next_section_id = None
462        actions = []
463        condition = None
464       
465        try:
466            for key_node, value_node in response_node.value:
467                key = key_node.value
468                if (key == u'REPLY'):
469                    text = loader.construct_object(value_node)
470                elif (key == u'ACTIONS'):
471                    for action_node in value_node.value:
[680]472                        action = self._constructDialogueAction(loader,
[668]473                                                             action_node)
474                        actions.append(action)
475                elif (key == u'CONDITION'):
476                    condition = loader.construct_object(value_node)
477                elif (key == u'GOTO'):
478                    next_section_id = loader.construct_object(value_node)
479        except (AttributeError, TypeError, ValueError) as e:
480            raise DialogueFormatError(e)
481       
482        dialogue_response = DialogueResponse(text=text,
483                                             next_section_id=next_section_id,
484                                             actions=actions,
485                                             condition=condition)
486        return dialogue_response
487   
[680]488    def _constructDialogueAction(self, loader, action_node):
[668]489        mapping = loader.construct_mapping(action_node, deep=True)
490        keyword, arguments = mapping.items()[0]
491        if (isinstance(arguments, dict)):
492            # Got a dictionary of keyword arguments.
493            args = ()
494            kwargs = arguments
495        elif (not isinstance(arguments, Sequence) or
496              isinstance(arguments, basestring)):
497            # Got a single positional argument.
[683]498            args = (arguments,)
[668]499            kwargs = {}
500        elif (not len(arguments) == 2 or not isinstance(arguments[1], dict)):
501            # Got a list of positional arguments.
502            args = arguments
503            kwargs = {}
504        else:
505            self.logger.error(
506                '{0} is an invalid DialogueAction argument'.format(arguments)
507            )
508            return None
509       
510        action_type = DialogueAction.registered_actions.get(keyword)
511        if (action_type is None):
512            self.logger.error(
513                'no DialogueAction with keyword "{0}"'.format(keyword)
514            )
515            dialogue_action = None
516        else:
517            dialogue_action = action_type(*args, **kwargs)
518        return dialogue_action
[369]519
520
[668]521class OldYamlDialogueParser(YamlDialogueParser):
[680]522    """
523    L{YAMLDialogueParser} that can read and write dialogues in the old
524    Techdemo1 dialogue file format.
525   
526    @warning: This class is deprecated and likely to be removed in a future
527        version.
528    """
[668]529    logger = logging.getLogger('dialogueparser.OldYamlDialogueParser')
530   
531    def __init__(self):
532        self.response_actions = {}
533   
534    def load(self, stream):
535        dialogue = YamlDialogueParser.load(self, stream)
536        # Place all DialogueActions that were in DialogueSections into the
537        # DialogueResponse that led to the action's original section.
538        for section in dialogue.sections.values():
539            for response in section.responses:
540                actions = self.response_actions.get(response.next_section_id)
541                if (actions is not None):
542                    response.actions = actions
543        return dialogue
544   
[680]545    def _constructDialogue(self, loader, yaml_node):
[668]546        npc_name = None
547        avatar_path = None
548        start_section_id = None
549        sections = []
550       
551        try:
552            for key_node, value_node in yaml_node.value:
553                key = key_node.value
554                if (key == u'NPC'):
555                    npc_name = loader.construct_object(value_node)
556                elif (key == u'AVATAR'):
557                    avatar_path = loader.construct_object(value_node)
558                elif (key == u'START'):
559                    start_section_id = loader.construct_object(value_node)
560                elif (key == u'SECTIONS'):
561                    for id_node, section_node in value_node.value:
[680]562                        dialogue_section = self._constructDialogueSection(
[668]563                            loader,
564                            id_node,
565                            section_node
566                        )
567                        sections.append(dialogue_section)
568        except (AttributeError, TypeError, ValueError) as e:
569            raise DialogueFormatError(e)
570       
571        dialogue = Dialogue(npc_name=npc_name, avatar_path=avatar_path,
572                            start_section_id=start_section_id,
[690]573                            sections=sections)
[668]574        return dialogue
575   
[680]576    def _constructDialogueSection(self, loader, id_node, section_node):
[668]577        id = loader.construct_object(id_node)
578        text = None
579        responses = []
580        actions = []
581        dialogue_section = None
582       
583        try:
584            for node in section_node.value:
585                key_node, value_node = node.value[0]
586                key = key_node.value
587                if (key == u'say'):
588                    text = loader.construct_object(value_node)
589                elif (key == u'meet'):
[680]590                    action = self._constructDialogueAction(loader, node)
[668]591                    actions.append(action)
592                elif (key in [u'start_quest', u'complete_quest', u'fail_quest',
593                              u'restart_quest', u'set_value',
594                              u'decrease_value', u'increase_value',
595                              u'give_stuff', u'get_stuff']):
[680]596                    action = self._constructDialogueAction(loader, node)
[668]597                    if (id not in self.response_actions.keys()):
598                        self.response_actions[id] = []
599                    self.response_actions[id].append(action)
600                elif (key == u'responses'):
601                    for response_node in value_node.value:
[680]602                        dialogue_response = self._constructDialogueResponse(
[668]603                            loader,
604                            response_node
605                        )
606                        responses.append(dialogue_response)
607        except (AttributeError, TypeError, ValueError) as e:
608            raise DialogueFormatError(e)
609        else:
610            dialogue_section = DialogueSection(id=id, text=text,
611                                               responses=responses,
612                                               actions=actions)
613       
614        return dialogue_section
615   
[680]616    def _constructDialogueResponse(self, loader, response_node):
[668]617        text = None
618        next_section_id = None
619        actions = []
620        condition = None
621       
622        try:
623            text = loader.construct_object(response_node.value[0])
624            next_section_id = loader.construct_object(response_node.value[1])
625            if (len(response_node.value) == 3):
626                condition = loader.construct_object(response_node.value[2])
627        except (AttributeError, TypeError, ValueError) as e:
628            raise DialogueFormatError(e)
629       
630        dialogue_response = DialogueResponse(text=text,
631                                             next_section_id=next_section_id,
632                                             actions=actions,
633                                             condition=condition)
634        return dialogue_response
635   
[680]636    def _constructDialogueAction(self, loader, action_node):
[668]637        mapping = loader.construct_mapping(action_node, deep=True)
638        keyword, arguments = mapping.items()[0]
639        if (keyword == 'get_stuff'):
640            # Renamed keyword in new syntax.
641            keyword = 'take_stuff'
642        elif (keyword == 'set_value'):
643            keyword = 'set_quest_value'
644        elif (keyword == 'increase_value'):
645            keyword = 'increase_quest_value'
646        elif (keyword == 'decrease_value'):
647            keyword = 'decrease_quest_value'
648        if (isinstance(arguments, dict)):
649            # Got a dictionary of keyword arguments.
650            args = ()
651            kwargs = arguments
652        elif (not isinstance(arguments, Sequence) or
653              isinstance(arguments, basestring)):
654            # Got a single positional argument.
655            args = (arguments,)
656            kwargs = {}
657        elif (not len(arguments) == 2 or not isinstance(arguments[1], dict)):
658            # Got a list of positional arguments.
659            args = arguments
660            kwargs = {}
661        else:
662            self.logger.error(
663                '{0} is an invalid DialogueAction argument'.format(arguments)
664            )
665            return None
666        action_type = DialogueAction.registered_actions.get(keyword)
667        if (action_type is None):
668            self.logger.error(
669                'no DialogueAction with keyword "{0}"'.format(keyword)
670            )
671            dialogue_action = None
672        else:
673            dialogue_action = action_type(*args, **kwargs)
[774]674        return dialogue_action
Note: See TracBrowser for help on using the repository browser.