diff --git a/src/diana/config.py b/src/diana/config.py --- a/src/diana/config.py +++ b/src/diana/config.py @@ -2,49 +2,49 @@ import os from argparse import ArgumentParser -progName="DianaXO" -version=(0,1,0) +progName = "DianaXO" +version = (0, 1, 0) -curDir=os.path.dirname(__file__) +curDir = os.path.dirname(__file__) -parser=ArgumentParser() +parser = ArgumentParser() parser.add_argument("--encoding") -parser.add_argument("-s","--suffix") -parser.add_argument("-r","--recursive") -parser.add_argument("-i","--input", nargs="+", required=True) -parser.add_argument("-o","--output") +parser.add_argument("-s", "--suffix") +parser.add_argument("-r", "--recursive") +parser.add_argument("-i", "--input", nargs="+", required=True) +parser.add_argument("-o", "--output") parser.add_argument("--format", choices={"svg"}) -parser.add_argument("-k","--keep-broken") +parser.add_argument("-k", "--keep-broken") parser.add_argument("--moves-p-d", type=int) parser.add_argument("--min-p-d", type=int) -parser.add_argument("--version", action="version", version="{0} {1}.{2}.{3}".format(progName,*version)) +parser.add_argument("--version", action="version", version="{0} {1}.{2}.{3}".format(progName, *version)) -inputFiles=[] -encoding="utf-8-sig" -sgfSuffix=True -recursive=False -outputDir=curDir -outputFormat="svg" -keepBroken=False +inputFiles = [] +encoding = "utf-8-sig" +sgfSuffix = True +recursive = False +outputDir = curDir +outputFormat = "svg" +keepBroken = False -movesPerDiagram=100 -minMovesPerDiagram=10 +movesPerDiagram = 100 +minMovesPerDiagram = 10 def parseArgs(): - global inputFiles,encoding,sgfSuffix,recursive,outputDir,outputFormat,keepBroken,movesPerDiagram + global inputFiles, encoding, sgfSuffix, recursive, outputDir, outputFormat, keepBroken, movesPerDiagram global minMovesPerDiagram args=parser.parse_args() inputFiles=args.input - if args.encoding: encoding=args.encoding - if args.suffix: sgfSuffix=True - if args.recursive: recursive=True - if args.output: outputDir=args.output - if args.format: outputFormat=args.format - if args.keep_broken is not None: keepBroken=True + if args.encoding: encoding = args.encoding + if args.suffix: sgfSuffix = True + if args.recursive: recursive = True + if args.output: outputDir = args.output + if args.format: outputFormat = args.format + if args.keep_broken is not None: keepBroken = True - if args.moves_p_d: movesPerDiagram=args.moves_p_d - if args.min_p_d: minMovesPerDiagram=args.min_p_d + if args.moves_p_d: movesPerDiagram = args.moves_p_d + if args.min_p_d: minMovesPerDiagram = args.min_p_d diff --git a/src/diana/diana.py b/src/diana/diana.py --- a/src/diana/diana.py +++ b/src/diana/diana.py @@ -11,106 +11,113 @@ from .drawer.tikz import Tikz def collectMoves(root): - node=root - while len(node.children)>0: - b=node.getProp("B") - w=node.getProp("W") - if b is not None: yield ("b",b) - elif w is not None: yield ("w",w) + node = root + while len(node.children) > 0: + b = node.getProp("B") + w = node.getProp("W") + if b is not None: + yield ("b", b) + elif w is not None: + yield ("w", w) # else: yield None # !! not really robust - node=node.children[0] + node = node.children[0] class SourceFile: - def __init__(self,fileName): - self.fileName=fileName - self._shortName= "".join(re.split(r'[/\\]', fileName)[-1].split('.')[:-1]) - self._game=go.Go() + def __init__(self, fileName): + self.fileName = fileName + self._shortName = "".join(re.split(r'[/\\]', fileName)[-1].split('.')[:-1]) + self._game = go.Go() with open(self.fileName, 'r', encoding=cfg.encoding) as f: - games=Collection(f.read()).listGames() - self._record=list(games)[0] - self._moves=list(collectMoves(self._record.root)) + games = Collection(f.read()).listGames() + self._record = list(games)[0] + self._moves = list(collectMoves(self._record.root)) def process(self): print("{0}... ".format(self.fileName), end="") - i=1 - for k in range(0,len(self._moves),cfg.movesPerDiagram): - filename=os.path.join(cfg.outputDir,"{0}-{1}".format(self._shortName,i)) - self.createDiagram(k,k+cfg.movesPerDiagram).save(filename,"templ-pleb.svg") - i+=1 + i = 1 + for k in range(0,len(self._moves), cfg.movesPerDiagram): + filename = os.path.join(cfg.outputDir, "{0}-{1}".format(self._shortName, i)) + self.createDiagram(k, k+cfg.movesPerDiagram).save(filename, "templ-pleb.svg") + i += 1 - infoStr="""{GN} + infoStr = """{GN} B: {PB} {BR} W: {PW} {WR} {DT} -{RE}""".format(**self.fetchGameInfo(["GN","PB","BR","PW","WR","DT","RE"],"")) - notes=open(os.path.join(cfg.outputDir,"{0}.txt".format(self._shortName)), 'w') +{RE}""".format(**self.fetchGameInfo(["GN","PB","BR","PW","WR","DT","RE"], "")) + notes = open(os.path.join(cfg.outputDir, "{0}.txt".format(self._shortName)), 'w') notes.write(infoStr) notes.close() print("done") - def createDiagram(self,start,end): + def createDiagram(self, start, end): # initialize the diagram - template=Svg() + template = Svg() self._setMove(start) # draw current state - for lineNumber,line in enumerate(self._game.board): - for itemNumber,item in enumerate(line): - if item!=EMPTY: template.addStone(itemNumber,lineNumber,"b" if item==BLACK else "w") + for lineNumber, line in enumerate(self._game.board): + for itemNumber, item in enumerate(line): + if item != EMPTY: + template.addStone(itemNumber, lineNumber, "b" if item==BLACK else "w") # draw the moves - for k in range(start,end): - if k>=len(self._moves): break + for k in range(start, end): + if k >= len(self._moves): + break - color,move=self._moves[k] - if move==tuple(): - template.overlays.append((k,"pass")) # !! + color, move = self._moves[k] + if move == tuple(): + template.overlays.append((k, "pass")) # !! continue else: - (c,r)=move + (c, r) = move - if not self._move(color,c,r): - if cfg.keepBroken: continue - else: return False + if not self._move(color, c, r): + if cfg.keepBroken: + continue + else: + return False # draw the move - template.addMove(c,r,color,k+1) + template.addMove(c, r, color, k+1) return template - def fetchGameInfo(self,fieldNames,default=None): - return {k:self._record.get(k,default) for k in fieldNames} + def fetchGameInfo(self, fieldNames, default=None): + return {k: self._record.get(k, default) for k in fieldNames} - def _setMove(self,k): - self._game=go.Go() + def _setMove(self, k): + self._game = go.Go() - blackStones=self._record.root.getProp("AB") - whiteStones=self._record.root.getProp("AW") + blackStones = self._record.root.getProp("AB") + whiteStones = self._record.root.getProp("AW") if blackStones: for p in blackStones: - self._game.board[p.r][p.c]=BLACK + self._game.board[p.r][p.c] = BLACK if whiteStones: for p in whiteStones: - self._game.board[p.r][p.c]=WHITE + self._game.board[p.r][p.c] = WHITE for i in range(k): - (color,move)=self._moves[i] - if move==tuple(): continue # pass - self._move(color,*move) + (color, move) = self._moves[i] + if move == tuple(): + continue # pass + self._move(color, *move) - def _move(self,color,c,r): - if not self._game.move(BLACK if color=='b' else WHITE, c,r): + def _move(self, color, c, r): + if not self._game.move(BLACK if color=='b' else WHITE, c, r): # !! we do not honor http://red-bean.com/sgf/ff5/m_vs_ax.htm at the moment - msg="illegal move: {0} at {1},{2}".format(self._game.moveCount+1,c,r) + msg = "illegal move: {0} at {1},{2}".format(self._game.moveCount+1, c, r) if cfg.keepBroken: print(msg) else: - msg+=". aborted" + msg += ". aborted" print(msg) return False return True @@ -119,16 +126,17 @@ W: {PW} {WR} def main(): cfg.parseArgs() print("processing:") - files=cfg.inputFiles[:] + files = cfg.inputFiles[:] for item in files: if os.path.isfile(item): try: - f=SourceFile(item) + f = SourceFile(item) f.process() except ParserError as e: - print("Couldn't parse {0}, following error occured: {1}".format(item,e)) + print("Couldn't parse {0}, following error occured: {1}".format(item, e)) elif os.path.isdir(item): - files+=[os.path.join(item,child) for child in os.listdir(item)] + files += [os.path.join(item,child) for child in os.listdir(item)] print("contents of the '{0}' directory added to the queue".format(item)) - else: print("the '{0}' path could not be resolved to either a file nor a directory".format(item)) + else: + print("the '{0}' path could not be resolved to either a file nor a directory".format(item)) diff --git a/src/diana/drawer/base.py b/src/diana/drawer/base.py --- a/src/diana/drawer/base.py +++ b/src/diana/drawer/base.py @@ -1,57 +1,57 @@ import os from itertools import count -from jinja2 import Environment,FileSystemLoader +from jinja2 import Environment, FileSystemLoader class DiagramPoint: - def __init__(self,x,y,color="",label=""): - self.x=x - self.y=y - self.color=color - self.label=label + def __init__(self, x, y, color="", label=""): + self.x = x + self.y = y + self.color = color + self.label = label def __repr__(self): - return 'DiagramPoint({0},{1},"{2}","{3}")'.format(self.x,self.y,self.color,self.label) + return 'DiagramPoint({0},{1},"{2}","{3}")'.format(self.x, self.y, self.color, self.label) class Base: - highNumbers=True + highNumbers = True - def __init__(self,start=0): - self.overlays=[] - self._letter="a" + def __init__(self, start=0): + self.overlays = [] + self._letter = "a" - self._index=dict() - self._indexGen=count(start) + self._index = dict() + self._indexGen = count(start) - curDir=os.path.dirname(__file__) - templateDir=os.path.join(curDir,"..","templ") - self._env=Environment(loader=FileSystemLoader(templateDir)) - self._env.trim_blocks=True - self._env.lstrip_blocks=True + curDir = os.path.dirname(__file__) + templateDir = os.path.join(curDir, "..", "templ") + self._env = Environment(loader=FileSystemLoader(templateDir)) + self._env.trim_blocks = True + self._env.lstrip_blocks = True - def addStone(self,x,y,color): - assert (x,y) not in self._index - self._index[(x,y)]=(next(self._indexGen),DiagramPoint(x,y,color)) + def addStone(self, x, y, color): + assert (x, y) not in self._index + self._index[(x, y)] = (next(self._indexGen), DiagramPoint(x, y, color)) - def addMove(self,x,y,color,label): - if (not self.highNumbers) and isinstance(label,int) and label%100!=0: - label%=100 + def addMove(self, x, y, color, label): + if (not self.highNumbers) and isinstance(label, int) and label%100 != 0: + label %= 100 - if (x,y) not in self._index: - self._index[(x,y)]=(next(self._indexGen),DiagramPoint(x,y,color,label)) + if (x, y) not in self._index: + self._index[(x, y)] = (next(self._indexGen), DiagramPoint(x, y, color, label)) else: - (_,point)=self._index[(x,y)] + (_, point) = self._index[(x, y)] if not point.label: - point.label=self._letter - self._letter=chr(ord(self._letter)+1) + point.label = self._letter + self._letter = chr(ord(self._letter)+1) self.overlays.append((label, point.label)) - def addLabel(self,x,y,label): - self._index[(x,y)]=(next(self._indexGen),DiagramPoint(x,y,"",label)) + def addLabel(self, x, y, label): + self._index[(x, y)] = (next(self._indexGen), DiagramPoint(x, y, "", label)) - def save(self,filename): + def save(self, filename): notes=open(filename+".txt", 'w') - notes.write("\n".join("{0} = {1}".format(a,b) for (a,b) in self.overlays)) + notes.write("\n".join("{0} = {1}".format(a, b) for (a, b) in self.overlays)) notes.close() diff --git a/src/diana/drawer/svg.py b/src/diana/drawer/svg.py --- a/src/diana/drawer/svg.py +++ b/src/diana/drawer/svg.py @@ -2,50 +2,42 @@ from .base import Base def adjustFont(base,text): - text=str(text) - if len(text)<2: return round(0.7*base) - elif len(text)<3: return round(0.55*base) - else: return round(0.4*base) - - -class DiagramPoint: - def __init__(self,x,y,color="",label=""): - self.x=x - self.y=y - self.color=color - self.label=label - - def __repr__(self): - return 'DiagramPoint({0},{1},"{2}","{3}")'.format(self.x,self.y,self.color,self.label) + text = str(text) + if len(text) < 2: + return round(0.7*base) + elif len(text) < 3: + return round(0.55*base) + else: + return round(0.4*base) class Svg(Base): - extension="svg" + extension = "svg" - padding=15 - highNumbers=True + padding = 15 + highNumbers = True - def __init__(self,start=0): + def __init__(self, start=0): super().__init__(start) - self.boardSize=480 - self.padding=30 + self.boardSize = 480 + self.padding = 30 - def render(self,templateName,bgcolor=""): - points = [p for (i,p) in sorted(self._index.values(), key=lambda x: x[0])] + def render(self, templateName, bgcolor=""): + points = [p for (i, p) in sorted(self._index.values(), key=lambda x: x[0])] - stones = [p for p in points if p.color and p.label==""] + stones = [p for p in points if p.color and p.label == ""] moves = [p for p in points if p.color and p.label] labels = [p for p in points if not p.color and p.label] params = { - "boardSize":self.boardSize, "padding":self.padding, "stones":stones, "moves":moves, - "labels":labels, "adjustFont":adjustFont, "bgcolor":bgcolor} + "boardSize": self.boardSize, "padding": self.padding, "stones": stones, "moves": moves, + "labels": labels, "adjustFont": adjustFont, "bgcolor": bgcolor} return self._env.get_template(templateName).render(params) - def save(self,filename,template="templ.svg",bgcolor=""): - file=open(filename+".svg",'w') - file.write(self.render(template,bgcolor)) + def save(self, filename, template="templ.svg", bgcolor=""): + file = open(filename+".svg", 'w') + file.write(self.render(template, bgcolor)) file.close() super().save(filename) diff --git a/src/diana/drawer/tikz.py b/src/diana/drawer/tikz.py --- a/src/diana/drawer/tikz.py +++ b/src/diana/drawer/tikz.py @@ -1,38 +1,39 @@ class Tikz: - content="" - footer="" - extension="tex" + content = "" + footer = "" + extension = "tex" - highNumbers=True + highNumbers = True def __init__(self): - self.content=r'''\begin{tikzpicture} + self.content = r'''\begin{tikzpicture} \draw[step=\boardSquare,gray,very thin] (0,0) grid (18\boardSquare,18\boardSquare); \draw (0,0) rectangle (18\boardSquare,18\boardSquare); ''' - # hvězdy + # stars for i in range(3): for j in range(3): - self.content+=r''' \filldraw[fill=black] ({0}\boardSquare,{1}\boardSquare) circle[radius=0.04];'''.format(6*i+3, 6*j+3)+'\n' - self.content+='\n' + self.content += r''' \filldraw[fill=black] ({0}\boardSquare, {1}\boardSquare) circle[radius=0.04];'''.format(6*i+3, 6*j+3)+'\n' + self.content += '\n' - self.footer=r'\end{tikzpicture}' '\n' + self.footer = r'\end{tikzpicture}' '\n' def __str__(self): return self.content+self.footer - def drawStone(self,x,y,color): - fill="black" if color=="b" else "white" - self.content+=r' \filldraw[draw=black,fill={0}] ({1}\boardSquare,{2}\boardSquare) circle[radius=0.5\boardSquare];'.format(fill,x,18-y)+'\n' + def drawStone(self, x, y, color): + fill = "black" if color == "b" else "white" + self.content += r' \filldraw[draw=black, fill={0}] ({1}\boardSquare, {2}\boardSquare) circle[radius=0.5\boardSquare];'.format(fill, x, 18-y)+'\n' - def drawMove(self,x,y,label,color): - fill="black" if color=="b" else "white" - labelColor="white" if color=="b" else "black" - if (not self.highNumbers) and isinstance(label,int) and label%100!=0: label=label%100 # dost neobratná logika + def drawMove(self, x, y, label, color): + fill = "black" if color == "b" else "white" + labelColor = "white" if color == "b" else "black" + if (not self.highNumbers) and isinstance(label, int) and label%100 != 0: + label = label%100 - self.content+=r' \filldraw[draw=black,fill={0}] ({1}\boardSquare,{2}\boardSquare) circle[radius=0.5\boardSquare] node[color={3}]{{{4}}};'.format(fill,x,18-y,labelColor,label)+'\n' + self.content += r' \filldraw[draw=black, fill={0}] ({1}\boardSquare, {2}\boardSquare) circle[radius=0.5\boardSquare] node[color={3}]{{{4}}};'.format(fill, x, 18-y, labelColor, label)+'\n' def getContent(self): return self.content+self.footer diff --git a/src/diana/go.py b/src/diana/go.py --- a/src/diana/go.py +++ b/src/diana/go.py @@ -1,39 +1,50 @@ -BLACK=1 -WHITE=-1 -EMPTY=0 +BLACK = 1 +WHITE = -1 +EMPTY = 0 class Go: - board=[[EMPTY]*19 for i in range(19)] + board = [[EMPTY]*19 for i in range(19)] def __init__(self): - self.board=[[EMPTY]*19 for i in range(19)] - self.moveCount=0 + self.board = [[EMPTY]*19 for i in range(19)] + self.moveCount = 0 - def move(self,color,y,x): - if self.board[x][y]!=EMPTY: return False + def move(self, color, y, x): + if self.board[x][y] != EMPTY: + return False - self.board[x][y]=color + self.board[x][y] = color - for i,j in ((-1,0),(1,0),(0,-1),(0,1)): - self.temp=[[False]*19 for i in range(19)] - if not self._floodFill(-color,x+i,y+j): self._remove() - self.temp=[[False]*19 for i in range(19)] - if not self._floodFill(color,x,y): - self.board[x][y]=EMPTY + for i, j in ((-1, 0), (1, 0), (0, -1), (0, 1)): + self.temp = [[False]*19 for i in range(19)] + if not self._floodFill(-color, x+i, y+j): + self._remove() + self.temp = [[False]*19 for i in range(19)] + if not self._floodFill(color, x, y): + self.board[x][y] = EMPTY return False - self.moveCount+=1 + self.moveCount += 1 return True - def _floodFill(self,color,x,y): - if x<0 or x>18 or y<0 or y>18: return False - if self.temp[x][y]: return False - if self.board[x][y]==EMPTY: return True - if self.board[x][y]!=color: return False - self.temp[x][y]=True - return self._floodFill(color,x-1,y) or self._floodFill(color,x+1,y) or self._floodFill(color,x,y-1) or self._floodFill(color,x,y+1) + def _floodFill(self, color, x, y): + if x < 0 or x > 18 or y < 0 or y > 18: + return False + if self.temp[x][y]: + return False + if self.board[x][y] == EMPTY: + return True + if self.board[x][y] != color: + return False + self.temp[x][y] = True + + return self._floodFill(color, x-1, y) or \ + self._floodFill(color, x+1, y) or \ + self._floodFill(color, x, y-1) or \ + self._floodFill(color, x, y+1) def _remove(self): for i in range(19): for j in range(19): - if self.temp[i][j]: self.board[i][j]=EMPTY + if self.temp[i][j]: + self.board[i][j] = EMPTY diff --git a/src/diana/sgfParser/__init__.py b/src/diana/sgfParser/__init__.py --- a/src/diana/sgfParser/__init__.py +++ b/src/diana/sgfParser/__init__.py @@ -1,29 +1,31 @@ -def skipWhitespace(s,start): - i=start - while ii: + k = 0 + (r, c) = (0, 0) + for (r, line) in enumerate(s.splitlines(True)): + c = i-k + if k+len(line) > i: break else: - k+=len(line) + k += len(line) return (r+1, c+1) class ParserError(Exception): - def __init__(self,msg,s,i): - self.msg=msg - self.row,self.col=strRowCol(s,i) - self.context=s[i:i+16] + def __init__(self, msg, s, i): + self.msg = msg + (self.row, self.col) = strRowCol(s, i) + self.context = s[i:i+16] def __str__(self): - return "{0} at row {1}, col {2}, got '{3}...' instead".format(self.msg,self.row,self.col,self.context) + return "{0} at row {1}, col {2}, got '{3}...' instead".format(self.msg, self.row, self.col, self.context) class ParserWarning(ParserError): diff --git a/src/diana/sgfParser/collection.py b/src/diana/sgfParser/collection.py --- a/src/diana/sgfParser/collection.py +++ b/src/diana/sgfParser/collection.py @@ -4,84 +4,90 @@ from .gameRecord import GameRecord class Collection: - def __init__(self,s): - self.gameTrees=[] - i=skipWhitespace(s,0) - if i>=len(s): return - elif not GameTree.fits(s,i): - raise ParserError("expected a GameTree starting with '('",s,i) - while GameTree.fits(s,i): - i,x=GameTree.create(s,i) + def __init__(self, s): + self.gameTrees = [] + i = skipWhitespace(s, 0) + if i >= len(s): + return + elif not GameTree.fits(s, i): + raise ParserError("expected a GameTree starting with '('", s, i) + while GameTree.fits(s, i): + (i, x) = GameTree.create(s, i) self.gameTrees.append(x) - if i=len(s) or s[i]!=")": - raise ParserError("expected end of a GameTree marked by ')'",s,i) - i=skipWhitespace(s,i+1) - return (i,res) + subroot = x.getNode(0) + subroot.parent = y + if y: + y.addChild(subroot) + i = skipWhitespace(s, i) + + if i >= len(s) or s[i] != ")": + raise ParserError("expected end of a GameTree marked by ')'", s, i) + i = skipWhitespace(s, i+1) + return (i, res) ## Expand multiple games into distinct GameTrees and yield each. def listGames(self): - if len(self.nodes)==0: return None + if len(self.nodes) == 0: + return None for node in self.nodes[0].listGINodes(): yield GameRecord(self._buildSubtree(node)) - def getNode(self,i): - if 0<=i0: - node=queue.popleft() + queue = deque(self.children) + while len(queue) > 0: + node = queue.popleft() if node.isGINode(): - empty=False + empty = False yield node queue.extend(node.children) - if empty: yield self # always yield at least self, can work as GINode as well as any other + if empty: + yield self # always yield at least self, can work as GINode as well as any other def isGINode(self): - return any(prop.type==GAME_INFO for prop in self.properties.values()) + return any(prop.type == GAME_INFO for prop in self.properties.values()) - def setProp(self,name,value): - self.properties[name]=value - # zkontrolovat typ value + def setProp(self, name, value): + self.properties[name] = value + # check value type - def setChildren(self,children): - self.children=children - for child in children: child.parent=self + def setChildren(self, children): + self.children = children + for child in children: + child.parent = self - def addChild(self,node): - if node in self.children: return node - node.parent=self + def addChild(self, node): + if node in self.children: + return node + node.parent = self self.children.append(node) return node - def removeChild(self,node): + def removeChild(self, node): if node not in self.children: return None del self.children[self.children.index(node)] - node.parent=None + node.parent = None return node - def removeChildAt(self,i): - if -len(self.children)0: - node,left,right=stack.pop() - if left>0: output.append("("*left) + while len(stack) > 0: + (node, left, right) = stack.pop() + if left > 0: + output.append("("*left) output.append(str(node)+"\n") - childCount=len(node.children) - if childCount==0: # a leaf + childCount = len(node.children) + if childCount == 0: # a leaf output.append(")"*right+"\n") - elif childCount==1: # a line - stack.append((node.children[0],0,right)) - else: # a branching node + elif childCount == 1: # a line + stack.append((node.children[0], 0, right)) + else: # a branching node # first child pops first, last child closes parent's parentheses - children=zip(node.children,[1]*childCount,[1]*(childCount-1)+[1+right]) - stack.extend(reversed(children)) + children = zip(node.children, [1]*childCount, [1]*(childCount-1)+[1+right]) + stack.extend(reversed(list(children))) return "".join(output) diff --git a/src/diana/sgfParser/propValues.py b/src/diana/sgfParser/propValues.py --- a/src/diana/sgfParser/propValues.py +++ b/src/diana/sgfParser/propValues.py @@ -4,175 +4,180 @@ from . import ParserError, skipWhitespac class Regexp: - number=re.compile(r"(\+|-|)\d+") - real=re.compile(r"(\+|-|)\d+(\.\d+)?") - point=re.compile(r"[a-zA-Z]{2}|") - text=re.compile(r"(?:.*?[^\\])??(?:\\\\)*(?=])", re.DOTALL) - composedText=re.compile(r"(?:.*?[^\\])??(?:\\\\)*(?=]|:)", re.DOTALL) + number = re.compile(r"(\+|-|)\d+") + real = re.compile(r"(\+|-|)\d+(\.\d+)?") + point = re.compile(r"[a-zA-Z]{2}|") + text = re.compile(r"(?:.*?[^\\])??(?:\\\\)*(?=])", re.DOTALL) + composedText = re.compile(r"(?:.*?[^\\])??(?:\\\\)*(?=]|:)", re.DOTALL) class Text: - softBreaks=re.compile(r"(^|[^\\])((\\\\)*)\\((\n\r)|(\r\n)|\r|\n)") - whitespace=re.compile(r"[\t\f\v]") - simpleWhitespace=re.compile(r"[\t\f\v\n\r]") - removeSlashes=re.compile(r"(^|[^\\])((\\\\)*)\\($|[^\\])") - unescapeSlashes=re.compile(r"\\\\") + softBreaks = re.compile(r"(^|[^\\])((\\\\)*)\\((\n\r)|(\r\n)|\r|\n)") + whitespace = re.compile(r"[\t\f\v]") + simpleWhitespace = re.compile(r"[\t\f\v\n\r]") + removeSlashes = re.compile(r"(^|[^\\])((\\\\)*)\\($|[^\\])") + unescapeSlashes = re.compile(r"\\\\") class Composed: - def __init__(self,a=None,b=None): - self.a=a - self.b=b + def __init__(self, a=None, b=None): + self.a = a + self.b = b def __str__(self): - return "{0}:{1}".format(self.a,self.b) + return "{0}:{1}".format(self.a, self.b) class Point: - def __init__(self,c,r): - self.r=r - self.c=c + def __init__(self, c, r): + self.r = r + self.c = c def __iter__(self): yield self.c yield self.r def __str__(self): - a=ord("a") - return chr(a+self.c)+chr(a+self.r) + a = ord("a") + return chr(a+self.c) + chr(a+self.r) ## Metatype matching one of the provided types. # # Returns the first match, so the order is important. def choose(*vTypes): - def f(s,start): + def f(s, start): for vType in vTypes: try: - i,x=vType(s,start) - return (i,x) - except ParserError: pass - raise ParserError("no variant of a 'choose' property value matched",s,start) + (i, x) = vType(s, start) + return (i, x) + except ParserError: + pass + raise ParserError("no variant of a 'choose' property value matched", s, start) return f -def singletonFits(s,i): - return i=len(s) or s[i]!=":": - raise ParserError("expected a composed property value separated by ':'",s,i) - i,b=vTypeB(s,i+1) - return (i,Composed(a,b)) +def compose(vTypeA, vTypeB): + def f(s, start): + (i, a) = vTypeA(s, start) + if i >= len(s) or s[i] != ":": + raise ParserError("expected a composed property value separated by ':'", s, i) + (i, b) = vTypeB(s, i+1) + return (i, Composed(a, b)) return f -def number(s,start): - m=Regexp.number.match(s,start) - if m is None: raise ParserError("expected a number matching '{0}'".format(Regexp.number.pattern),s,start) - res=int(m.group(0)) - return (m.end(),res) +def number(s, start): + m = Regexp.number.match(s, start) + if m is None: + raise ParserError("expected a number matching '{0}'".format(Regexp.number.pattern), s, start) + res = int(m.group(0)) + return (m.end(), res) -def real(s,start): - m=Regexp.real.match(s,start) - if m is None: raise ParserError("expected a real number matching '{0}'".format(Regexp.real.pattern),s,start) - res=float(m.group(0)) - return (m.end(),res) +def real(s, start): + m = Regexp.real.match(s, start) + if m is None: + raise ParserError("expected a real number matching '{0}'".format(Regexp.real.pattern), s, start) + res = float(m.group(0)) + return (m.end(), res) -def double(s,start): - c=s[start] +def double(s, start): + c = s[start] if c not in ("1", "2"): - raise ParserError("expected a double value, either '1' or '2'",s,start) - return (start+1,c) + raise ParserError("expected a double value, either '1' or '2'", s, start) + return (start+1, c) def color(s,start): - c=s[start] + c = s[start] if c not in ("B", "W"): - raise ParserError("expected a color value, either 'B' or 'W'",s,start) - return (start+1,c) + raise ParserError("expected a color value, either 'B' or 'W'", s, start) + return (start+1, c) -def text(simple=True,composed=False): - def f(s,start): - regexps=Regexp.Text - m=Regexp.composedText.match(s,start) if composed else Regexp.text.match(s,start) - res=m.group(0) - res=regexps.softBreaks.sub(r"\1\2",res) # remove soft line breaks +def text(simple=True, composed=False): + def f(s, start): + regexps = Regexp.Text + m = Regexp.composedText.match(s, start) if composed else Regexp.text.match(s, start) + res = m.group(0) + res = regexps.softBreaks.sub(r"\1\2", res) # remove soft line breaks if simple: - res=regexps.simpleWhitespace.sub(" ",res) # convert whitespace to spaces, no escapes + res = regexps.simpleWhitespace.sub(" ", res) # convert whitespace to spaces, no escapes else: - res=regexps.whitespace.sub(" ",res) # convert whitespace to spaces, no escapes - res=regexps.removeSlashes.sub(r"\1\2\4",res) - res=regexps.unescapeSlashes.sub(r"\\",res) # unescape slashes + res = regexps.whitespace.sub(" ", res) # convert whitespace to spaces, no escapes + res = regexps.removeSlashes.sub(r"\1\2\4", res) + res = regexps.unescapeSlashes.sub(r"\\", res) # unescape slashes - return (m.end(),res) + return (m.end(), res) return f -def empty(s,start): return (start,"") +def empty(s, start): + return (start, "") -def anything(s,start): - esc=False - i=start - for i,c in enumerate(s[start:],start): - if esc: esc=False - elif c=="\\": esc=True - elif c=="]": break - return (i,s[start:i]) +def anything(s, start): + esc = False + i = start + for (i, c) in enumerate(s[start:], start): + if esc: esc = False + elif c == "\\": esc = True + elif c == "]": break + return (i, s[start:i]) # go specific -def point(s,start): - m=Regexp.point.match(s,start) # !! limit to board size - if m is None: raise ParserError("expected a point value matching '{0}'".format(Regexp.point.pattern),s,start) - if m.group(0)=="": # pass, !! tt - return (m.end(),tuple()) - col=m.group(0)[0] - row=m.group(0)[1] - col=ord(col)-(ord("a") if "a"<=col<="z" else ord("A")-26) - row=ord(row)-(ord("a") if "a"<=row<="z" else ord("A")-26) - return (m.end(),Point(col,row)) +def point(s, start): + m = Regexp.point.match(s, start) # !! limit to board size + if m is None: + raise ParserError("expected a point value matching '{0}'".format(Regexp.point.pattern), s, start) + if m.group(0) == "": # pass, !! tt + return (m.end(), tuple()) + col = m.group(0)[0] + row = m.group(0)[1] + col = ord(col) - (ord("a") if "a" <= col <= "z" else ord("A")-26) + row = ord(row) - (ord("a") if "a" <= row <= "z" else ord("A")-26) + return (m.end(), Point(col, row)) -move=point -stone=point +move = point +stone = point diff --git a/src/diana/sgfParser/property.py b/src/diana/sgfParser/property.py --- a/src/diana/sgfParser/property.py +++ b/src/diana/sgfParser/property.py @@ -5,8 +5,8 @@ import logging as log from .propValues import choose, singleton, listOf, compose, number, real, double, color, text, empty, anything, point, move, stone from . import skipWhitespace, ParserError -GAME_INFO=1 -UNKNOWN=99 +GAME_INFO = 1 +UNKNOWN = 99 class DateException(Exception): @@ -14,174 +14,184 @@ class DateException(Exception): class Property: - identRegexp=re.compile(r"[A-Z]+") + identRegexp = re.compile(r"[A-Z]+") def __init__(self): - self.name="" - self.value="" + self.name = "" + self.value = "" @staticmethod - def fits(s,i): - return i