def parse_command(command):
    """
    Parse the given one-line QF command analogously to parse_file().
    """
    m = re.match(r'^\#?([bdpq]|build|dig|place|query)\s+(.+)', command)

    if m is None:
        raise ParametersError("Invalid command format '%s'." % command)

    # set up details dict
    details = {
        'build_type': buildconfig.get_full_build_type_name(m.group(1)),
        'start': (0, 0),
        'start_comment': '',
        'comment': ''
    }

    # break apart lines by # and cells by ,
    lines = [[cell.strip() for cell in line.split(',')]
             for line in m.group(2).split('#')]

    # break up lines into z-layers separated by #> or #<
    # TODO: actually support this properly, right now we are just
    # calling this to do conversion to FileLayers for us
    filelayers = split_zlayers(lines)

    # tidy up the layers
    for fl in filelayers:
        fl.fixup()
        fl.clean_cells()

    return filelayers, details
示例#2
0
def parse_startpos(start, width, height):
    """Transform startpos string like (1,1) or nw to corresponding Point."""

    # try (#,#) type syntax
    m = re.match(r'\(?(\d+)[,;](\d+)\)?', start)
    if m is not None:
        return (int(m.group(1)), int(m.group(2)))

    # try corner-coordinate syntax
    m = re.match(r'(ne|nw|se|sw)', start.lower())
    if m is not None:
        # convert corner String -> Direction -> (x, y) then magnify (x, y)
        # by width and height to get the corner coordinate we want
        (x, y) = Direction(m.group(1)).delta()
        point = (max(0, x) * (width - 1), max(0, y) * (height - 1))

        return point

    raise ParametersError("Invalid --position parameter '%s'" % start)
    def expand_height(self, rows, targetheight, alignment):
        """Expand rows to requested targetheight, aligning according to alignment param."""
        height = len(rows)
        if alignment == 't':
            # align top: add rows to the bottom
            top, bottom = 0, (targetheight - height)
        elif alignment == 'b':
            # align bottom: add rows to the top
            top, bottom = (targetheight - height), 0
        elif alignment == 'm':
            # align middle: add rows to both top and bottom evenly
            top = (targetheight - height) / 2
            bottom = targetheight - height - top
        else:
            raise ParametersError("Unrecognized vertical alignment code %s" %
                                  alignment)

        width = len(rows[0])
        rows = [[''] * width] * top + rows + [[''] * width] * bottom

        return rows
    def expand_width(self, rows, targetwidth, alignment):
        """Expand rows to requested targetwidth, aligning according to alignment param."""
        width = len(rows[0])
        if alignment == 'l':
            # align left: add columns to the right
            left, right = 0, (targetwidth - width)
        elif alignment == 'r':
            # align right: add columns to the left
            left, right = (targetwidth - width), 0
        elif alignment == 'm':
            # align middle: add columns to both left and right evenly
            left = (targetwidth - width) / 2
            right = targetwidth - width - left
        else:
            raise ParametersError("Unrecognized horizontal alignment code %s" %
                                  alignment)

        for i, r in enumerate(rows):
            r = [''] * left + r + [''] * right
            rows[i] = r
        return rows
示例#5
0
    def repeat_ztransforms(self, ztransforms, data, repeatfun):
        """
        Performs ztransform repetitions given some ztransforms [('2', 'd'),..],
        initial data, and a repeatfun (Function) to appply for each ztransform.
        The output of each ztransform is fed as input to the next.
        """
        if len(ztransforms) == 0:
            return data

        zdelta = GridLayer.zoffset(self.layers)
        for t in ztransforms:
            count, command = t
            if count < 2:
                continue

            if command not in ('d', 'u'):
                raise ParametersError('Unrecognized ztransform ' + command)

            # direction we want to move: 1=zdown, -1=zup
            dirsign = 1 if command == 'd' else -1

            # if we want to move in the same direction as the stack does,
            # we only need to move 1 z-level in that direction
            if dirsign * zdelta > 0:  # if signs of dirsign and zdelta match...
                zdelta = 0  # 'no z-change caused by stack'

            # with a ztransform moving in the opposite direction we
            # need to move twice the height of the blueprint-stack
            # so that the subsequent repetition of the original blueprint's
            # keys can playback without overlapping onto zlevels
            # that we'll have already designated
            zdistance = dirsign * (-1 + 2 * (1 + (dirsign * -zdelta)))

            # apply fn given the z-distance required to travel
            data = repeatfun(data, zdistance, count)
            zdelta = ((zdelta + 1) * count) - 1

        return data
    def apply_transform(self, trans, a, b):
        """
        Apply the requested transformation to 2D lists [[]] a and possibly b,
        and return the result.
        """
        #b = deepcopy(b)  # ensure a and b are different objects
        count, action = trans

        if action == 'sub':
            # do regex search-and-replace against every cell in B
            (pattern, replacement) = count

            # handle 'empty' patterns for the user by matching empty cells
            if pattern == '':
                pattern = '^$'
            elif pattern == '~':
                pattern = '~^$'  # matches any NON empty cell

            if len(pattern) > 0 and pattern[0] == '~':
                # "negate" the pattern - s/~d/x/ turns all non-d cells to x
                pattern = pattern[1:]
                b = [[
                    replacement if re.search(pattern, cell) is None else cell
                    for cell in row
                ] for row in b]
                return a, b

            # do normal by-cell search and replace
            b = [[re.sub(pattern, replacement, cell) for cell in row]
                 for row in b]
            return a, b

        if action == 'flipv':
            # flip b vertically
            b.reverse()
            return a, b

        if action == 'fliph':
            # flip b horizontally
            for row in b:
                row.reverse()
            return a, b

        if action in ('rotcw', 'rotccw'):
            # rotate a clockwise or counterclockwise 90 degrees
            rot = [list(x) for x in zip(*b)]  # pivot the grid (cols <-> rows)

            if action == 'rotcw':
                return self.apply_transform((1, 'fliph'), a, rot)  # clockwise

            return self.apply_transform((1, 'flipv'), a,
                                        rot)  # counterclockwise

        if action in ('n', 's', 'e', 'w'):
            # heights and widths
            ha, hb, wa, wb = len(a), len(b), len(a[0]), len(b[0])

            # handle alignment issues when a and b have differing dimensions
            if action in ('e', 'w'):
                if ha < hb:
                    a = self.expand_height(a, hb, self.valign)
                elif hb < ha:
                    b = self.expand_height(b, ha, self.valign)
            elif action in ('n', 's'):
                if wa < wb:
                    a = self.expand_width(a, wb, self.halign)
                elif wb < wa:
                    b = self.expand_width(b, wa, self.halign)

            # repeat (a+b) in given direction the requested number of times
            #   4e yields ABAB pattern; 3e yields ABA
            series = ([a, b] * (count // 2)) + \
                ([a] if count % 2 == 1 else [])

            # reverse series for negative directions
            if action in ('n', 'w'):
                series.reverse()

            # repeat series in direction
            if action in ('n', 's'):
                out = []
                for s in series:
                    out.extend(s)
            else:  # 'e', 'w'
                # combine each row of each series item into a new row
                rows = zip(*series)
                out = []
                for r in rows:
                    newrow = []
                    for s in r:
                        newrow.extend(s)
                    out.append(newrow)
            return out, out  # must be treated as two different objects

        raise ParametersError('Unknown transformation type: %d %s' % trans)
def parse_transform_str(transform_str):
    """
    Converts space separated transform string e.g. '2e valign=top rotcw'
    into list of tuples, e.g. [(2, 'e'), ('t', 'valign'), (1, 'rotcw')].

    Transforms of format s/pat/repl/ are stored as (('pat', 'repl'), 'sub')

    Commit sequences of the form 'rotcw ! 2e' can be entered as 'rotcw; 2e'
    and will be translated to the former form here.

    Returns the conversion result as follows:
        ('newphase', (x/y transforms), (z-level transforms))
    """
    if transform_str == '':
        return (None, None, None)

    transform_str = re.sub('\s*;\s*', ' ! ', transform_str)  # 2e;2e -> 2e ! 2e
    transform_str = re.sub('\s+', ' ', transform_str)  # cleanup spaces
    transforms = transform_str.strip().split(' ')
    readies = []
    newphase = None
    for t in transforms:
        lt = t.lower()
        try:
            # matches halign=(left|middle|right) or 1 letter equiv. l|m|r
            m = re.match(r'^(halign)=(l|m|r)\w*$', lt)
            if m is not None:
                readies.append(m.group(2, 1))
                continue

            # matches valign=(top|middle|bottom) or 1 letter equiv. t|m|b
            m = re.match(r'^(valign)=(t|m|b)\w*$', lt)
            if m is not None:
                readies.append(m.group(2, 1))
                continue

            # matches phase=(build|dig|place|query) or 1 letter equiv. b|d|p|q
            m = re.match(r'^(phase)=(b|d|p|q)\w*$', lt)
            if m is not None:
                newphase = m.group(2)
                continue

            # matches s/pattern/replacement/, allows escaping / as \/
            m = re.match(r'^(s)/(\S*?)(?<!\\)/(([^\s/]|\\/)*)(?<!\\)/?$', t)
            if m is not None:
                # internally the command is called 'sub' to avoid
                # ambiguity vs. 's' (repeat south) command
                readies.append((m.group(2, 3), 'sub'))
                continue

            # matches #D repeaters, rotcw/rotccw, fliph/flipv, and ! sequence separator
            m = re.match(r'^(\d+)?(n|s|e|w|u|d|rotcw|rotccw|fliph|flipv|!)$',
                         lt)
            if m is not None:
                count = int(m.group(1)) if m.group(1) else 1
                readies.append((count, m.group(2)))
                continue
        except:
            # error while match one of the permitted patterns
            raise ParametersError(
                "Syntax error in transform sequence.\n" + \
                "Transform string: %s\nError occurred on: %s" % (transform_str, t)
                )

        # failed to match any of the permitted patterns
        raise ParametersError(
            "Did not recognize transform command.\n" + \
            "Transform string: %s\nDid not recognize: %s" % (transform_str, t)
            )

    # separate zup/zdown transforms from x/y transforms
    xys = [t for t in readies if t[1][0] not in ('u', 'd')]
    zs = [t for t in readies if t[1][0] in ('u', 'd')]

    return newphase, xys, zs
def parse_options(argv):
    """Read options and args from argv (the command line parameters)."""
    usage = "usage: %prog [options] [input_file] [output_file]"
    parser = OptionParser(usage=usage, version="%prog " + VERSION)
    parser.add_option("-s",
                      "--sheetid",
                      dest="sheetid",
                      default=None,
                      help="worksheet index for xls/xlsx files [default: 0]")
    parser.add_option("-p",
                      "--position",
                      dest="startpos",
                      default=None,
                      help="starting position [one of: (x,y) ne nw se sw]")
    parser.add_option("-t",
                      "--transform",
                      dest="transform",
                      default='',
                      help="transformation rules, e.g. -t \"2e flipv 2s\"")
    parser.add_option("-m",
                      "--mode",
                      dest="mode",
                      default='macro',
                      help="output mode: [one of: macro key keylist csv]")
    parser.add_option("-c",
                      "--command",
                      dest='command',
                      help="Eval QF one-line command instead of input_file")
    parser.add_option("-T",
                      "--title",
                      dest='title',
                      help="title of output macro")
    parser.add_option("-i",
                      "--info",
                      action="store_true",
                      dest="info",
                      default=False,
                      help="output information about input_file")
    parser.add_option("-v",
                      "--visualize",
                      action="store_true",
                      dest="visualize",
                      default=False,
                      help="just moves cursor around blueprint's perimeter")
    parser.add_option("-C",
                      "--show-csv-parse",
                      action="append_const",
                      dest="loglevels",
                      const="file",
                      help="show blueprint parsing steps on stdout")
    parser.add_option("-X",
                      "--show-transforms",
                      action="append_const",
                      dest="loglevels",
                      const="transform",
                      help="show transform steps on stdout")
    parser.add_option("-A",
                      "--show-area",
                      action="append_const",
                      dest="loglevels",
                      const="area",
                      help="show area-discovery steps on stdout")
    parser.add_option("-R",
                      "--show-route",
                      action="append_const",
                      dest="loglevels",
                      const="router",
                      help="show route-planning steps on stdout")
    parser.add_option("-S",
                      "--show-summary",
                      action="append_const",
                      dest="loglevels",
                      const="summary",
                      help="show summary output")
    parser.add_option("-P",
                      "--profile",
                      action="store_true",
                      dest="profile",
                      default=False,
                      help="profile qfconvert performance")

    options, args = parser.parse_args(args=argv)

    if options.mode not in ('key', 'macro', 'keylist', 'csv'):
        raise ParametersError(
            "Invalid mode '%s', must be one of: macro key keylist csv" % \
                options.mode)

    if options.command is not None:
        options.infile = None
        options.outfile = args[0] if len(args) > 0 else None
        return options

    if len(args) < 1:
        parser.print_help()
        return None

    options.infile = args[0]
    options.outfile = args[1] if len(args) > 1 else None

    if options.sheetid is not None:
        try:
            options.sheetid = int(options.sheetid)
        except:
            raise ParametersError("sheetid must be numeric, not '%s'" % \
                options.sheetid)

    return options