<commit_before>import logging

from scanpointgenerator.compat import range_, np
from scanpointgenerator.core.generator import Generator
from scanpointgenerator.core.point import Point
from scanpointgenerator.core.excluder import Excluder
from scanpointgenerator.core.mutator import Mutator
from scanpointgenerator.rois import RectangularROI
from scanpointgenerator.generators import LineGenerator

class CompoundGenerator(object):
    """Nest N generators, apply exclusion regions to relevant generator pairs
    and apply any mutators before yielding points"""

    typeid = "scanpointgenerator:generator/CompoundGenerator:1.0"

    def __init__(self, generators, excluders, mutators):
        """
        Args:
            generators(list(Generator)): List of Generators to nest
            excluders(list(Excluder)): List of Excluders to filter points by
            mutators(list(Mutator)): List of Mutators to apply to each point
        """

        self.excluders = excluders
        self.mutators = mutators
        self.axes = []
        self.position_units = {}
        self.index_dims = []
        self.dimensions = []
        self.num = 1
        self.dim_meta = {}
        self.alternate_direction = [g.alternate_direction for g in generators]
        for generator in generators:
            logging.debug("Generator passed to Compound init")
            logging.debug(generator.to_dict())
            if isinstance(generator, self.__class__):
                raise TypeError("CompoundGenerators cannot be nested, nest"
                                "its constituent parts instead")
            self.axes += generator.axes
            self.position_units.update(generator.position_units)
        if len(self.axes) != len(set(self.axes)):
            raise ValueError("Axis names cannot be duplicated")

        self.generators = generators
        self.generator_dim_scaling = {}

    def prepare(self):
        """
        Prepare data structures and masks required for point generation.
        Must be called before get_point or iterator are called.
        """
        # we're going to mutate these structures
        excluders = list(self.excluders)
        generators = list(self.generators)

        # special case if we have rectangular regions on line generators
        # we should restrict the resulting grid rather than merge dimensions
        # this changes the alternating case a little (without doing this, we
        # may have started in reverse direction)
        for rect in [r for r in excluders \
                if isinstance(r.roi, RectangularROI) and r.roi.angle == 0]:
            axis_1, axis_2 = rect.scannables[0], rect.scannables[1]
            gen_1 = [g for g in generators if axis_1 in g.axes][0]
            gen_2 = [g for g in generators if axis_2 in g.axes][0]
            if gen_1 is gen_2:
                continue
            if isinstance(gen_1, LineGenerator) \
                    and isinstance(gen_2, LineGenerator):
                gen_1.produce_points()
                gen_2.produce_points()
                valid = np.full(gen_1.num, True, dtype=np.int8)
                valid &= \
                    gen_1.points[axis_1] <= rect.roi.width + rect.roi.start[0]
                valid &= gen_1.points[axis_1] >= rect.roi.start[0]
                points_1 = gen_1.points[axis_1][valid.astype(np.bool)]
                valid = np.full(gen_2.num, True, dtype=np.int8)
                valid &= \
                    gen_2.points[axis_2] <= rect.roi.height + rect.roi.start[1]
                valid &= gen_2.points[axis_2] >= rect.roi.start[1]
                points_2 = gen_2.points[axis_2][valid.astype(np.bool)]
                new_gen1 = LineGenerator(
                    gen_1.name, gen_1.units, points_1[0], points_1[-1],
                    len(points_1), gen_1.alternate_direction)
                new_gen2 = LineGenerator(
                    gen_2.name, gen_2.units, points_2[0], points_2[-1],
                    len(points_2), gen_2.alternate_direction)
                generators[generators.index(gen_1)] = new_gen1
                generators[generators.index(gen_2)] = new_gen2
                excluders.remove(rect)

        for generator in generators:
            generator.produce_points()
            self.dimensions.append(Dimension(generator))

        for excluder in excluders:
            axis_1, axis_2 = excluder.scannables
            gen_1 = [g for g in generators if axis_1 in g.axes][0]
            gen_2 = [g for g in generators if axis_2 in g.axes][0]
            gen_diff = generators.index(gen_1) \
                - generators.index(gen_2)
            if gen_diff < -1 or gen_diff > 1:
                raise ValueError(
                    "Excluders must be defined on axes that are adjacent in " \
                        "generator order")

            # merge dimensions if region spans two
            dim_1 = [i for i in self.dimensions if axis_1 in i.axes][0]
            dim_2 = [i for i in self.dimensions if axis_2 in i.axes][0]
            dim_diff = self.dimensions.index(dim_1) \
                - self.dimensions.index(dim_2)
            if dim_diff == 1:
                dim_1, dim_2 = dim_2, dim_1
                dim_diff = -1
            if dim_1.alternate != dim_2.alternate \
                    and dim_1 is not self.dimensions[0]:
                raise ValueError(
                    "Generators tied by regions must have the same " \
                            "alternate_direction setting")
            # merge "inner" into "outer"
            if dim_diff == -1:
                # dim_1 is "outer" - preserves axis ordering
                new_dim = Dimension.merge_dimensions(dim_1, dim_2)
                self.dimensions[self.dimensions.index(dim_1)] = new_dim
                self.dimensions.remove(dim_2)
                dim = new_dim
            else:
                dim = dim_1

            dim.apply_excluder(excluder)

        self.num = 1
        for dim in self.dimensions:
            self.dim_meta[dim] = {}
            mask = dim.create_dimension_mask()
            indicies = np.nonzero(mask)[0]
            if len(indicies) == 0:
                raise ValueError("Regions would exclude entire scan")
            self.num *= len(indicies)
            self.dim_meta[dim]["mask"] = mask
            self.dim_meta[dim]["indicies"] = indicies
            self.index_dims.append(len(indicies))

        repeat = self.num
        tile = 1
        for dim in self.dimensions:
            dim_length = len(self.dim_meta[dim]["indicies"])
            repeat /= dim_length
            self.dim_meta[dim]["tile"] = tile
            self.dim_meta[dim]["repeat"] = repeat
            tile *= dim_length

        for dim in self.dimensions:
            tile = 1
            repeat = 1
            for g in dim.generators:
                repeat *= g.num
            for g in dim.generators:
                repeat /= g.num
                d = {"tile":tile, "repeat":repeat}
                tile *= g.num
                self.generator_dim_scaling[g] = d

    def iterator(self):
        """
        Iterator yielding generator positions at each scan point

        Yields:
            Point: The next point
        """
        it = (self.get_point(n) for n in range_(self.num))
        for m in self.mutators:
            it = m.mutate(it)
        for p in it:
            yield p

    def get_point(self, n):
        """
        Retrieve the desired point from the generator
        Args:
            n (int): point to be generated
        """

        if n >= self.num:
            raise IndexError("Requested point is out of range")
        point = Point()

        # need to know how far along each dimension we are
        # and, in the case of alternating indicies, how
        # many times we've run through them
        kc = 0 # the "cumulative" k for each dimension
        for dim in self.dimensions:
            indicies = self.dim_meta[dim]["indicies"]
            i = n // self.dim_meta[dim]["repeat"]
            i %= len(indicies)
            k = indicies[i]
            dim_reverse = False
            if dim.alternate and kc % 2 == 1:
                i = len(indicies) - i - 1
                dim_reverse = True
            kc *= len(indicies)
            kc += k
            k = indicies[i]
            # need point k along each generator in dimension
            # in alternating case, need to sometimes go backward
            point.indexes.append(i)
            for g in dim.generators:
                j = k // self.generator_dim_scaling[g]["repeat"]
                r = j // g.num
                j %= g.num
                j_lower = j
                j_upper = j + 1
                if dim.alternate and g is not dim.generators[0] and r % 2 == 1:
                    # the top level generator's direction is handled by
                    # the fact that the reverse direction was appended
                    j = g.num - j - 1
                    j_lower = j + 1
                    j_upper = j
                elif dim_reverse and g is dim.generators[0]:
                    # top level generator is running in reverse,
                    # so bounds are swapped
                    j_lower, j_upper = j_upper, j_lower
                for axis in g.axes:
                    point.positions[axis] = g.points[axis][j]
                    point.lower[axis] = g.bounds[axis][j_lower]
                    point.upper[axis] = g.bounds[axis][j_upper]
        return point

    def to_dict(self):
        """Convert object attributes into a dictionary"""
        d = {}
        d['typeid'] = self.typeid
        d['generators'] = [g.to_dict() for g in self.generators]
        d['excluders'] = [e.to_dict() for e in self.excluders]
        d['mutators'] = [m.to_dict() for m in self.mutators]
        return d

    @classmethod
    def from_dict(cls, d):
        """
        Create a CompoundGenerator instance from a serialised dictionary
        Args:
            d(dict): Dictionary of attributes
        Returns:
            CompoundGenerator: New CompoundGenerator instance
        """
        generators = [Generator.from_dict(g) for g in d['generators']]
        excluders = [Excluder.from_dict(e) for e in d['excluders']]
        mutators = [Mutator.from_dict(m) for m in d['mutators']]
        return cls(generators, excluders, mutators)

class Dimension(object):
    """A collapsed set of generators joined by excluders"""
    def __init__(self, generator):
        self.axes = list(generator.axes)
        self.generators = [generator]
        self.size = generator.num
        self.masks = []
        self.alternate = generator.alternate_direction

    def apply_excluder(self, excluder):
        """Apply an excluder with axes matching some axes in the dimension to
        produce an internal mask"""
        axis_inner = excluder.scannables[0]
        axis_outer = excluder.scannables[1]
        gen_inner = [g for g in self.generators if axis_inner in g.axes][0]
        gen_outer = [g for g in self.generators if axis_outer in g.axes][0]
        points_x = gen_inner.points[axis_inner]
        points_y = gen_outer.points[axis_outer]
        if self.generators.index(gen_inner) > self.generators.index(gen_outer):
            gen_inner, gen_outer = gen_outer, gen_inner
            axis_inner, axis_outer = axis_outer, axis_inner
            points_x, points_y = points_y, points_x

        if gen_inner is gen_outer and self.alternate:
            points_x = np.append(points_x, points_x[::-1])
            points_y = np.append(points_y, points_y[::-1])
        elif self.alternate:
            points_x = np.append(points_x, points_x[::-1])
            points_x = np.repeat(points_x, gen_outer.num)
            points_y = np.append(points_y, points_y[::-1])
            points_y = np.tile(points_y, gen_inner.num)
        elif gen_inner is not gen_outer:
            points_x = np.repeat(points_x, gen_outer.num)
            points_y = np.tile(points_y, gen_inner.num)
        else:
            # copy the point arrays so the excluders can perform
            # array operations in place (advantageous in the other cases)
            points_x = np.copy(points_x)
            points_y = np.copy(points_y)

        if axis_inner == excluder.scannables[0]:
            mask = excluder.create_mask(points_x, points_y)
        else:
            mask = excluder.create_mask(points_y, points_x)
        tile = 0.5 if self.alternate else 1
        repeat = 1
        found_axis = False
        for g in self.generators:
            if axis_inner in g.axes or axis_outer in g.axes:
                found_axis = True
            else:
                if found_axis:
                    repeat *= g.num
                else:
                    tile *= g.num

        m = {"repeat":repeat, "tile":tile, "mask":mask}
        self.masks.append(m)

    def create_dimension_mask(self):
        """
        Create and return a mask for every point in the dimension

        e.g. (with [y1, y2, y3] and [x1, x2, x3] both alternating)
        y:    y1, y1, y1, y2, y2, y2, y3, y3, y3
        x:    x1, x2, x3, x3, x2, x1, x1, x2, x3
        mask: m1, m2, m3, m4, m5, m6, m7, m8, m9

        Returns:
            np.array(int8): One dimensional mask array
        """
        mask = np.full(self.size, True, dtype=np.int8)
        for m in self.masks:
            assert len(m["mask"]) * m["repeat"] * m["tile"] == len(mask), \
                "Mask lengths are not consistent"
            expanded = np.repeat(m["mask"], m["repeat"])
            if m["tile"] % 1 != 0:
                ex = np.tile(expanded, int(m["tile"]))
                expanded = np.append(ex, expanded[:len(expanded)//2])
            else:
                expanded = np.tile(expanded, int(m["tile"]))
            mask &= expanded
        return mask

    @staticmethod
    def merge_dimensions(outer, inner):
        """Collapse two dimensions into one, appropriate scaling structures"""
        dim = Dimension(outer.generators[0])
        # masks in the inner generator are tiled by the size of
        # outer generators and outer generators have their elements
        # repeated by the size of inner generators
        inner_masks = [m.copy() for m in inner.masks]
        outer_masks = [m.copy() for m in outer.masks]
        scale = 1
        for g in inner.generators:
            scale *= g.num
        for m in outer_masks:
            m["repeat"] *= scale
        scale = 1
        for g in outer.generators:
            scale *= g.num
        for m in inner_masks:
            m["tile"] *= scale
        dim.masks = outer_masks + inner_masks
        dim.axes = outer.axes + inner.axes
        dim.generators = outer.generators + inner.generators
        dim.alternate = outer.alternate or inner.alternate
        dim.size = outer.size * inner.size
        return dim

<commit_msg>Only apply bounds to innermost generator in CompoundGenerator<commit_after>import logging

from scanpointgenerator.compat import range_, np
from scanpointgenerator.core.generator import Generator
from scanpointgenerator.core.point import Point
from scanpointgenerator.core.excluder import Excluder
from scanpointgenerator.core.mutator import Mutator
from scanpointgenerator.rois import RectangularROI
from scanpointgenerator.generators import LineGenerator

class CompoundGenerator(object):
    """Nest N generators, apply exclusion regions to relevant generator pairs
    and apply any mutators before yielding points"""

    typeid = "scanpointgenerator:generator/CompoundGenerator:1.0"

    def __init__(self, generators, excluders, mutators):
        """
        Args:
            generators(list(Generator)): List of Generators to nest
            excluders(list(Excluder)): List of Excluders to filter points by
            mutators(list(Mutator)): List of Mutators to apply to each point
        """

        self.excluders = excluders
        self.mutators = mutators
        self.axes = []
        self.position_units = {}
        self.index_dims = []
        self.dimensions = []
        self.num = 1
        self.dim_meta = {}
        self.alternate_direction = [g.alternate_direction for g in generators]
        for generator in generators:
            logging.debug("Generator passed to Compound init")
            logging.debug(generator.to_dict())
            if isinstance(generator, self.__class__):
                raise TypeError("CompoundGenerators cannot be nested, nest"
                                "its constituent parts instead")
            self.axes += generator.axes
            self.position_units.update(generator.position_units)
        if len(self.axes) != len(set(self.axes)):
            raise ValueError("Axis names cannot be duplicated")

        self.generators = generators
        self.generator_dim_scaling = {}

    def prepare(self):
        """
        Prepare data structures and masks required for point generation.
        Must be called before get_point or iterator are called.
        """
        # we're going to mutate these structures
        excluders = list(self.excluders)
        generators = list(self.generators)

        # special case if we have rectangular regions on line generators
        # we should restrict the resulting grid rather than merge dimensions
        # this changes the alternating case a little (without doing this, we
        # may have started in reverse direction)
        for rect in [r for r in excluders \
                if isinstance(r.roi, RectangularROI) and r.roi.angle == 0]:
            axis_1, axis_2 = rect.scannables[0], rect.scannables[1]
            gen_1 = [g for g in generators if axis_1 in g.axes][0]
            gen_2 = [g for g in generators if axis_2 in g.axes][0]
            if gen_1 is gen_2:
                continue
            if isinstance(gen_1, LineGenerator) \
                    and isinstance(gen_2, LineGenerator):
                gen_1.produce_points()
                gen_2.produce_points()
                valid = np.full(gen_1.num, True, dtype=np.int8)
                valid &= \
                    gen_1.points[axis_1] <= rect.roi.width + rect.roi.start[0]
                valid &= gen_1.points[axis_1] >= rect.roi.start[0]
                points_1 = gen_1.points[axis_1][valid.astype(np.bool)]
                valid = np.full(gen_2.num, True, dtype=np.int8)
                valid &= \
                    gen_2.points[axis_2] <= rect.roi.height + rect.roi.start[1]
                valid &= gen_2.points[axis_2] >= rect.roi.start[1]
                points_2 = gen_2.points[axis_2][valid.astype(np.bool)]
                new_gen1 = LineGenerator(
                    gen_1.name, gen_1.units, points_1[0], points_1[-1],
                    len(points_1), gen_1.alternate_direction)
                new_gen2 = LineGenerator(
                    gen_2.name, gen_2.units, points_2[0], points_2[-1],
                    len(points_2), gen_2.alternate_direction)
                generators[generators.index(gen_1)] = new_gen1
                generators[generators.index(gen_2)] = new_gen2
                excluders.remove(rect)

        for generator in generators:
            generator.produce_points()
            self.dimensions.append(Dimension(generator))

        for excluder in excluders:
            axis_1, axis_2 = excluder.scannables
            gen_1 = [g for g in generators if axis_1 in g.axes][0]
            gen_2 = [g for g in generators if axis_2 in g.axes][0]
            gen_diff = generators.index(gen_1) \
                - generators.index(gen_2)
            if gen_diff < -1 or gen_diff > 1:
                raise ValueError(
                    "Excluders must be defined on axes that are adjacent in " \
                        "generator order")

            # merge dimensions if region spans two
            dim_1 = [i for i in self.dimensions if axis_1 in i.axes][0]
            dim_2 = [i for i in self.dimensions if axis_2 in i.axes][0]
            dim_diff = self.dimensions.index(dim_1) \
                - self.dimensions.index(dim_2)
            if dim_diff == 1:
                dim_1, dim_2 = dim_2, dim_1
                dim_diff = -1
            if dim_1.alternate != dim_2.alternate \
                    and dim_1 is not self.dimensions[0]:
                raise ValueError(
                    "Generators tied by regions must have the same " \
                            "alternate_direction setting")
            # merge "inner" into "outer"
            if dim_diff == -1:
                # dim_1 is "outer" - preserves axis ordering
                new_dim = Dimension.merge_dimensions(dim_1, dim_2)
                self.dimensions[self.dimensions.index(dim_1)] = new_dim
                self.dimensions.remove(dim_2)
                dim = new_dim
            else:
                dim = dim_1

            dim.apply_excluder(excluder)

        self.num = 1
        for dim in self.dimensions:
            self.dim_meta[dim] = {}
            mask = dim.create_dimension_mask()
            indicies = np.nonzero(mask)[0]
            if len(indicies) == 0:
                raise ValueError("Regions would exclude entire scan")
            self.num *= len(indicies)
            self.dim_meta[dim]["mask"] = mask
            self.dim_meta[dim]["indicies"] = indicies
            self.index_dims.append(len(indicies))

        repeat = self.num
        tile = 1
        for dim in self.dimensions:
            dim_length = len(self.dim_meta[dim]["indicies"])
            repeat /= dim_length
            self.dim_meta[dim]["tile"] = tile
            self.dim_meta[dim]["repeat"] = repeat
            tile *= dim_length

        for dim in self.dimensions:
            tile = 1
            repeat = 1
            for g in dim.generators:
                repeat *= g.num
            for g in dim.generators:
                repeat /= g.num
                d = {"tile":tile, "repeat":repeat}
                tile *= g.num
                self.generator_dim_scaling[g] = d

    def iterator(self):
        """
        Iterator yielding generator positions at each scan point

        Yields:
            Point: The next point
        """
        it = (self.get_point(n) for n in range_(self.num))
        for m in self.mutators:
            it = m.mutate(it)
        for p in it:
            yield p

    def get_point(self, n):
        """
        Retrieve the desired point from the generator
        Args:
            n (int): point to be generated
        """

        if n >= self.num:
            raise IndexError("Requested point is out of range")
        point = Point()

        # need to know how far along each dimension we are
        # and, in the case of alternating indicies, how
        # many times we've run through them
        kc = 0 # the "cumulative" k for each dimension
        for dim in self.dimensions:
            indicies = self.dim_meta[dim]["indicies"]
            i = n // self.dim_meta[dim]["repeat"]
            i %= len(indicies)
            k = indicies[i]
            dim_reverse = False
            if dim.alternate and kc % 2 == 1:
                i = len(indicies) - i - 1
                dim_reverse = True
            kc *= len(indicies)
            kc += k
            k = indicies[i]
            # need point k along each generator in dimension
            # in alternating case, need to sometimes go backward
            point.indexes.append(i)
            for g in dim.generators:
                j = k // self.generator_dim_scaling[g]["repeat"]
                r = j // g.num
                j %= g.num
                j_lower = j
                j_upper = j + 1
                if dim.alternate and g is not dim.generators[0] and r % 2 == 1:
                    # the top level generator's direction is handled by
                    # the fact that the reverse direction was appended
                    j = g.num - j - 1
                    j_lower = j + 1
                    j_upper = j
                elif dim_reverse and g is dim.generators[0]:
                    # top level generator is running in reverse,
                    # so bounds are swapped
                    j_lower, j_upper = j_upper, j_lower
                for axis in g.axes:
                    point.positions[axis] = g.points[axis][j]
                    if g is self.generators[-1]:
                        point.lower[axis] = g.bounds[axis][j_lower]
                        point.upper[axis] = g.bounds[axis][j_upper]
                    else:
                        point.lower[axis] = g.points[axis][j]
                        point.upper[axis] = g.points[axis][j]
        return point

    def to_dict(self):
        """Convert object attributes into a dictionary"""
        d = {}
        d['typeid'] = self.typeid
        d['generators'] = [g.to_dict() for g in self.generators]
        d['excluders'] = [e.to_dict() for e in self.excluders]
        d['mutators'] = [m.to_dict() for m in self.mutators]
        return d

    @classmethod
    def from_dict(cls, d):
        """
        Create a CompoundGenerator instance from a serialised dictionary
        Args:
            d(dict): Dictionary of attributes
        Returns:
            CompoundGenerator: New CompoundGenerator instance
        """
        generators = [Generator.from_dict(g) for g in d['generators']]
        excluders = [Excluder.from_dict(e) for e in d['excluders']]
        mutators = [Mutator.from_dict(m) for m in d['mutators']]
        return cls(generators, excluders, mutators)

class Dimension(object):
    """A collapsed set of generators joined by excluders"""
    def __init__(self, generator):
        self.axes = list(generator.axes)
        self.generators = [generator]
        self.size = generator.num
        self.masks = []
        self.alternate = generator.alternate_direction

    def apply_excluder(self, excluder):
        """Apply an excluder with axes matching some axes in the dimension to
        produce an internal mask"""
        axis_inner = excluder.scannables[0]
        axis_outer = excluder.scannables[1]
        gen_inner = [g for g in self.generators if axis_inner in g.axes][0]
        gen_outer = [g for g in self.generators if axis_outer in g.axes][0]
        points_x = gen_inner.points[axis_inner]
        points_y = gen_outer.points[axis_outer]
        if self.generators.index(gen_inner) > self.generators.index(gen_outer):
            gen_inner, gen_outer = gen_outer, gen_inner
            axis_inner, axis_outer = axis_outer, axis_inner
            points_x, points_y = points_y, points_x

        if gen_inner is gen_outer and self.alternate:
            points_x = np.append(points_x, points_x[::-1])
            points_y = np.append(points_y, points_y[::-1])
        elif self.alternate:
            points_x = np.append(points_x, points_x[::-1])
            points_x = np.repeat(points_x, gen_outer.num)
            points_y = np.append(points_y, points_y[::-1])
            points_y = np.tile(points_y, gen_inner.num)
        elif gen_inner is not gen_outer:
            points_x = np.repeat(points_x, gen_outer.num)
            points_y = np.tile(points_y, gen_inner.num)
        else:
            # copy the point arrays so the excluders can perform
            # array operations in place (advantageous in the other cases)
            points_x = np.copy(points_x)
            points_y = np.copy(points_y)

        if axis_inner == excluder.scannables[0]:
            mask = excluder.create_mask(points_x, points_y)
        else:
            mask = excluder.create_mask(points_y, points_x)
        tile = 0.5 if self.alternate else 1
        repeat = 1
        found_axis = False
        for g in self.generators:
            if axis_inner in g.axes or axis_outer in g.axes:
                found_axis = True
            else:
                if found_axis:
                    repeat *= g.num
                else:
                    tile *= g.num

        m = {"repeat":repeat, "tile":tile, "mask":mask}
        self.masks.append(m)

    def create_dimension_mask(self):
        """
        Create and return a mask for every point in the dimension

        e.g. (with [y1, y2, y3] and [x1, x2, x3] both alternating)
        y:    y1, y1, y1, y2, y2, y2, y3, y3, y3
        x:    x1, x2, x3, x3, x2, x1, x1, x2, x3
        mask: m1, m2, m3, m4, m5, m6, m7, m8, m9

        Returns:
            np.array(int8): One dimensional mask array
        """
        mask = np.full(self.size, True, dtype=np.int8)
        for m in self.masks:
            assert len(m["mask"]) * m["repeat"] * m["tile"] == len(mask), \
                "Mask lengths are not consistent"
            expanded = np.repeat(m["mask"], m["repeat"])
            if m["tile"] % 1 != 0:
                ex = np.tile(expanded, int(m["tile"]))
                expanded = np.append(ex, expanded[:len(expanded)//2])
            else:
                expanded = np.tile(expanded, int(m["tile"]))
            mask &= expanded
        return mask

    @staticmethod
    def merge_dimensions(outer, inner):
        """Collapse two dimensions into one, appropriate scaling structures"""
        dim = Dimension(outer.generators[0])
        # masks in the inner generator are tiled by the size of
        # outer generators and outer generators have their elements
        # repeated by the size of inner generators
        inner_masks = [m.copy() for m in inner.masks]
        outer_masks = [m.copy() for m in outer.masks]
        scale = 1
        for g in inner.generators:
            scale *= g.num
        for m in outer_masks:
            m["repeat"] *= scale
        scale = 1
        for g in outer.generators:
            scale *= g.num
        for m in inner_masks:
            m["tile"] *= scale
        dim.masks = outer_masks + inner_masks
        dim.axes = outer.axes + inner.axes
        dim.generators = outer.generators + inner.generators
        dim.alternate = outer.alternate or inner.alternate
        dim.size = outer.size * inner.size
        return dim

<|endoftext|>


<commit_before>from typing import List
from discord import Role, Color, role

from ..bunkbot import BunkBot
from ..channel.channel_service import ChannelService
from ..core.bunk_user import BunkUser
from ..core.service import Service
from ..db.database_service import DatabaseService

class RoleService(Service):
    """
    Service responsible for handling role references
    and removing/adding new roles

    Parameters
    -----------
    bot: Bunkbot
        Super class instance of the bot

    database: DatabaseService
        Super class instance of the database service

    channels: ChannelService
        Access to the server channels and other channel functions
    """
    def __init__(self, bot: BunkBot, database: DatabaseService, channels: ChannelService):
        super().__init__(bot, database)
        self.admin: Role = None
        self.channels: ChannelService = channels

    def get_role(self, role_name: str) -> Role:
        """
        Get a role directly from the server by name

        Parameters
        -----------
        role_name: str
            Name of the role to retrieve from the server
        """
        return next((role for role in self.server.roles if role.name == role_name), None)

    def get_role_by_pattern(self, pattern: str, roles: List[Role] = None) -> Role:
        """
        Get a role directly from the server with a pattern "contains"

        Parameters
        -----------
        pattern: str
            Pattern which will be used to fuzzy search a role name

        roles: List[Role] (optional)
            Optional list of roles to search if the default server is not used
        """
        if roles is None:
            roles = self.server.roles

        return next((role for role in roles if pattern in role.name), None)

    async def rm_role(self, role_name: str, user: BunkUser = None) -> None:
        """
        Non-event driven - directly remove a role when another service has deemed appropriate

        Parameters
        -----------
        role_name: str
            Name of the role to remove

        user: Bunkuser (optional)
            When supplied, the role will be removed from a user rather than the server list
        """
        if user is not None:
            roles = user.member.roles.copy()
            roles = [r for r in user.member.roles if r.name != role_name]
            await user.set_roles(roles)
        else:
            roles: List[Role] = [r for r in self.bot.server.roles.copy() if r.name == role_name]
            for role in roles:
                ref: Role = role
                await ref.delete()

    async def rm_roles_from_user(self, role_names: List[str], user: BunkUser) -> None:
        """
        Non-event driven - directly remove a role when another service has deemed appropriate

        Parameters
        -----------
        role_names: List[str]
            List of the roles to remove

        user: Bunkuser
            User from which the roles will be removed from a user
        """
        roles: List[Role] = user.member.roles.copy()
        new_roles: List[Role] = [r for r in roles if not r.name not in role_names]

        await user.set_roles(new_roles)

    async def add_role_to_user(self, role_name: str, user: BunkUser, color: Color = None) -> Role:
        """
        Non-event driven - directly add a role when another service has deemed appropriate

        Parameters
        -----------
        role_name: str
            Name of the role to add

        user: BunkUser
            User which to add the role

        color: Color (optional)
            Optionally add a color to the role

        Returns
        --------
        Role added to the user
        """
        roles: List[Role] = await self._get_user_roles_to_set(user.member.roles.copy(), role_name, user, color)
        await user.set_roles(roles)

        return self.get_role(role_name)

    async def add_roles_to_user(self, role_names: List[str], user: BunkUser, color: Color = None) -> List[Role]:
        """
        Non-event driven - directly add multiple roles when another service has deemed appropriate

        Parameters
        -----------
        role_names: List[str]
            List of roles to add to the user

        user: BunkUser
            User which to add the roles

        color: Color (optional)
            Optionally add a color to the roles

        Returns
        --------
        Roles added to the user
        """
        roles = user.member.roles.copy()
        for role_name in role_names:
            roles = await self._get_user_roles_to_set(roles, role_name, user, color)

        await user.set_roles(roles)
        return roles

    async def _get_user_roles_to_set(self, current_roles: List[Role], role_name: str, user: BunkUser, color: Color = None) -> List[Role]:
        if not user.has_role(role_name):
            role = self.get_role(role_name)

            if role is None:
                if color is None:
                    role: Role = await self.bot.server.create_role(name=role_name)
                else:
                    role: Role = await self.bot.server.create_role(name=role_name, color=color)

            current_roles.append(role)
        return current_roles

    def _get_user_roles_to_rm(self, role_name: str, user: BunkUser) -> List[Role]:
        roles_to_rm: List[Role] = []
        if user.has_role(role_name):
            role = self.get_role(role_name)
            roles_to_rm.append(role)

        return roles_to_rm

    async def prune_orphaned_roles(self, pattern: str = None) -> None:
        """
        When updating users/roles check for roles which are no longer being used

        Parameters
        -----------
        pattern: str (optional)
            Only pruned orphaned roles that contain a specific pattern in the name
        """
        if self.bot.server is None:
            pass
        else:
            empty_color_roles: List[str] = []

            if pattern is None:
                empty_color_roles = [r.name for r in self.bot.server.roles if len(r.members) == 0]
            else:
                empty_color_roles = [r.name for r in self.bot.server.roles if pattern in r.name and len(r.members) == 0]

            for orphan_role in empty_color_roles:
                await self.channels.log_info("Removing role `{0}`".format(orphan_role))
                await self.rm_role(orphan_role)

    async def get_role_containing(self, pattern: str, user: BunkUser) -> Role:
        """
        Get a user role that contains a given pattern in the name

        Parameters
        -----------
        pattern: str
            Pattern which the role name must contain

        user: BunkUser
            User which to find the role
        """
        role = next((r for r in user.member.roles if pattern in r.name.lower()), None)

        return role

    async def get_lowest_index_for(self, pattern: str) -> int:
        """
        Get the server role index of a given role name (pattern)

        Parameters
        -----------
        pattern: str
            Pattern which to locate a role by it's index
        """
        roles: List[int] = [r.position for r in self.bot.server.roles if pattern in r.name]
        roles.sort()

        if len(roles) == 0:
            return 1

        return roles[:1][0]
<commit_msg>fix: rly<commit_after>from typing import List
from discord import Role, Color, role

from ..bunkbot import BunkBot
from ..channel.channel_service import ChannelService
from ..core.bunk_user import BunkUser
from ..core.service import Service
from ..db.database_service import DatabaseService

class RoleService(Service):
    """
    Service responsible for handling role references
    and removing/adding new roles

    Parameters
    -----------
    bot: Bunkbot
        Super class instance of the bot

    database: DatabaseService
        Super class instance of the database service

    channels: ChannelService
        Access to the server channels and other channel functions
    """
    def __init__(self, bot: BunkBot, database: DatabaseService, channels: ChannelService):
        super().__init__(bot, database)
        self.admin: Role = None
        self.channels: ChannelService = channels

    def get_role(self, role_name: str) -> Role:
        """
        Get a role directly from the server by name

        Parameters
        -----------
        role_name: str
            Name of the role to retrieve from the server
        """
        return next((role for role in self.server.roles if role.name == role_name), None)

    def get_role_by_pattern(self, pattern: str, roles: List[Role] = None) -> Role:
        """
        Get a role directly from the server with a pattern "contains"

        Parameters
        -----------
        pattern: str
            Pattern which will be used to fuzzy search a role name

        roles: List[Role] (optional)
            Optional list of roles to search if the default server is not used
        """
        if roles is None:
            roles = self.server.roles

        return next((role for role in roles if pattern in role.name), None)

    async def rm_role(self, role_name: str, user: BunkUser = None) -> None:
        """
        Non-event driven - directly remove a role when another service has deemed appropriate

        Parameters
        -----------
        role_name: str
            Name of the role to remove

        user: Bunkuser (optional)
            When supplied, the role will be removed from a user rather than the server list
        """
        if user is not None:
            roles = user.member.roles.copy()
            roles = [r for r in user.member.roles if r.name != role_name]
            await user.set_roles(roles)
        else:
            roles: List[Role] = [r for r in self.bot.server.roles.copy() if r.name == role_name]
            for role in roles:
                ref: Role = role
                await ref.delete()

    async def rm_roles_from_user(self, role_names: List[str], user: BunkUser) -> None:
        """
        Non-event driven - directly remove a role when another service has deemed appropriate

        Parameters
        -----------
        role_names: List[str]
            List of the roles to remove

        user: Bunkuser
            User from which the roles will be removed from a user
        """
        roles: List[Role] = user.member.roles.copy()
        new_roles: List[Role] = [r for r in roles if r.name not in role_names]

        await user.set_roles(new_roles)

    async def add_role_to_user(self, role_name: str, user: BunkUser, color: Color = None) -> Role:
        """
        Non-event driven - directly add a role when another service has deemed appropriate

        Parameters
        -----------
        role_name: str
            Name of the role to add

        user: BunkUser
            User which to add the role

        color: Color (optional)
            Optionally add a color to the role

        Returns
        --------
        Role added to the user
        """
        roles: List[Role] = await self._get_user_roles_to_set(user.member.roles.copy(), role_name, user, color)
        await user.set_roles(roles)

        return self.get_role(role_name)

    async def add_roles_to_user(self, role_names: List[str], user: BunkUser, color: Color = None) -> List[Role]:
        """
        Non-event driven - directly add multiple roles when another service has deemed appropriate

        Parameters
        -----------
        role_names: List[str]
            List of roles to add to the user

        user: BunkUser
            User which to add the roles

        color: Color (optional)
            Optionally add a color to the roles

        Returns
        --------
        Roles added to the user
        """
        roles = user.member.roles.copy()
        for role_name in role_names:
            roles = await self._get_user_roles_to_set(roles, role_name, user, color)

        await user.set_roles(roles)
        return roles

    async def _get_user_roles_to_set(self, current_roles: List[Role], role_name: str, user: BunkUser, color: Color = None) -> List[Role]:
        if not user.has_role(role_name):
            role = self.get_role(role_name)

            if role is None:
                if color is None:
                    role: Role = await self.bot.server.create_role(name=role_name)
                else:
                    role: Role = await self.bot.server.create_role(name=role_name, color=color)

            current_roles.append(role)
        return current_roles

    async def prune_orphaned_roles(self, pattern: str = None) -> None:
        """
        When updating users/roles check for roles which are no longer being used

        Parameters
        -----------
        pattern: str (optional)
            Only pruned orphaned roles that contain a specific pattern in the name
        """
        if self.bot.server is None:
            pass
        else:
            empty_color_roles: List[str] = []

            if pattern is None:
                empty_color_roles = [r.name for r in self.bot.server.roles if len(r.members) == 0]
            else:
                empty_color_roles = [r.name for r in self.bot.server.roles if pattern in r.name and len(r.members) == 0]

            for orphan_role in empty_color_roles:
                await self.channels.log_info("Removing role `{0}`".format(orphan_role))
                await self.rm_role(orphan_role)

    async def get_role_containing(self, pattern: str, user: BunkUser) -> Role:
        """
        Get a user role that contains a given pattern in the name

        Parameters
        -----------
        pattern: str
            Pattern which the role name must contain

        user: BunkUser
            User which to find the role
        """
        role = next((r for r in user.member.roles if pattern in r.name.lower()), None)

        return role

    async def get_lowest_index_for(self, pattern: str) -> int:
        """
        Get the server role index of a given role name (pattern)

        Parameters
        -----------
        pattern: str
            Pattern which to locate a role by it's index
        """
        roles: List[int] = [r.position for r in self.bot.server.roles if pattern in r.name]
        roles.sort()

        if len(roles) == 0:
            return 1

        return roles[:1][0]
<|endoftext|>


<commit_before>from nose.tools import *

import cli
import json

def test_node_to_path():
    obj = json.load(open('tests/data.json'))
    nodes = cli.get_result_nodes('.owner', obj)
    eq_(1, len(nodes))

    eq_(['wut', 'metadata', 'owner'], cli.node_to_path(nodes[0]))

    nodes = cli.get_result_nodes('.foo', obj)
    eq_(1, len(nodes))
    eq_(['foo'], cli.node_to_path(nodes[0]))

    nodes = cli.get_result_nodes('.baz', obj)
    eq_(1, len(nodes))
    eq_(['foo', 2, 'baz'], cli.node_to_path(nodes[0]))

def test_select_path_dict():
    obj = { 'foo': { 'bar': 'baz' }, 'quux': 'wut' }
    out = {}
    cli.select_path(['foo', 'bar'], obj, out)
    eq_({'foo': {'bar': 'baz'}}, out)

    out = {}
    cli.select_path(['quux'], obj, out)
    eq_({'quux': 'wut'}, out)

def test_select_path_int():
    obj = [ 'a', 'b', 'c' ]
    out = []
    cli.select_path([1], obj, out)
    eq_(['a'], out)
    cli.select_path([3], obj, out)
    eq_(['a', 'c'], out)

def test_mixed_select():
    obj = { 'foo': [ { 'bar': 'baz', 'a': 'b' }, { 'x': 'y', 'z': 4 } ] }
    out = {}
    cli.select_path(['foo', 2, 'z'], obj, out)
    eq_({'foo': [{ 'z': 4 }]}, out)

    obj = {'foo': ['bar', {'baz': 'quux'}]}
    out = {}
    cli.select_path(['foo', 1], obj, out)
    eq_({'foo': ['bar']}, out)

def test_nonempty_out():
    '''cli.select_path should preserve any objects already in out_obj.'''
    obj = ['bar', {'baz': 'quux'}]
    out = ['bar']
    cli.select_path([2], obj, out)
    eq_(['bar', {'baz': 'quux'}], out)

    obj = {'foo': ['bar', {'baz': 'quux'}]}
    out = {'foo': ['bar']}

    cli.select_path(['foo', 2], obj, out)
    eq_({'foo': ['bar', {'baz': 'quux'}]}, out)

#def test_select_paths():
#    obj = json.load(open('tests/data.json'))
#    eq_({'foo': ['bar', {'baz': 'quux'}]},
#        cli.select_paths([['foo', 1], ['foo', 2, 'baz']], obj))
<commit_msg>select_paths test passes<commit_after>from nose.tools import *

import cli
import json

def test_node_to_path():
    obj = json.load(open('tests/data.json'))
    nodes = cli.get_result_nodes('.owner', obj)
    eq_(1, len(nodes))

    eq_(['wut', 'metadata', 'owner'], cli.node_to_path(nodes[0]))

    nodes = cli.get_result_nodes('.foo', obj)
    eq_(1, len(nodes))
    eq_(['foo'], cli.node_to_path(nodes[0]))

    nodes = cli.get_result_nodes('.baz', obj)
    eq_(1, len(nodes))
    eq_(['foo', 2, 'baz'], cli.node_to_path(nodes[0]))

def test_select_path_dict():
    obj = { 'foo': { 'bar': 'baz' }, 'quux': 'wut' }
    out = {}
    cli.select_path(['foo', 'bar'], obj, out)
    eq_({'foo': {'bar': 'baz'}}, out)

    out = {}
    cli.select_path(['quux'], obj, out)
    eq_({'quux': 'wut'}, out)

def test_select_path_int():
    obj = [ 'a', 'b', 'c' ]
    out = []
    cli.select_path([1], obj, out)
    eq_(['a'], out)
    cli.select_path([3], obj, out)
    eq_(['a', 'c'], out)

def test_mixed_select():
    obj = { 'foo': [ { 'bar': 'baz', 'a': 'b' }, { 'x': 'y', 'z': 4 } ] }
    out = {}
    cli.select_path(['foo', 2, 'z'], obj, out)
    eq_({'foo': [{ 'z': 4 }]}, out)

    obj = {'foo': ['bar', {'baz': 'quux'}]}
    out = {}
    cli.select_path(['foo', 1], obj, out)
    eq_({'foo': ['bar']}, out)

def test_nonempty_out():
    '''cli.select_path should preserve any objects already in out_obj.'''
    obj = ['bar', {'baz': 'quux'}]
    out = ['bar']
    cli.select_path([2], obj, out)
    eq_(['bar', {'baz': 'quux'}], out)

    obj = {'foo': ['bar', {'baz': 'quux'}]}
    out = {'foo': ['bar']}

    cli.select_path(['foo', 2], obj, out)
    eq_({'foo': ['bar', {'baz': 'quux'}]}, out)

def test_select_paths():
    obj = json.load(open('tests/data.json'))
    eq_({'foo': ['bar', {'baz': 'quux'}]},
        cli.select_paths([['foo', 1], ['foo', 2, 'baz']], obj))
<|endoftext|>


<commit_before># -*- coding: UTF-8 -*-
# Copyright (C) 2009 Itaapy, ArsAperta, Pierlis, Talend

# Import from the Standard Library
from copy import deepcopy

# Import from lpod
from container import odf_get_container, odf_new_container_from_template
from container import odf_new_container_from_class, odf_container
from xmlpart import odf_element, odf_xmlpart, LAST_CHILD
from xmlpart import odf_create_element

#
# odf creation functions
#

def odf_create_section(style):
    data = '<text:section text:style-name="%s"></text:section>' % style
    return odf_create_element(data)

def odf_create_paragraph(style, text=''):
    data = '<text:p text:style-name="%s">%s</text:p>' % (style, text)
    return odf_create_element(data)

def odf_create_heading(style, level, text=''):
    data = '<text:h text:style-name="%s" text:outline-level="%d">%s</text:h>'
    return odf_create_element(data % (style, level, text))

def odf_create_frame(name, style, width, height, page=None, x=None, y=None):
    if page is None:
        anchor = 'text:anchor-type="paragraph"'
    else:
        anchor = 'text:anchor-type="page" text:anchor-page-number="%d"' % page
        if x is not None:
            anchor += ' svg:x="%s"' % x
        if y is not None:
            anchor += ' svg:y="%s"' % y
    data = ('<draw:frame draw:name="%s" draw:style-name="%s" '
            'svg:width="%s" svg:height="%s" %s/>')

    return odf_create_element(data % (name, style, width, height, anchor))

def odf_create_image(link):
    return odf_create_element('<draw:image xlink:href="%s"/>' % link)

def odf_create_cell():
    return odf_create_element(
                    '<table:table-cell office:value-type="String"/>')

def odf_create_row(width=None):
    row = odf_create_element('<table:table-row/>')
    if width is not None:
        for i in xrange(width):
            cell = odf_create_cell()
            row.insert_element(cell, LAST_CHILD)
    return row

def odf_create_column(style):
    data = '<table:table-column table:style-name="%s"/>'
    return odf_create_element(data % style)

def odf_create_table(name, style, width=None, height=None):
    data = '<table:table table:name="%s" table:style-name="%s"/>'
    table = odf_create_element(data % (name, style))
    if width is not None or height is not None:
        width = width if width is not None else 1
        height = height if height is not None else 1
        for i in xrange(height):
            row = odf_create_row(width)
            table.insert_element(row, LAST_CHILD)
    return table

def odf_create_item():
    return odf_create_element('<text:list-item/>')

def odf_create_list(style):
    return odf_create_element('<text:list text:style-name="%s"/>' % style)

#
# Some private functions
#

def _generate_xpath_query(element_name, attributes={}, position=None,
                          context=None):
    if context is not None:
        query = [context._get_xpath_path(), '//']
    else:
        query = ['//']
    query.append(element_name)
    # Sort attributes for reproducible test cases
    for qname in sorted(attributes):
        value = attributes[qname]
        if value is not None:
            query.append('[@{qname}="{value}"]'.format(qname=qname,
                                                      value=str(value)))
        else:
            query.append('[@{qname}]'.format(qname=qname))
    if position is not None:
        query.append('[{position}]'.format(position=str(position)))
    return ''.join(query)

def _get_cell_coordinates(name):
    lower = name.lower()

    # First "x"
    x = 0
    for p in xrange(len(lower)):
        c = lower[p]
        if not c.isalpha():
            break
        v = ord(c) - ord('a') + 1
        x = x * 26 + v
    if x == 0:
        raise ValueError, 'Your cell name "%s" is malformed' % name

    # And "y"
    try:
        y = int(lower[p:])
    except ValueError:
        raise ValueError, 'Your cell name "%s" is malformed' % name
    if y <= 0:
        raise ValueError, 'Your cell name "%s" is malformed' % name

    return x, y

def _check_arguments(context=None, position=None, style=None):
    if context is not None:
        if not isinstance(context, odf_element):
            raise TypeError, "an odf element is expected"
    if position is not None:
        if not isinstance(position, int):
            raise TypeError, "an integer position is expected"
        if position < 1:
            raise ValueError, "position count begin at 1"
    if style is not None:
        if not isinstance(style, str):
            raise TypeError, "a style name is expected"

def _check_level(level):
    if not isinstance(level, int):
        raise TypeError, "an integer level is expected"
    if level < 1:
        raise ValueError, "level count begin at 1"

def _check_position_name(position, name):
    if not ((position is None) ^ (name is None)):
        raise ValueError, 'You must choose position "xor" name'

#
# The odf_document object
#

class odf_document(object):

    def __init__(self, container):
        if not isinstance(container, odf_container):
            raise TypeError, "container is not an ODF container"
        self.container = container

        # Cache of XML parts
        self.__xmlparts = {}

    def __get_xmlpart(self, part_name):
        parts = self.__xmlparts
        part = parts.get(part_name)
        if part is None:
            container = self.container
            part = odf_xmlpart(part_name, container)
            parts[part_name] = part
        return part

    def __get_element_list(self, qname, style=None, attributes=None,
                           frame_style=None, context=None):
        _check_arguments(style=style, context=context)
        content = self.__get_xmlpart('content')
        if attributes is None:
            attributes = {}
        if style:
            attributes['text:style-name'] = style
        if frame_style:
            attributes['draw:style-name'] = frame_style
        query = _generate_xpath_query(qname, attributes=attributes,
                                      context=context)
        return content.get_element_list(query)

    def __get_element(self, qname, position=None, attributes=None,
                      context=None):
        _check_arguments(position=position, context=context)
        content = self.__get_xmlpart('content')
        if attributes is None:
            attributes = {}
        query = _generate_xpath_query(qname, attributes=attributes,
                                      position=position, context=context)
        result = content.get_element_list(query)
        if not result:
            return None
        return result[0]

    def __insert_element(self, element, context, xmlposition):
        if context is not None:
            context.insert_element(element, xmlposition)
        else:
            # We insert it in the last office:text
            content = self.__get_xmlpart('content')
            office_text = content.get_element_list('//office:text')[-1]
            office_text.insert_element(element, LAST_CHILD)

    def clone(self):
        clone = object.__new__(self.__class__)
        for name in self.__dict__:
            if name == 'container':
                setattr(clone, name, self.container.clone())
            elif name == '_odf_document__xmlparts':
                # TODO odf_xmlpart.clone
                setattr(clone, name, {})
            else:
                value = getattr(self, name)
                value = deepcopy(value)
                setattr(clone, name, value)
        return clone

    def save(self, uri=None, packaging=None, pretty=False):
        # Synchronize data with container
        for part_name, part in self.__xmlparts.items():
            if part is not None:
                self.container.set_part(part_name, part.serialize(pretty))

        # Save the container
        self.container.save(uri, packaging)

    #
    # Sections
    #

    def get_section_list(self, style=None, context=None):
        return self.__get_element_list('text:section', style=style,
                                       context=context)

    def get_section(self, position, context=None):
        return self.__get_element('text:section', position, context=context)

    def insert_section(self, element, context=None, xmlposition=LAST_CHILD):
        self.__insert_element(element, context, xmlposition)

    #
    # Paragraphs
    #

    def get_paragraph_list(self, style=None, context=None):
        return self.__get_element_list('text:p', style=style,
                                       context=context)

    def get_paragraph(self, position, context=None):
        return self.__get_element('text:p', position, context=context)

    def insert_paragraph(self, element, context=None,
                         xmlposition=LAST_CHILD):
        self.__insert_element(element, context, xmlposition)

    #
    # Headings
    #

    def get_heading_list(self, style=None, level=None, context=None):
        if level is not None:
            _check_level(level)
            attributes = {'text:outline-level': level}
        else:
            attributes = None
        return self.__get_element_list('text:h', style=style,
                                       attributes=attributes,
                                       context=context)

    def get_heading(self, position, level=None, context=None):
        if level is not None:
            _check_level(level)
            attributes = {'text:outline-level': level}
        else:
            attributes = None
        return self.__get_element('text:h', position, attributes=attributes,
                                  context=context)

    def insert_heading(self, element, context=None, xmlposition=LAST_CHILD):
        self.__insert_element(element, context, xmlposition)

    #
    # Frames
    #

    def get_frame_list(self, style=None, context=None):
        return self.__get_element_list('draw:frame', frame_style=style,
                                       context=context)

    def get_frame(self, position=None, name=None, context=None):
        _check_position_name(position, name)
        attributes = {'draw:name': name} if name is not None else {}
        return self.__get_element('draw:frame', position,
                                  attributes=attributes,
                                  context=context)

    def insert_frame(self, element, context=None, position=LAST_CHILD):
        self.__insert_element(element, context, position)

    #
    # Images
    #

    def insert_image(self, element, context=None, xmlposition=LAST_CHILD):
        # XXX If context is None
        #     => auto create a frame with the good dimensions
        if context is None:
            raise NotImplementedError

        self.__insert_element(element, context, xmlposition)

    def get_image(self, position=None, name=None, context=None):
        # Automatically get the frame
        frame = self.get_frame(position, name, context)
        return self.__get_element('draw:image', context=frame)

    #
    # Tables
    #

    def get_table_list(self, style=None, context=None):
        return self.__get_element_list('table:table', style=style,
                                       context=context)

    def get_table(self, position=None, name=None, context=None):
        _check_position_name(position, name)
        attributes = {'table:name': name} if name is not None else {}
        return self.__get_element('table:table', position,
                                  attributes=attributes, context=context)

    def insert_table(self, element, context=None, xmlposition=LAST_CHILD):
        self.__insert_element(element, context, xmlposition)

    #
    # Columns
    #

    def insert_column(self, element, context, xmlposition=LAST_CHILD):
        context.insert_element(element, xmlposition)

    #
    # Rows
    #

    def insert_row(self, element, context, xmlposition=LAST_CHILD):
        context.insert_element(element, xmlposition)

    def get_row_list(self, style=None, context=None):
        return self.__get_element_list('table:table-row', style=style,
                                       context=context)

    #
    # Cells
    #

    def insert_cell(self, element, context, xmlposition=LAST_CHILD):
        context.insert_element(element, xmlposition)

    def get_cell_list(self, style=None, context=None):
        return self.__get_element_list('table:table-cell', style=style,
                                       context=context)

    # Warning: This function gives just a "read only" odf_element
    def get_cell(self, name, context):
        # The coordinates of your cell
        x, y = _get_cell_coordinates(name)

        # First, we must find the good row
        cell_y = 0
        for row in self.get_row_list(context=context):
            repeat = row.get_attribute('table:number-rows-repeated')
            repeat = int(repeat) if repeat is not None else 1
            if cell_y + 1 <= y and y <= (cell_y + repeat):
                break
            cell_y += repeat
        else:
            raise IndexError, 'I cannot find cell "%s"' % name

        # Second, we must find the good cell
        cell_x = 0
        for cell in self.get_cell_list(context=row):
            repeat = cell.get_attribute('table:number-columns-repeated')
            repeat = int(repeat) if repeat is not None else 1
            if cell_x + 1 <= x and x <= (cell_x + repeat):
                break
            cell_x += repeat
        else:
            raise IndexError, 'i cannot find your cell "%s"' % name

        return cell

    #
    # Lists
    #

    def insert_list(self, element, context=None, xmlposition=LAST_CHILD):
        self.__insert_element(element, context, xmlposition)

    def insert_item(self, element, context, xmlposition=LAST_CHILD):
        context.insert_element(element, xmlposition)

    #
    # Styles
    #

    def get_style(name):
        """Only paragraph styles for now.
        """
        raise NotImplementedError

#
# odf_document factories
#

def odf_get_document(uri):
    """Return an "odf_document" instance of the ODF document stored at the
    given URI.
    """
    container = odf_get_container(uri)
    return odf_document(container)

def odf_new_document_from_template(template_uri):
    """Return an "odf_document" instance using the given template.
    """
    container = odf_new_container_from_template(template_uri)
    return odf_document(container)

def odf_new_document_from_class(odf_class):
    """Return an "odf_document" instance of the given class.
    """
    container = odf_new_container_from_class(odf_class)
    return odf_document(container)
<commit_msg>table/cell: Now we can choice the type of a cell<commit_after># -*- coding: UTF-8 -*-
# Copyright (C) 2009 Itaapy, ArsAperta, Pierlis, Talend

# Import from the Standard Library
from copy import deepcopy

# Import from lpod
from container import odf_get_container, odf_new_container_from_template
from container import odf_new_container_from_class, odf_container
from xmlpart import odf_element, odf_xmlpart, LAST_CHILD
from xmlpart import odf_create_element

#
# odf creation functions
#

def odf_create_section(style):
    data = '<text:section text:style-name="%s"></text:section>' % style
    return odf_create_element(data)

def odf_create_paragraph(style, text=''):
    data = '<text:p text:style-name="%s">%s</text:p>' % (style, text)
    return odf_create_element(data)

def odf_create_heading(style, level, text=''):
    data = '<text:h text:style-name="%s" text:outline-level="%d">%s</text:h>'
    return odf_create_element(data % (style, level, text))

def odf_create_frame(name, style, width, height, page=None, x=None, y=None):
    if page is None:
        anchor = 'text:anchor-type="paragraph"'
    else:
        anchor = 'text:anchor-type="page" text:anchor-page-number="%d"' % page
        if x is not None:
            anchor += ' svg:x="%s"' % x
        if y is not None:
            anchor += ' svg:y="%s"' % y
    data = ('<draw:frame draw:name="%s" draw:style-name="%s" '
            'svg:width="%s" svg:height="%s" %s/>')

    return odf_create_element(data % (name, style, width, height, anchor))

def odf_create_image(link):
    return odf_create_element('<draw:image xlink:href="%s"/>' % link)

def odf_create_cell(type='string'):
    return odf_create_element(
                    '<table:table-cell office:value-type="%s"/>' % type)

def odf_create_row(width=None):
    row = odf_create_element('<table:table-row/>')
    if width is not None:
        for i in xrange(width):
            cell = odf_create_cell()
            row.insert_element(cell, LAST_CHILD)
    return row

def odf_create_column(style):
    data = '<table:table-column table:style-name="%s"/>'
    return odf_create_element(data % style)

def odf_create_table(name, style, width=None, height=None):
    data = '<table:table table:name="%s" table:style-name="%s"/>'
    table = odf_create_element(data % (name, style))
    if width is not None or height is not None:
        width = width if width is not None else 1
        height = height if height is not None else 1
        for i in xrange(height):
            row = odf_create_row(width)
            table.insert_element(row, LAST_CHILD)
    return table

def odf_create_item():
    return odf_create_element('<text:list-item/>')

def odf_create_list(style):
    return odf_create_element('<text:list text:style-name="%s"/>' % style)

#
# Some private functions
#

def _generate_xpath_query(element_name, attributes={}, position=None,
                          context=None):
    if context is not None:
        query = [context._get_xpath_path(), '//']
    else:
        query = ['//']
    query.append(element_name)
    # Sort attributes for reproducible test cases
    for qname in sorted(attributes):
        value = attributes[qname]
        if value is not None:
            query.append('[@{qname}="{value}"]'.format(qname=qname,
                                                      value=str(value)))
        else:
            query.append('[@{qname}]'.format(qname=qname))
    if position is not None:
        query.append('[{position}]'.format(position=str(position)))
    return ''.join(query)

def _get_cell_coordinates(name):
    lower = name.lower()

    # First "x"
    x = 0
    for p in xrange(len(lower)):
        c = lower[p]
        if not c.isalpha():
            break
        v = ord(c) - ord('a') + 1
        x = x * 26 + v
    if x == 0:
        raise ValueError, 'Your cell name "%s" is malformed' % name

    # And "y"
    try:
        y = int(lower[p:])
    except ValueError:
        raise ValueError, 'Your cell name "%s" is malformed' % name
    if y <= 0:
        raise ValueError, 'Your cell name "%s" is malformed' % name

    return x, y

def _check_arguments(context=None, position=None, style=None):
    if context is not None:
        if not isinstance(context, odf_element):
            raise TypeError, "an odf element is expected"
    if position is not None:
        if not isinstance(position, int):
            raise TypeError, "an integer position is expected"
        if position < 1:
            raise ValueError, "position count begin at 1"
    if style is not None:
        if not isinstance(style, str):
            raise TypeError, "a style name is expected"

def _check_level(level):
    if not isinstance(level, int):
        raise TypeError, "an integer level is expected"
    if level < 1:
        raise ValueError, "level count begin at 1"

def _check_position_name(position, name):
    if not ((position is None) ^ (name is None)):
        raise ValueError, 'You must choose position "xor" name'

#
# The odf_document object
#

class odf_document(object):

    def __init__(self, container):
        if not isinstance(container, odf_container):
            raise TypeError, "container is not an ODF container"
        self.container = container

        # Cache of XML parts
        self.__xmlparts = {}

    def __get_xmlpart(self, part_name):
        parts = self.__xmlparts
        part = parts.get(part_name)
        if part is None:
            container = self.container
            part = odf_xmlpart(part_name, container)
            parts[part_name] = part
        return part

    def __get_element_list(self, qname, style=None, attributes=None,
                           frame_style=None, context=None):
        _check_arguments(style=style, context=context)
        content = self.__get_xmlpart('content')
        if attributes is None:
            attributes = {}
        if style:
            attributes['text:style-name'] = style
        if frame_style:
            attributes['draw:style-name'] = frame_style
        query = _generate_xpath_query(qname, attributes=attributes,
                                      context=context)
        return content.get_element_list(query)

    def __get_element(self, qname, position=None, attributes=None,
                      context=None):
        _check_arguments(position=position, context=context)
        content = self.__get_xmlpart('content')
        if attributes is None:
            attributes = {}
        query = _generate_xpath_query(qname, attributes=attributes,
                                      position=position, context=context)
        result = content.get_element_list(query)
        if not result:
            return None
        return result[0]

    def __insert_element(self, element, context, xmlposition):
        if context is not None:
            context.insert_element(element, xmlposition)
        else:
            # We insert it in the last office:text
            content = self.__get_xmlpart('content')
            office_text = content.get_element_list('//office:text')[-1]
            office_text.insert_element(element, LAST_CHILD)

    def clone(self):
        clone = object.__new__(self.__class__)
        for name in self.__dict__:
            if name == 'container':
                setattr(clone, name, self.container.clone())
            elif name == '_odf_document__xmlparts':
                # TODO odf_xmlpart.clone
                setattr(clone, name, {})
            else:
                value = getattr(self, name)
                value = deepcopy(value)
                setattr(clone, name, value)
        return clone

    def save(self, uri=None, packaging=None, pretty=False):
        # Synchronize data with container
        for part_name, part in self.__xmlparts.items():
            if part is not None:
                self.container.set_part(part_name, part.serialize(pretty))

        # Save the container
        self.container.save(uri, packaging)

    #
    # Sections
    #

    def get_section_list(self, style=None, context=None):
        return self.__get_element_list('text:section', style=style,
                                       context=context)

    def get_section(self, position, context=None):
        return self.__get_element('text:section', position, context=context)

    def insert_section(self, element, context=None, xmlposition=LAST_CHILD):
        self.__insert_element(element, context, xmlposition)

    #
    # Paragraphs
    #

    def get_paragraph_list(self, style=None, context=None):
        return self.__get_element_list('text:p', style=style,
                                       context=context)

    def get_paragraph(self, position, context=None):
        return self.__get_element('text:p', position, context=context)

    def insert_paragraph(self, element, context=None,
                         xmlposition=LAST_CHILD):
        self.__insert_element(element, context, xmlposition)

    #
    # Headings
    #

    def get_heading_list(self, style=None, level=None, context=None):
        if level is not None:
            _check_level(level)
            attributes = {'text:outline-level': level}
        else:
            attributes = None
        return self.__get_element_list('text:h', style=style,
                                       attributes=attributes,
                                       context=context)

    def get_heading(self, position, level=None, context=None):
        if level is not None:
            _check_level(level)
            attributes = {'text:outline-level': level}
        else:
            attributes = None
        return self.__get_element('text:h', position, attributes=attributes,
                                  context=context)

    def insert_heading(self, element, context=None, xmlposition=LAST_CHILD):
        self.__insert_element(element, context, xmlposition)

    #
    # Frames
    #

    def get_frame_list(self, style=None, context=None):
        return self.__get_element_list('draw:frame', frame_style=style,
                                       context=context)

    def get_frame(self, position=None, name=None, context=None):
        _check_position_name(position, name)
        attributes = {'draw:name': name} if name is not None else {}
        return self.__get_element('draw:frame', position,
                                  attributes=attributes,
                                  context=context)

    def insert_frame(self, element, context=None, position=LAST_CHILD):
        self.__insert_element(element, context, position)

    #
    # Images
    #

    def insert_image(self, element, context=None, xmlposition=LAST_CHILD):
        # XXX If context is None
        #     => auto create a frame with the good dimensions
        if context is None:
            raise NotImplementedError

        self.__insert_element(element, context, xmlposition)

    def get_image(self, position=None, name=None, context=None):
        # Automatically get the frame
        frame = self.get_frame(position, name, context)
        return self.__get_element('draw:image', context=frame)

    #
    # Tables
    #

    def get_table_list(self, style=None, context=None):
        return self.__get_element_list('table:table', style=style,
                                       context=context)

    def get_table(self, position=None, name=None, context=None):
        _check_position_name(position, name)
        attributes = {'table:name': name} if name is not None else {}
        return self.__get_element('table:table', position,
                                  attributes=attributes, context=context)

    def insert_table(self, element, context=None, xmlposition=LAST_CHILD):
        self.__insert_element(element, context, xmlposition)

    #
    # Columns
    #

    def insert_column(self, element, context, xmlposition=LAST_CHILD):
        context.insert_element(element, xmlposition)

    #
    # Rows
    #

    def insert_row(self, element, context, xmlposition=LAST_CHILD):
        context.insert_element(element, xmlposition)

    def get_row_list(self, style=None, context=None):
        return self.__get_element_list('table:table-row', style=style,
                                       context=context)

    #
    # Cells
    #

    def insert_cell(self, element, context, xmlposition=LAST_CHILD):
        context.insert_element(element, xmlposition)

    def get_cell_list(self, style=None, context=None):
        return self.__get_element_list('table:table-cell', style=style,
                                       context=context)

    # Warning: This function gives just a "read only" odf_element
    def get_cell(self, name, context):
        # The coordinates of your cell
        x, y = _get_cell_coordinates(name)

        # First, we must find the good row
        cell_y = 0
        for row in self.get_row_list(context=context):
            repeat = row.get_attribute('table:number-rows-repeated')
            repeat = int(repeat) if repeat is not None else 1
            if cell_y + 1 <= y and y <= (cell_y + repeat):
                break
            cell_y += repeat
        else:
            raise IndexError, 'I cannot find cell "%s"' % name

        # Second, we must find the good cell
        cell_x = 0
        for cell in self.get_cell_list(context=row):
            repeat = cell.get_attribute('table:number-columns-repeated')
            repeat = int(repeat) if repeat is not None else 1
            if cell_x + 1 <= x and x <= (cell_x + repeat):
                break
            cell_x += repeat
        else:
            raise IndexError, 'i cannot find your cell "%s"' % name

        return cell

    #
    # Lists
    #

    def insert_list(self, element, context=None, xmlposition=LAST_CHILD):
        self.__insert_element(element, context, xmlposition)

    def insert_item(self, element, context, xmlposition=LAST_CHILD):
        context.insert_element(element, xmlposition)

    #
    # Styles
    #

    def get_style(name):
        """Only paragraph styles for now.
        """
        raise NotImplementedError

#
# odf_document factories
#

def odf_get_document(uri):
    """Return an "odf_document" instance of the ODF document stored at the
    given URI.
    """
    container = odf_get_container(uri)
    return odf_document(container)

def odf_new_document_from_template(template_uri):
    """Return an "odf_document" instance using the given template.
    """
    container = odf_new_container_from_template(template_uri)
    return odf_document(container)

def odf_new_document_from_class(odf_class):
    """Return an "odf_document" instance of the given class.
    """
    container = odf_new_container_from_class(odf_class)
    return odf_document(container)
<|endoftext|>


<commit_before>#!/usr/bin/env python

import glob

enc = "utf-8"

# Generate include files

f = open("theme_data.h", "wb")

f.write(b"// THIS FILE HAS BEEN AUTOGENERATED, DON\'T EDIT!!\n")

# Generate png image block
f.write(b"\n// png image block\n")

pixmaps = glob.glob("*.png")
pixmaps.sort()

for x in pixmaps:

    var_str = x[:-4] + "_png"

    s = "\nstatic const unsigned char " + var_str + "[] = {\n\t"
    f.write(s.encode(enc))

    pngf = open(x, "rb")

    b = pngf.read(1)
    while(len(b) == 1):
        f.write(hex(ord(b)).encode(enc))
        b = pngf.read(1)
        if (len(b) == 1):
            f.write(b", ")

    f.write(b"\n};\n")
    pngf.close()

# Generate shaders block
f.write(b"\n// shaders block\n");

shaders = glob.glob("*.gsl")
shaders.sort()

for x in shaders:

    var_str = x[:-4] + "_shader_code"

    s = "\nstatic const char *" + var_str + " = \n"
    f.write(s.encode(enc))

    sf = open(x, "rb")

    b = sf.readline()
    while(b != ""):
        if (b.endswith("\r\n")):
            b = b[:-2]
        if (b.endswith("\n")):
            b = b[:-1]
        s = '			\"' + b
        f.write(s.encode(enc))
        b = sf.readline()
        if (b != ""):
            f.write(b'"\n')

    f.write(b'";\n')
    sf.close()

f.close()
<commit_msg>Make `make_header.py` functional when run from any location<commit_after>#!/usr/bin/env python

import glob
import os

enc = "utf-8"

# Change to the directory where the script is located,
# so that the script can be run from any location
os.chdir(os.path.dirname(os.path.realpath(__file__)))

# Generate include files

f = open("theme_data.h", "wb")

f.write(b"// THIS FILE HAS BEEN AUTOGENERATED, DON\'T EDIT!!\n")

# Generate png image block
f.write(b"\n// png image block\n")

pixmaps = glob.glob("*.png")
pixmaps.sort()

for x in pixmaps:

    var_str = x[:-4] + "_png"

    s = "\nstatic const unsigned char " + var_str + "[] = {\n\t"
    f.write(s.encode(enc))

    pngf = open(x, "rb")

    b = pngf.read(1)
    while(len(b) == 1):
        f.write(hex(ord(b)).encode(enc))
        b = pngf.read(1)
        if (len(b) == 1):
            f.write(b", ")

    f.write(b"\n};\n")
    pngf.close()

# Generate shaders block
f.write(b"\n// shaders block\n");

shaders = glob.glob("*.gsl")
shaders.sort()

for x in shaders:

    var_str = x[:-4] + "_shader_code"

    s = "\nstatic const char *" + var_str + " = \n"
    f.write(s.encode(enc))

    sf = open(x, "rb")

    b = sf.readline()
    while(b != ""):
        if (b.endswith("\r\n")):
            b = b[:-2]
        if (b.endswith("\n")):
            b = b[:-1]
        s = '			\"' + b
        f.write(s.encode(enc))
        b = sf.readline()
        if (b != ""):
            f.write(b'"\n')

    f.write(b'";\n')
    sf.close()

f.close()
<|endoftext|>