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
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
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