Ejemplo n.º 1
0
Archivo: menu.py Proyecto: daasara/riba
class Menu(Interaction):
    """IVR-based menu, returns options selected by the user and keypresses
	
	The Menu holds a collection of Option instances along with a prompt 
	which presents those options to the user.  The menu will attempt to 
	collect the user's selected option up to maxRepetitions times, playing 
	the prompt each time.
	
	If tellInvalid is true, will allow any character being pressed to stop
	the playback, and will tell the user if the pressed character is not 
	recognised.  Otherwise will simply ignore a pressed character which isn't
	part of an Option object's 'option' property.
	
	The menu will chain into callable Options, so that SubMenu and ExitOn can
	be used to produce effects such as multi-level menus with options to 
	return to the parent menu level.
	
	Returns [(option,char(pressedKey))...] for each level of menu explored
	"""
    INVALID_OPTION_FILE = 'pm-invalid-option'
    prompt = common.ListProperty(
        "prompt",
        """(Set of) prompts to run, can be Prompt instances or filenames
		
		Used by the PromptRunner to produce prompt selections
		""",
    )
    textPrompt = common.StringProperty(
        "textPrompt",
        """Textual prompt describing the option""",
    )
    options = common.ListProperty(
        "options",
        """Set of options the user may select""",
    )
    tellInvalid = common.IntegerProperty(
        "tellInvalid",
        """Whether to tell the user that their selection is unrecognised""",
        defaultValue=True,
    )
    runnerClass = MenuRunner
Ejemplo n.º 2
0
class Tag(propertied.Propertied):
    """Represents a particular tag within a document"""
    name = common.StringProperty(
        "name",
        "The name of the tag",
        defaultValue="",
    )
    attributes = common.DictionaryProperty(
        "attributes",
        """The in-tag attributes of the tag""",
        defaultFunction=lambda x, y: {},
    )
    content = common.ListProperty(
        "content",
        """The content (children) of the tag""",
        setDefaultOnGet=1,
        defaultFunction=lambda x, y: [],
    )

    def __cmp__(self, other):
        """Compare this tag to another"""
        if not isinstance(other, Tag):
            return -1
        if other.name != self.name:
            return cmp(self.name, other.name)
        if other.attributes != self.attributes:
            return cmp(self.attributes, other.attributes)
        if other.content != self.content:
            return cmp(self.content, other.content)
        return 0

    def __repr__(self):
        """Create a decent representation of this tag"""
        fragments = []
        name = self.name.decode().encode('utf-8')
        fragments.append("<" + name)
        for key, value in self.attributes.items():
            fragments.append("""%s=%r""" % (key, value))
        fragments = [" ".join(fragments)]
        if self.content:
            fragments.append(">")
            for item in self.content:
                if isinstance(item, str):
                    fragments.append(item)
                else:
                    fragments.append(repr(item))
            fragments.append("</%s>" % (name))
        else:
            fragments.append("/>")
        return "".join(fragments)
Ejemplo n.º 3
0
Archivo: menu.py Proyecto: daasara/riba
class CollectAudio(Interaction):
    """Collects audio file from the user"""
    prompt = common.ListProperty(
        "prompt",
        """(Set of) prompts to run, can be Prompt instances or filenames
		
		Used by the PromptRunner to produce prompt selections
		""",
    )
    textPrompt = common.StringProperty(
        "textPrompt",
        """Textual prompt describing the option""",
    )
    temporaryFile = common.StringLocaleProperty(
        "temporaryFile",
        """Temporary file into which to record the audio before moving to filename""",
    )
    filename = common.StringLocaleProperty(
        "filename",
        """Final filename into which to record the file...""",
    )
    deleteOnFail = common.BooleanProperty(
        "deleteOnFail",
        """Whether to delete failed attempts to record a file""",
        defaultValue=True)
    escapeDigits = common.StringLocaleProperty(
        "escapeDigits",
        """Set of digits which escape from recording the file""",
        defaultValue='#*0123456789',
    )
    timeout = common.FloatProperty(
        "timeout",
        """Duration to wait for recording (maximum record time)""",
        defaultValue=60,
    )
    silence = common.FloatProperty(
        "silence",
        """Duration to wait for recording (maximum record time)""",
        defaultValue=5,
    )
    beep = common.BooleanProperty(
        "beep",
        """Whether to play a "beep" sound at beginning of recording""",
        defaultValue=True,
    )
    runnerClass = CollectAudioRunner
Ejemplo n.º 4
0
Archivo: menu.py Proyecto: daasara/riba
class PromptRunner(propertied.Propertied):
    """Prompt formed from list of sub-prompts
	"""
    elements = common.ListProperty(
        "elements",
        """Sub-elements of the prompt to be presented""",
    )
    agi = basic.BasicProperty(
        "agi",
        """The FastAGI instance we're controlling""",
    )
    escapeDigits = common.StringLocaleProperty(
        "escapeDigits",
        """Set of digits which escape from playing the prompt""",
    )
    timeout = common.FloatProperty(
        "timeout",
        """Timeout on data-entry after completed reading""",
    )

    def __call__(self):
        """Return a deferred that chains all of the sub-prompts in order
		
		Returns from the first of the sub-prompts that recevies a selection
		
		returns str(digit) for the key the user pressed
		"""
        return self.onNext(None)

    def onNext(self, result, index=0):
        """Process the next operation"""
        if result is not None:
            return result
        try:
            element = self.elements[index]
        except IndexError, err:
            # okay, do a waitForDigit from timeout seconds...
            return self.agi.waitForDigit(self.timeout).addCallback(
                self.processKey).addCallback(self.processLast)
        else:
Ejemplo n.º 5
0
class Survey(propertied.Propertied):
    """Models a single survey to be completed"""
    surveyId = common.IntegerProperty(
        "surveyId",
        """Unique identifier for this survey""",
    )
    owner = basic.BasicProperty(
        "owner",
        """Owner's phone number to which to connect""",
    )
    questions = common.ListProperty(
        "questions",
        """Set of questions which make up the survey""",
    )
    YOU_CURRENTLY_HAVE = 'vm-youhave'
    QUESTIONS_IN_YOUR_SURVEY = 'vm-messages'
    QUESTION_IN_YOUR_SURVEY = 'vm-message'
    TO_LISTEN_TO_SURVEY_QUESTION = 'to-listen-to-it'
    TO_RECORD_A_NEW_SURVEY_QUESTION = 'to-rerecord-it'
    TO_FINISH_SURVEY_SETUP = 'vm-helpexit'

    def setupSurvey(self, agi):
        """AGI application to allow the user to set up the survey

        Screen 1:
            You have # questions.
            To listen to a question, press the number of the question.
            To record a new question, press pound.
            To finish setup, press star.
        """
        seq = fastagi.InSequence()
        seq.append(agi.wait, 2)
        base = """You currently have %s question%s.
        To listen to a question press the number of the question.
        To record a new question, press pound.
        To finish survey setup, press star.
        """ % (
            len(self.questions),
            ['', 's'][len(self.questions) == 1],
        )
        if len(base) != 1:
            base += 's'
        base = " ".join(base.split())
        seq.append(agi.execute, 'Festival', base)
        seq.append(agi.finish, )
        return seq()
        seq.append(agi.streamFile, self.YOU_CURRENTLY_HAVE)
        seq.append(agi.sayNumber, len(self.questions))
        if len(self.questions) == 1:
            seq.append(agi.streamFile, self.QUESTION_IN_YOUR_SURVEY)
        else:
            seq.append(agi.streamFile, self.QUESTIONS_IN_YOUR_SURVEY)
        seq.append(agi.streamFile, self.TO_LISTEN_TO_SURVEY_QUESTION)
        seq.append(agi.streamFile, self.TO_RECORD_A_NEW_SURVEY_QUESTION)
        seq.append(agi.streamFile, self.TO_FINISH_SURVEY_SETUP)
        seq.append(agi.finish, )
        return seq()

    def newQuestionId(self):
        """Return a new, unique, question id"""
        bad = True
        while bad:
            bad = False
            id = random.randint(0, sys.maxint)
            for question in self.questions:
                if id == question.__dict__.get('questionId'):
                    bad = True
        return id
Ejemplo n.º 6
0
class Callable(propertied.Propertied):
    """Modelling of a callable Python object"""
    name = common.StringProperty(
        'name',
        """The callable object's-name (may be different from underlying object)""",
    )
    implementation = basic.BasicProperty(
        "implementation",
        """The underlying implementation (callable Python object)""",
    )
    arguments = common.ListProperty(
        'arguments',
        """Argument-list for the callable object""",
        baseType=listof_Arguments,
    )
    shortHelp = common.StringProperty(
        'shortHelp',
        """Short help-string suitable for tooltips/status-bars""",
    )
    longHelp = common.StringProperty(
        'longHelp',
        """Longer help-string suitable for context-sensitive help""",
    )
    coerce = common.BooleanProperty(
        "coerce",
        """Whether to coerce arguments if possible""",
        defaultValue=0,
    )

    def __init__(self,
                 implementation,
                 name=__NULL__,
                 arguments=__NULL__,
                 shortHelp=__NULL__,
                 longHelp=__NULL__,
                 **named):
        """Initialize the Callable object

		implementation -- a callable python object
		name -- if provided, will override the given name
		arguments -- if provided, will override calculated arguments
		shortHelp -- short help-string, first line of __doc__ if not given
		longHelp -- long help-string, entire __doc__ string if not given
		"""
        if name is __NULL__:
            name = self._name(implementation)
        if arguments is __NULL__:
            arguments = self._arguments(implementation)
        if shortHelp is __NULL__:
            shortHelp = self._shortHelp(implementation)
        if longHelp is __NULL__:
            longHelp = self._longHelp(implementation)
        super(Callable, self).__init__(implementation=implementation,
                                       name=name,
                                       arguments=arguments,
                                       **named)

    def __str__(self):
        """Return a friendly string representation"""
        return """%s( %s )""" % (self.__class__.__name__, self.implementation)

    def __call__(self, *arguments, **named):
        """Do the actual calling of the callable object"""
        set = {}
        for argument, value in zip(arguments, self.arguments):
            set[argument.name] = (argument, value)
        # XXX potentially there are missing positional arguments!
        if named:
            nameSet = dict([(arg.name, arg) for arg in self.arguments])
            for key, value in named.items():
                if set.has_key(key):
                    raise ValueError(
                        """Redefinition of argument order for argument %s""" %
                        (set.get(key)))
                else:
                    # note that argument may be None
                    set[key] = nameSet.get(key), value
        for key, (argument, value) in set.items():
            if self.coerce and argument and argument.baseType and hasattr(
                    argument.baseType, "coerce"):
                value = argument.baseType.coerce(argument)
            set[key] = value
        # XXX Should keep arguments in order to allow for *args set :(
        return self.implementation(**set)

    def getArgument(self, name):
        """Retieve an argument by name"""
        for argument in self.arguments:
            if argument.name == name:
                return argument
        raise KeyError("""%r object doesn't have a %s argument""" %
                       (self, name))

    def _name(self, value):
        """Try to find a decent name for a callable object"""
        name = "<unknown>"
        for attribute in [
                '__name__', 'name', 'func_name', 'co_name', '__file__',
                "friendlyName"
        ]:
            if hasattr(value, attribute):
                v = getattr(value, attribute)
                if isinstance(v, (str, unicode)):
                    name = v
        if '.' in name:
            return name.split('.')[-1]
        return name

    def _shortHelp(self, value):
        """Try to find the short-docstring for an object"""
        if hasattr(value, '__doc__') and value.__doc__:
            return value.__doc__.split('\n')[0]
        else:
            return ""

    def _longHelp(self, value):
        """Try to find the short-docstring for an object"""
        if hasattr(value, '__doc__') and value.__doc__:
            return value.__doc__
        else:
            return ""

    def _useCall(self, value):
        """Can we use __call__ to call this object?

		returns true if we should be able to use it
		"""
        return (
            # must have __call__
            hasattr(value, '__call__') and (
                # call should be a function or method...
                hasattr(value.__call__, 'im_func')
                or hasattr(value.__call__, 'im_code')))

    def _arguments(self, value):
        """Get a list of arguments for a callable object"""
        if self._useCall(value):
            value = value.__call__
        if hasattr(value, 'im_func'):
            # receiver is a method. Drop the first argument, usually 'self'.
            func = value.im_func
            arguments = inspect.getargspec(func)
            if value.im_self is not None:
                # a bound instance or class method
                arguments = inspect.getargspec(func)
                del arguments[0][0]
            else:
                # an un-bound method
                pass
        elif hasattr(value, 'func_code') or hasattr(value, 'im_code'):
            # receiver is a function.
            func = value
            arguments = inspect.getargspec(func)
        else:
            raise ValueError('unknown reciever type %s %s' %
                             (receiver, type(receiver)))
        names, vararg, varnamed, defaults = arguments
        defaults = defaults or ()
        result = [Argument(name=name) for name in names]
        for name, default in zip(names[-len(defaults):], defaults):
            for item in result:
                if item.name == name:
                    item.default = default
        return result

    def check(cls, value):
        """Strict check to see if value is an instance of cls"""
        return isinstance(value, cls)

    check = classmethod(check)

    def coerce(cls, value):
        """Coerce value to a Callable-object"""
        if cls.check(value):
            return value
        if callable(value):
            return cls(implementation=value, )
        else:
            raise TypeError("Don't know how to convert %r to a %s object" % (
                value,
                cls.__name__,
            ))

    coerce = classmethod(coerce)

    def __eq__(self, other):
        """Determine whether other is our equivalent

		returns true if other is of the same class, with
		the same primary attributes
		"""
        if self.__class__ is not other.__class__:
            return 0
        NULL = []
        for nm in ['name', 'implementation', 'arguments']:
            if hasattr(self, nm) and not hasattr(other, nm):
                return 0
            elif not hasattr(self, nm) and hasattr(other, nm):
                return 0
            elif hasattr(self, nm):
                if getattr(self, nm) != getattr(other, nm):
                    return 0
        return 1
Ejemplo n.º 7
0
class PromptRunner(propertied.Propertied):
    """Prompt formed from list of sub-prompts
    """
    elements = common.ListProperty(
        "elements",
        """Sub-elements of the prompt to be presented""",
    )
    agi = basic.BasicProperty(
        "agi",
        """The FastAGI instance we're controlling""",
    )
    escapeDigits = common.StringLocaleProperty(
        "escapeDigits",
        """Set of digits which escape from playing the prompt""",
    )
    timeout = common.FloatProperty(
        "timeout",
        """Timeout on data-entry after completed reading""",
    )

    def __call__(self):
        """Return a deferred that chains all of the sub-prompts in order

        Returns from the first of the sub-prompts that recevies a selection

        returns str(digit) for the key the user pressed
        """
        return self.onNext(None)

    def onNext(self, result, index=0):
        """Process the next operation"""
        if result is not None:
            return result
        try:
            element = self.elements[index]
        except IndexError as err:
            # okay, do a waitForDigit from timeout seconds...
            return self.agi.waitForDigit(self.timeout).addCallback(
                self.processKey).addCallback(self.processLast)
        else:
            df = element.read(self.agi, self.escapeDigits)
            df.addCallback(self.processKey)
            df.addCallback(self.onNext, index=index + 1)
            return df

    def processKey(self, result):
        """Does the pressed key belong to escapeDigits?"""
        if isinstance(result, tuple):
            # getOption result...
            if result[1] == 0:
                # failure during load of the file...
                log.warn("Apparent failure during load of audio file: %s",
                         self.value)
                result = 0
            else:
                result = result[0]
            if isinstance(result, str):
                if result:
                    result = ord(result)
                else:
                    result = 0
        if result:  # None or 0
            # User pressed a key during the reading...
            key = chr(result)
            if key in self.escapeDigits:
                log.info('Exiting early due to user press of: %r', key)
                return key
            else:
                # we don't warn user in this menu if they press an unrecognised key!
                log.info(
                    'Ignoring user keypress because not in escapeDigits: %r',
                    key)
            # completed reading without any escape digits, continue reading
        return None

    def processLast(self, result):
        if result is None:
            result = ''
        return result