Example #1
0
    def populate(self, jenv: Environment, params: Dict[str, Any]) -> Dict[str, str]:
        """
        Populate a dict based on the fields definition and provided vars.
        """
        ret = {}

        for key, def_ in self.fields.items():
            if isinstance(def_, bool):
                required = def_
                value = params.get(key)
                if value is None and required:
                    raise KeyError(key)
                remove_if_empty = False
            elif isinstance(def_, str):
                # is a template
                template: Optional[str] = def_
                expr = jenv.compile_expression(cast(str, template))
                value = expr(**params)
                remove_if_empty = False
            elif isinstance(def_, dict):
                template = def_.get("template")
                remove_if_empty = def_["removeIfEmpty"]

                if template is None:
                    required = def_["required"]
                    true_key = def_.get("valueFrom") or key
                    value = params.get(true_key)
                    if value is None and required:
                        raise KeyError(key)
                else:
                    expr = jenv.compile_expression(template)
                    value = expr(**params)
            else:
                raise UnreachableError()

            if value is not None:
                str_value = str(value)

                if not (remove_if_empty and not str_value):
                    ret[key] = str_value
                    continue

        return ret
Example #2
0
def main():

    statment = sys.argv[1]
    arguments = sys.argv[2::]
    argv = dict()

    for a in arguments:
        name, value = a.split("=")
        argv[name] = value

    env = Environment()
    expr = env.compile_expression(statment)
    print(expr(**argv))
Example #3
0
    def parse_overrides(raw_overrides):
        environment = Environment()
        overrides = {}

        for override in raw_overrides:
            if '=' not in override:
                logger.error("settings overrides: invalid format: '%s'",
                             override)

                continue

            name, value = override.split('=', 1)
            value = environment.compile_expression(value)()

            overrides[name] = value

        return overrides
Example #4
0
    def get(self, expression: str, default=None):
        """
        Evaluate a Jinja expression and return the corresponding
        Python object.
        """
        expression = expression.strip().lstrip('{').rstrip('}').strip()
        environment = Environment()
        expression = environment.compile_expression(
            expression,
            undefined_to_none=False
        )
        value = expression(**self._context)

        if isinstance(value, Undefined):
            return default
        else:
            return value
Example #5
0
class UssdHandlerAbstract(object, metaclass=UssdHandlerMetaClass):
    abstract = True

    def __init__(self,
                 ussd_request: UssdRequest,
                 handler: str,
                 screen_content: dict,
                 template_namespace=None,
                 logger=None):
        self.ussd_request = ussd_request
        self.handler = handler
        self.screen_content = screen_content

        self.SINGLE_VAR = re.compile(r"^%s\s*(\w*)\s*%s$" % ('{{', '}}'))
        self.clean_regex = re.compile(r'^{{\s*(\S*)\s*}}$')
        self.logger = logger or get_logger(__name__).bind(
            **ussd_request.all_variables())
        self.template_namespace = template_namespace

        if template_namespace is not None:
            self.template_namespace = staticconf.config.\
                configuration_namespaces[self.template_namespace].\
                configuration_values

        # initialize jinja2 environment
        self.env = Environment(keep_trailing_newline=True)
        self.env.filters.update(_registered_filters)

    def _get_session_items(self) -> dict:
        return dict(iter(self.ussd_request.session.items()))

    def _get_context(self, extra_context=None):
        context = self._get_session_items()
        context.update(dict(ussd_request=self.ussd_request.all_variables()))
        context.update(dict(os.environ))
        if self.template_namespace:
            context.update(self.template_namespace)
        if extra_context is not None:
            context.update(extra_context)

        # add timestamp in the context
        context.update(dict(now=datetime.now()))
        return context

    def _render_text(self, text, context=None, extra=None, encode=None):
        if context is None:
            context = self._get_context()

        if extra:
            context.update(extra)

        template = self.env.from_string(text or '')
        text = template.render(context)
        return json.dumps(text) if encode is 'json' else text

    def get_text(self, text_context=None):
        text_context = self.screen_content.get('text')\
                       if text_context is None \
                       else text_context

        if isinstance(text_context, dict):
            language = self.ussd_request.language \
                   if self.ussd_request.language \
                          in text_context.keys() \
                   else self.ussd_request.default_language

            text_context = text_context[language]

        return self._render_text(text_context)

    def evaluate_jija_expression(self,
                                 expression,
                                 extra_context=None,
                                 lazy_evaluating=False):
        if not isinstance(expression, str) or \
                (lazy_evaluating and not self._contains_vars(expression)):
            return expression

        context = self._get_context(extra_context=extra_context)
        expression = expression.replace("{{", "").replace("}}", "")
        try:
            expr = self.env.compile_expression(expression)
        except TemplateSyntaxError:
            return []
        return expr(context)

    @classmethod
    def validate(cls, screen_name: str, ussd_content: dict) -> (bool, dict):
        screen_content = ussd_content[screen_name]

        validation = cls.serializer(data=screen_content, context=ussd_content)

        if validation.is_valid():
            return True, {}
        return False, validation.errors

    @staticmethod
    def _contains_vars(data):
        '''
        returns True if the data contains a variable pattern
        '''
        if isinstance(data, str):
            for marker in ('{%', '{{', '{#'):
                if marker in data:
                    return True
        return False

    def get_loop_items(self):
        loop_items = self.evaluate_jija_expression(
            self.screen_content["with_items"]) if self.screen_content.get(
                "with_items") else [0] or [0]
        return loop_items
Example #6
0
class ConfigMaker:
    def __init__(self):
        self.registry: Dict[str, Layer] = {}
        self.entries: List[Layer] = []
        self.jinja_env = Environment()

    def _check_record_keys(self, record):
        valid = {
            'config',
            'host',
            'match',
            'merge',
            'name',
            'vars',
        }
        bad_key = next((k for k in record.keys() if k not in valid), None)
        if bad_key:
            raise ValueError(f"Invalid layer attribute {bad_key}")

    def render_value(self, value, locals_, memo: Dict[int, Any] = None):
        """
        Take a mixture of str, int, bool, list, and dict, and substitute str values accordingly

        If the string are surrounded by double open and close curl brackets (eg, "{{ expr }}"),
        try to substitute the str value by the evaluation result of the Jinja expression. Otherwise, the string is rendered
        as Jinja template.
        """
        if isinstance(value, str):
            if value.startswith('{{') and value.endswith('}}'):
                # Try to interpret the expr result if possible, fallback to template string
                try:
                    tpl_expr = self.jinja_env.compile_expression(value[2:-2])
                except TemplateSyntaxError:
                    pass
                else:
                    return tpl_expr(locals_)

            template = self.jinja_env.from_string(value)
            return template.render(locals_)
        elif isinstance(value, (list, dict)):
            if memo is None:
                memo = {}

            id_ = id(value)

            if id_ in memo:
                return memo[id_]

            if isinstance(value, list):
                result = memo[id_] = []

                for item in value:
                    result.append(self.render_value(item, locals_, memo=memo))
            else:
                result = memo[id_] = {}
                for key, val in value.items():
                    result[key] = self.render_value(val, locals_, memo=memo)

            return result
        elif value is None or isinstance(value, (int, bool)):
            return value
        else:
            raise ValueError(f"Bad value type {type(value)!r}")

    def add_record(self, record):
        self._check_record_keys(record)

        name, merge, raw_vars = dict_gets(record, 'name', 'merge', 'vars')

        merged_vars = {}
        merged_config = CIDict()

        merge = yaml_str_list(merge)

        for lower_name in merge:
            try:
                lower_layer = self.registry[lower_name]
            except KeyError:
                raise ValueError(f"Undefined record '{lower_name}'")
            else:
                merged_config = merge_config(merged_config, lower_layer.config)
                merged_vars.update(lower_layer.vars)

        if raw_vars:
            merged_vars.update(self.render_value(raw_vars, {
                **merged_vars,
                'name': name,
            }))

        raw_config, raw_host, raw_match = dict_gets(record, 'config', 'host', 'match')

        host = match = None

        if raw_host is not None:
            if raw_match is not None:
                raise ValueError('Specifying both host and match is prohibited')

            host = yaml_str_list(self.render_value(raw_host, {
                **merged_vars,
                'name': name,
            }))

            if not isinstance(host, list):
                raise ValueError(f'host must be a str or list, got {type(host)!r}')
        elif raw_match is not None:
            match = normalize_match(self.render_value(raw_match, {
                **merged_vars,
                'name': name,
            }))

        config = None
        if raw_config:
            # Substitute values with merged_vars and merged_config
            config = self.render_value(raw_config, {
                **merged_vars,
                'name': name,
                'host': host,
                'match': match,
            })
            merged_config = merge_config(merged_config, config)

        layer = Layer(
            config=merged_config,
            vars=merged_vars,
            host=host,
            match=match,
        )

        if name:
            self.registry[name] = layer

        if host or match:
            self.entries.append(layer)
Example #7
0
def render_sheet(sheet, **data):
    """
    Replaces the Jinja2 placeholders in a given sheet
    """
    if sheet.name.startswith("##"):
        return
    book = sheet.book

    # Shapes aren't properly moved otherwise
    sheet.select()

    # Inserting rows with Frames changes the print area.
    # Get it here so we can revert at the end.
    print_area = sheet.page_setup.print_area

    # A Jinja env defines the placeholder markers and allows to register custom filters
    env = Environment()
    env.filters["datetime"] = filters.datetime
    env.filters[
        "format"] = filters.fmt  # This overrides Jinja's built-in filter
    env.filters["fontcolor"] = filters.fontcolor

    # used_range doesn't start automatically in A1
    last_cell = sheet.used_range.last_cell
    values_all = (sheet.range(
        (1, 1), (last_cell.row, last_cell.column)).options(
            ndim=2).value if sheet.used_range.value else [])

    # Frames
    uses_frames = False
    frame_indices = []
    for ix, cell in enumerate(sheet.range((1, 1), (1, last_cell.column))):
        if cell.note:
            if cell.note.text.strip() == "<frame>":
                frame_indices.append(ix)
                uses_frames = True
    is_single_frame = True if len(frame_indices) == 1 else False
    frame_indices += [0, last_cell.column]
    frame_indices = list(sorted(set(frame_indices)))
    values_per_frame = []
    for ix in range(len(frame_indices) - 1):
        values_per_frame.append(
            [i[frame_indices[ix]:frame_indices[ix + 1]] for i in values_all])

    # Loop through every cell for each frame
    for ix, values in enumerate(values_per_frame):
        row_shift = 0
        for i, row in enumerate(values):
            for j, value in enumerate(row):
                cell = sheet[i + row_shift, j + frame_indices[ix]]
                if isinstance(value, str):
                    if (value.count("{{") == 1 and value.startswith("{{")
                            and value.endswith("}}")):
                        # Cell contains single Jinja variable
                        var, filter_list = parse_single_placeholder(value, env)
                        result = env.compile_expression(var)(**data)
                        if (isinstance(result, Image) or
                            (PIL and isinstance(result, PIL.Image.Image))
                                or (Figure and isinstance(result, Figure))
                                or (plotly and isinstance(
                                    result, plotly.graph_objs.Figure))):

                            # Image filters: these filters can only be used once. If
                            # supplied multiple times, the first one will be used.
                            width = filters.width(filter_list)
                            height = filters.height(filter_list)
                            scale = filters.scale(filter_list)
                            format_ = filters.image_format(filter_list)
                            top = filters.top(filter_list)
                            left = filters.left(filter_list)

                            image_types = (
                                Image, PIL.Image.Image) if PIL else (Image, )
                            image = (result.filename if isinstance(
                                result, image_types) else result)
                            sheet.pictures.add(
                                image,
                                top=top + cell.top,
                                left=left + cell.left,
                                width=width,
                                height=height,
                                scale=scale,
                                format=format_,
                            )
                            cell.value = None
                        elif isinstance(result, (str, numbers.Number)):
                            if any(["fontcolor" in f for f in filter_list]):
                                cell.font.color = filters.fontcolor(
                                    filter_list=filter_list)
                            cell.value = env.from_string(value).render(**data)
                        elif isinstance(result, Markdown):
                            # This will conveniently render placeholders
                            # within Markdown instances
                            cell.value = Markdown(
                                text=env.from_string(
                                    result.text).render(**data),
                                style=result.style,
                            )
                        elif isinstance(result, dt.datetime):
                            cell.value = env.from_string(value).render(**data)
                        else:
                            # Arrays
                            options = {
                                "index": False,
                                "header": True
                            }  # defaults
                            if isinstance(result,
                                          (list, tuple)) and isinstance(
                                              result[0], (list, tuple)):
                                result_len = len(result)
                            elif np and isinstance(result, np.ndarray):
                                result_len = len(result)
                            elif pd and isinstance(result, pd.DataFrame):
                                result = (
                                    result.copy()
                                )  # prevents manipulation of the df in the data dict
                                # DataFrame Filters
                                for filter_item in filter_list:
                                    for filter_name, filter_args in filter_item.items(
                                    ):
                                        if filter_name in ("showindex",
                                                           "noheader"):
                                            continue  # handled below
                                        func = getattr(filters, filter_name)
                                        result = func(result, filter_args)
                                # showindex is undocumented
                                # as df.reset_index() is preferred
                                options = {
                                    "index":
                                    any([
                                        "showindex" in f for f in filter_list
                                    ]),
                                    "header":
                                    not any(
                                        ["noheader" in f
                                         for f in filter_list]),
                                }

                                # Assumes 1 header row,
                                # MultiIndex headers aren't supported
                                if any(["header" in f for f in filter_list]):
                                    # Hack for the 'header' filter
                                    result_len = 1
                                else:
                                    result_len = (len(result) +
                                                  1 if options["header"] else
                                                  len(result))
                            else:
                                result_len = 1
                            # Insert rows if within <frame>
                            # and 'result' is multiple rows high
                            rows_to_be_inserted = 0
                            if uses_frames and result_len > 1:
                                # Deduct header and first data row
                                rows_to_be_inserted = result_len - (
                                    2 if options["header"] else 1)
                                if rows_to_be_inserted > 0:
                                    properties = ({
                                        "screen_updating": True
                                    } if sys.platform.startswith("win") else
                                                  {})
                                    with book.app.properties(**properties):
                                        # Windows doesn't move objects properly with
                                        # screen_updating=False. Since CopyOrigin is
                                        # not supported on Mac, we start copying two
                                        # rows below the header so the data row
                                        # formatting gets carried over.
                                        start_row = (
                                            i + row_shift +
                                            (3 if options["header"] else 2))
                                        start_col = j + frame_indices[ix] + 1
                                        end_row = (
                                            i + row_shift +
                                            rows_to_be_inserted +
                                            (2 if options["header"] else 1))
                                        end_col = frame_indices[ix] + len(
                                            values[0])
                                        if is_single_frame:
                                            # This will preserve the row height of rows
                                            # below the inserted ones
                                            sheet.range(
                                                f"{start_row}:{end_row}"
                                            ).insert("down")
                                        else:
                                            sheet.range(
                                                (start_row, start_col),
                                                (end_row, end_col),
                                            ).insert("down")
                                        # Inserting does not take over borders
                                        sheet.range(
                                            (start_row - 1, start_col),
                                            (start_row - 1, end_col),
                                        ).copy()
                                        sheet.range(
                                            (start_row - 1, start_col),
                                            (end_row, end_col),
                                        ).paste(paste="formats")
                            # Write the array to Excel
                            if cell.table:
                                cell.table.update(result,
                                                  index=options["index"])
                            else:
                                cell.options(**options).value = result
                            row_shift += rows_to_be_inserted
                    elif "{{" in value:
                        # These are strings with (multiple) Jinja variables so apply
                        # standard text rendering here
                        template = env.from_string(value)
                        cell.value = template.render(data)
                    else:
                        # Don't do anything with cells that don't contain any templating
                        # so we don't lose the formatting
                        pass

    # Loop through all shapes of interest with a template text
    for shape in [
            shape for shape in sheet.shapes
            if shape.type in ("auto_shape", "text_box")
    ]:
        shapetext = shape.text
        if shapetext and "{{" in shapetext:
            if (shapetext.count("{{") == 1 and shapetext.startswith("{{")
                    and shapetext.endswith("}}")):
                # Single Jinja variable case, the only case we support with Markdown
                var, filter_list = parse_single_placeholder(shapetext, env)
                result = env.compile_expression(var)(**data)
                if isinstance(result, Markdown):
                    # This will conveniently render placeholders within Markdown text
                    shape.text = Markdown(
                        text=env.from_string(result.text).render(**data),
                        style=result.style,
                    )
                else:
                    # Single Jinja var but no Markdown
                    if any(["fontcolor" in f for f in filter_list]):
                        shape.font.color = filters.fontcolor(
                            filter_list=filter_list)
                    template = env.from_string(shapetext)
                    shape.text = template.render(data)
            else:
                # Multiple Jinja vars and no Markdown
                template = env.from_string(shapetext)
                shape.text = template.render(data)

    # Copy/pasting the formatting leaves ranges selected.
    book.app.cut_copy_mode = False

    # Reset print area
    if print_area:
        sheet.page_setup.print_area = print_area

    try:
        sheet["A1"].select()
    except:
        pass