def build_date_context(container): """ Builds the date context (i.e. @date.now, @date.today, ...) """ as_date = container.now.date() as_datetime_str = conversions.to_string(container.now, container) as_date_str = conversions.to_string(as_date, container) return { '*': as_datetime_str, 'now': as_datetime_str, 'today': as_date_str, 'tomorrow': conversions.to_string(as_date + timedelta(days=1), container), 'yesterday': conversions.to_string(as_date - timedelta(days=1), container) }
def get_value_as_text(self, context): """ Gets the input value as text which can be matched by rules :param context: the evaluation context :return: the text value """ return conversions.to_string(self.value, context)
def build_context(self, run, container): """ Builds the evaluation context for this contact :param run: the current run state :param container: the containing evaluation context :return: the context """ context = { '*': self.get_display(run.org, False), 'name': self.name, 'first_name': self.get_first_name(run.org), 'tel_e164': self.get_urn_display(run.org, ContactUrn.Scheme.TEL, True), 'groups': ",".join(self.groups), 'uuid': self.uuid, 'language': self.language } # add all URNs for scheme in ContactUrn.Scheme.__members__.values(): context[unicode(scheme.name).lower()] = self.get_urn_display(run.org, scheme, False) # add all fields for key, raw_value in self.fields.iteritems(): field = run.get_or_create_field(key) if field and field.value_type == Field.ValueType.DATETIME: as_datetime = conversions.to_datetime(raw_value, container) value = conversions.to_string(as_datetime, container) else: value = raw_value context[key] = value return context
def rept(ctx, text, number_times): """ Repeats text a given number of times """ if number_times < 0: raise ValueError("Number of times can't be negative") return conversions.to_string(text, ctx) * conversions.to_integer(number_times, ctx)
def datedif(ctx, start_date, end_date, unit): """ Calculates the number of days, months, or years between two dates. """ start_date = conversions.to_date(start_date, ctx) end_date = conversions.to_date(end_date, ctx) unit = conversions.to_string(unit, ctx).lower() if start_date > end_date: raise ValueError("Start date cannot be after end date") if unit == 'y': return relativedelta(end_date, start_date).years elif unit == 'm': delta = relativedelta(end_date, start_date) return 12 * delta.years + delta.months elif unit == 'd': return (end_date - start_date).days elif unit == 'md': return relativedelta(end_date, start_date).days elif unit == 'ym': return relativedelta(end_date, start_date).months elif unit == 'yd': return (end_date - start_date.replace(year=end_date.year)).days raise ValueError("Invalid unit value: %s" % unit)
def visit(self, runner, run, step, input): if logger.isEnabledFor(logging.DEBUG): logger.debug("Visiting rule set %s with input %s from contact %s" % (self.uuid, unicode(input), run.contact.uuid)) input.consume() context = run.build_context(runner, input) match = self.find_matching_rule(runner, run, context) if not match: return None rule, test_result = match # get category in the flow base language category = rule.category.get_localized_by_preferred([run.flow.base_language], "") value_as_str = conversions.to_string(test_result.value, context) result = RuleSet.Result(rule, value_as_str, category, input.get_value_as_text(context)) step.rule_result = result run.update_value(self, result, input.time) return rule.destination
def word_slice(ctx, text, start, stop=0, by_spaces=False): """ Extracts a substring spanning from start up to but not-including stop """ text = conversions.to_string(text, ctx) start = conversions.to_integer(start, ctx) stop = conversions.to_integer(stop, ctx) by_spaces = conversions.to_boolean(by_spaces, ctx) if start == 0: raise ValueError("Start word cannot be zero") elif start > 0: start -= 1 # convert to a zero-based offset if stop == 0: # zero is treated as no end stop = None elif stop > 0: stop -= 1 # convert to a zero-based offset words = __get_words(text, by_spaces) selection = operator.getitem(words, slice(start, stop)) # re-combine selected words with a single space return " ".join(selection)
def remove_first_word(ctx, text): """ Removes the first word from the given text string """ text = conversions.to_string(text, ctx).lstrip() first = first_word(ctx, text) return text[len(first):].lstrip() if first else ''
def word_slice(ctx, text, start, stop=0, by_spaces=False): """ Extracts a substring spanning from start up to but not-including stop """ text = conversions.to_string(text, ctx) start = conversions.to_integer(start, ctx) stop = conversions.to_integer(stop, ctx) by_spaces = conversions.to_boolean(by_spaces, ctx) if start == 0: raise ValueError("Start word cannot be zero") elif start > 0: start -= 1 # convert to a zero-based offset if stop == 0: # zero is treated as no end stop = None elif stop > 0: stop -= 1 # convert to a zero-based offset words = __get_words(text, by_spaces) selection = operator.getitem(words, slice(start, stop)) # re-combine selected words with a single space return ' '.join(selection)
def word_count(ctx, text, by_spaces=False): """ Returns the number of words in the given text string """ text = conversions.to_string(text, ctx) by_spaces = conversions.to_boolean(by_spaces, ctx) return len(__get_words(text, by_spaces))
def visit(self, runner, run, step, input): if logger.isEnabledFor(logging.DEBUG): logger.debug("Visiting rule set %s with input %s from contact %s" % (self.uuid, unicode(input), run.contact.uuid)) input.consume() context = run.build_context(runner, input) match = self.find_matching_rule(runner, run, context) if not match: return None rule, test_result = match # get category in the flow base language category = rule.category.get_localized_by_preferred( [run.get_flow().base_language], "") value_as_str = conversions.to_string(test_result.value, context) result = RuleSet.Result(step.get_flow(), rule, value_as_str, category, input.get_value_as_text(context), input.media) step.rule_result = result run.update_value(self, result, input.time) return rule.destination
def remove_first_word(ctx, text): """ Removes the first word from the given text string """ text = conversions.to_string(text, ctx).lstrip() first = first_word(ctx, text) return text[len(first) :].lstrip() if first else ""
def _unicode(ctx, text): """ Returns a numeric code for the first character in a text string """ text = conversions.to_string(text, ctx) if len(text) == 0: raise ValueError("Text can't be empty") return ord(text[0])
def left(ctx, text, num_chars): """ Returns the first characters in a text string """ num_chars = conversions.to_integer(num_chars, ctx) if num_chars < 0: raise ValueError("Number of chars can't be negative") return conversions.to_string(text, ctx)[0:num_chars]
def concatenate(ctx, *text): """ Joins text strings into one text string """ result = '' for arg in text: result += conversions.to_string(arg, ctx) return result
def build_context(self, container): return { '*': self.value, 'value': self.value, 'category': self.category, 'text': self.text, 'time': conversions.to_string(self.time, container) }
def invoke_function(self, ctx, name, arguments): """ Invokes the given function :param ctx: the evaluation context :param name: the function name (case insensitive) :param arguments: the arguments to be passed to the function :return: the function return value """ from temba_expressions import EvaluationError, conversions # find function with given name func = self.get_function(name) if func is None: raise EvaluationError("Undefined function: %s" % name) args, varargs, defaults = self._get_arg_spec(func) call_args = [] passed_args = list(arguments) for arg in args: if arg == 'ctx': call_args.append(ctx) elif passed_args: call_args.append(passed_args.pop(0)) elif arg in defaults: call_args.append(defaults[arg]) else: raise EvaluationError( "Too few arguments provided for function %s" % name) if varargs is not None: call_args.extend(passed_args) passed_args = [] # any unused arguments? if passed_args: raise EvaluationError( "Too many arguments provided for function %s" % name) try: return func(*call_args) except Exception as e: pretty_args = [] for arg in arguments: if isinstance(arg, str): pretty = '"%s"' % arg else: try: pretty = conversions.to_string(arg, ctx) except EvaluationError: pretty = str(arg) pretty_args.append(pretty) raise EvaluationError( "Error calling function %s with arguments %s" % (name, ', '.join(pretty_args)), e)
def regex_group(ctx, text, pattern, group_num): """ Tries to match the text with the given pattern and returns the value of matching group """ text = conversions.to_string(text, ctx) pattern = conversions.to_string(pattern, ctx) group_num = conversions.to_integer(group_num, ctx) expression = regex.compile( pattern, regex.UNICODE | regex.IGNORECASE | regex.MULTILINE | regex.V0) match = expression.search(text) if not match: return "" if group_num < 0 or group_num > len(match.groups()): raise ValueError("No such matching group %d" % group_num) return match.group(group_num)
def substitute(ctx, text, old_text, new_text, instance_num=-1): """ Substitutes new_text for old_text in a text string """ text = conversions.to_string(text, ctx) old_text = conversions.to_string(old_text, ctx) new_text = conversions.to_string(new_text, ctx) if instance_num < 0: return text.replace(old_text, new_text) else: splits = text.split(old_text) output = splits[0] instance = 1 for split in splits[1:]: sep = new_text if instance == instance_num else old_text output += sep + split instance += 1 return output
def evaluate(self, runner, run, context, text): matches = [] for test in self.tests: result = test.evaluate(runner, run, context, text) if result.matched: matches.append(conversions.to_string(result.value, context)) else: return Test.Result.NO_MATCH # all came out true, we are true return Test.Result.match(" ".join(matches))
def right(ctx, text, num_chars): """ Returns the last characters in a text string """ num_chars = conversions.to_integer(num_chars, ctx) if num_chars < 0: raise ValueError("Number of chars can't be negative") elif num_chars == 0: return '' else: return conversions.to_string(text, ctx)[-num_chars:]
def invoke_function(self, ctx, name, arguments): """ Invokes the given function :param ctx: the evaluation context :param name: the function name (case insensitive) :param arguments: the arguments to be passed to the function :return: the function return value """ from temba_expressions import EvaluationError, conversions # find function with given name func = self.get_function(name) if func is None: raise EvaluationError("Undefined function: %s" % name) args, varargs, defaults = self._get_arg_spec(func) call_args = [] passed_args = list(arguments) for arg in args: if arg == 'ctx': call_args.append(ctx) elif passed_args: call_args.append(passed_args.pop(0)) elif arg in defaults: call_args.append(defaults[arg]) else: raise EvaluationError("Too few arguments provided for function %s" % name) if varargs is not None: call_args.extend(passed_args) passed_args = [] # any unused arguments? if passed_args: raise EvaluationError("Too many arguments provided for function %s" % name) try: return func(*call_args) except Exception, e: pretty_args = [] for arg in arguments: if isinstance(arg, basestring): pretty = '"%s"' % arg else: try: pretty = conversions.to_string(arg, ctx) except EvaluationError: pretty = unicode(arg) pretty_args.append(pretty) raise EvaluationError("Error calling function %s with arguments %s" % (name, ', '.join(pretty_args)), e)
def build_context(self, container, contact_context): """ Builds the evaluation context for this input :param container: the evaluation context :param contact_context: the context :return: """ as_text = self.get_value_as_text(container) return { '*': as_text, 'value': as_text, 'time': conversions.to_string(self.time, container), 'contact': contact_context }
def read_digits(ctx, text): """ Formats digits in text for reading in TTS """ def chunk(value, chunk_size): return [ value[i:i + chunk_size] for i in range(0, len(value), chunk_size) ] text = conversions.to_string(text, ctx).strip() if not text: return '' # trim off the plus for phone numbers if text[0] == '+': text = text[1:] length = len(text) # ssn if length == 9: result = ' '.join(text[:3]) result += ' , ' + ' '.join(text[3:5]) result += ' , ' + ' '.join(text[5:]) return result # triplets, most international phone numbers if length % 3 == 0 and length > 3: chunks = chunk(text, 3) return ' '.join(','.join(chunks)) # quads, credit cards if length % 4 == 0: chunks = chunk(text, 4) return ' '.join(','.join(chunks)) # otherwise, just put a comma between each number return ','.join(text)
def read_digits(ctx, text): """ Formats digits in text for reading in TTS """ def chunk(value, chunk_size): return [value[i : i + chunk_size] for i in range(0, len(value), chunk_size)] text = conversions.to_string(text, ctx).strip() if not text: return "" # trim off the plus for phone numbers if text[0] == "+": text = text[1:] length = len(text) # ssn if length == 9: result = " ".join(text[:3]) result += " , " + " ".join(text[3:5]) result += " , " + " ".join(text[5:]) return result # triplets, most international phone numbers if length % 3 == 0 and length > 3: chunks = chunk(text, 3) return " ".join(",".join(chunks)) # quads, credit cards if length % 4 == 0: chunks = chunk(text, 4) return " ".join(",".join(chunks)) # otherwise, just put a comma between each number return ",".join(text)
def proper(ctx, text): """ Capitalizes the first letter of every word in a text string """ return conversions.to_string(text, ctx).title()
def lower(ctx, text): """ Converts a text string to lowercase """ return conversions.to_string(text, ctx).lower()
def _len(ctx, text): """ Returns the number of characters in a text string """ return len(conversions.to_string(text, ctx))
def format_location(ctx, text): """ Takes a single parameter (administrative boundary as a string) and returns the name of the leaf boundary """ text = conversions.to_string(text, ctx) return text.split(">")[-1].strip()
def clean(ctx, text): """ Removes all non-printable characters from a text string """ text = conversions.to_string(text, ctx) return ''.join([c for c in text if ord(c) >= 32])
def upper(ctx, text): """ Converts a text string to uppercase """ return conversions.to_string(text, ctx).upper()