Changeset - 686166c7d5bc
[Not reviewed]
default
0 13 0
Laman - 3 years ago 2022-03-05 18:08:48

added more whitespace
13 files changed with 635 insertions and 592 deletions:
0 comments (0 inline, 0 general)
src/diana/config.py
Show inline comments
 
@@ -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
src/diana/diana.py
Show inline comments
 
@@ -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))
src/diana/drawer/base.py
Show inline comments
 
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()
src/diana/drawer/svg.py
Show inline comments
 
@@ -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)
src/diana/drawer/tikz.py
Show inline comments
 
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
src/diana/go.py
Show inline comments
 
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
src/diana/sgfParser/__init__.py
Show inline comments
 
def skipWhitespace(s,start):
 
	i=start
 
	while i<len(s) and s[i].isspace(): i+=1
 
def skipWhitespace(s, start):
 
	i = start
 
	while i < len(s) and s[i].isspace():
 
		i+=1
 

	
 
	return i
 

	
 

	
 
def strRowCol(s, i):
 
	k=0
 
	r,c=0,0
 
	for (r,line) in enumerate(s.splitlines(True)):
 
		c=i-k
 
		if k+len(line)>i:
 
	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):
src/diana/sgfParser/collection.py
Show inline comments
 
@@ -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):
 
			raise ParserError("expected EOF",s,i)
 
		if i < len(s):
 
			raise ParserError("expected EOF", s, i)
 

	
 
	def listGames(self):
 
		for tree in self.gameTrees:
 
			for game in tree.listGames(): yield game
 
			for game in tree.listGames():
 
				yield game
 

	
 

	
 
class GameTree:
 
	def __init__(self):
 
		self.nodes=[]
 
		self.branches=[]
 
		self.nodes = []
 
		self.branches = []
 

	
 
	@staticmethod
 
	def fits(s,i):
 
		return i<len(s) and s[i]=="("
 
	def fits(s, i):
 
		return i < len(s) and s[i] == "("
 

	
 
	@staticmethod
 
	def create(s,start):
 
		assert GameTree.fits(s,start)
 
		res=GameTree()
 
	def create(s, start):
 
		assert GameTree.fits(s, start)
 
		res = GameTree()
 

	
 
		i=skipWhitespace(s,start+1)
 
		if not Node.fits(s,i):
 
			raise ParserError("expected a Node starting with ';'",s,i)
 
		i = skipWhitespace(s, start+1)
 
		if not Node.fits(s, i):
 
			raise ParserError("expected a Node starting with ';'", s, i)
 

	
 
		y=None
 
		while Node.fits(s,i):
 
			i,x=Node.create(s,i)
 
		y = None
 
		while Node.fits(s, i):
 
			(i, x) = Node.create(s, i)
 
			res.nodes.append(x)
 
			if y: y.addChild(x)
 
			x.parent=y
 
			y=x
 
			i=skipWhitespace(s,i)
 
			if y:
 
				y.addChild(x)
 
			x.parent = y
 
			y = x
 
			i = skipWhitespace(s, i)
 

	
 
		while GameTree.fits(s,i):
 
			i,x=GameTree.create(s,i)
 
		while GameTree.fits(s, i):
 
			(i, x) = GameTree.create(s, i)
 
			res.branches.append(x)
 
			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)
 
			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<=i<len(self.nodes):
 
	def getNode(self, i):
 
		if 0 <= i < len(self.nodes):
 
			return self.nodes[i]
 
		return None
 

	
 
	## Create and return a new game tree containing the provided Node.
 
	#
 
	# Ancestor nodes are copied, descendants are moved from the seedNode.
 
	def _buildSubtree(self,seedNode):
 
		node=seedNode.copy()
 
	def _buildSubtree(self, seedNode):
 
		node = seedNode.copy()
 
		node.setChildren(seedNode.children)
 
		seedNode.children=[]
 
		seedNode.children = []
 

	
 
		while node.parent:
 
			newNode=node.parent.copy()
 
			newNode = node.parent.copy()
 
			newNode.addChild(node)
 
			node=newNode
 
			node = newNode
 

	
 
		return node
src/diana/sgfParser/gameRecord.py
Show inline comments
 
@@ -3,15 +3,15 @@ from .node import Node
 

	
 
## Wrapper around a Node tree.
 
class GameRecord:
 
	def __init__(self,root=None):
 
		self.root=root or Node()
 
		self._gameInfoNode=next(root.listGINodes())
 
	def __init__(self, root=None):
 
		self.root = root or Node()
 
		self._gameInfoNode = next(root.listGINodes())
 

	
 
	def export(self):
 
		return self.root.export()
 

	
 
	def set(self,name,value):
 
		self._gameInfoNode.setProp(name,value)
 
	def set(self, name, value):
 
		self._gameInfoNode.setProp(name, value)
 

	
 
	def get(self,name,default=None):
 
		return self._gameInfoNode.getProp(name,default)
 
	def get(self, name, default=None):
 
		return self._gameInfoNode.getProp(name, default)
src/diana/sgfParser/node.py
Show inline comments
 
@@ -7,91 +7,97 @@ from .property import Property, GAME_INF
 

	
 
class Node:
 
	def __init__(self):
 
		self.properties=dict()
 
		self.parent=None
 
		self.children=[]
 
		self.properties = dict()
 
		self.parent = None
 
		self.children = []
 

	
 
	@staticmethod
 
	def fits(s,i):
 
		return i<len(s) and s[i]==";"
 
	def fits(s, i):
 
		return i < len(s) and s[i] == ";"
 

	
 
	@staticmethod
 
	def create(s,start):
 
		assert Node.fits(s,start)
 
		res=Node()
 
	def create(s, start):
 
		assert Node.fits(s, start)
 
		res = Node()
 

	
 
		i=skipWhitespace(s,start+1)
 
		while Property.fits(s,i):
 
			i,x=Property.create(s,i)
 
		i = skipWhitespace(s, start+1)
 
		while Property.fits(s, i):
 
			(i, x) = Property.create(s, i)
 
			if x.name in res.properties:
 
				log.warning(ParserWarning('duplicate "{0}" property in a node. second value ignored'.format(x.name),s,i))
 
				log.warning(ParserWarning('duplicate "{0}" property in a node. second value ignored'.format(x.name), s, i))
 
			else:
 
				res.properties[x.name]=x
 
			i=skipWhitespace(s,i)
 
		return (i,res)
 
				res.properties[x.name] = x
 
			i = skipWhitespace(s, i)
 
		return (i, res)
 

	
 
	def listGINodes(self):
 
		if self.isGINode(): yield self
 
		empty=not self.isGINode()
 
		if self.isGINode():
 
			yield self
 
		empty = not self.isGINode()
 

	
 
		node=self
 
		node = self
 
		while node.parent:
 
			node=node.parent
 
			node = node.parent
 
			if node.isGINode():
 
				empty=False
 
				empty = False
 
				yield node
 

	
 
		queue=deque(self.children)
 
		while len(queue)>0:
 
			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)<i<len(self.children):
 
			res=self.children[i]
 
	def removeChildAt(self, i):
 
		if -len(self.children) < i < len(self.children):
 
			res = self.children[i]
 
			del self.children[i]
 
			res.parent=None
 
			res.parent = None
 
			return res
 
		return None
 

	
 
	## Create a copy of the Node, with the same parent and deep copied properties, no copied children.
 
	def copy(self):
 
		res=Node()
 
		res.properties={k: v.copy() for (k,v) in self.properties.items()}
 
		res.parent=self.parent
 
		res = Node()
 
		res.properties = {k: v.copy() for (k, v) in self.properties.items()}
 
		res.parent = self.parent
 
		return res
 

	
 
	def getProp(self,name,default=None):
 
		if name in self.properties: return self.properties[name].value
 
		else: return default
 
	def getProp(self, name, default=None):
 
		if name in self.properties:
 
			return self.properties[name].value
 
		else:
 
			return default
 

	
 
	## Returns textual representation of the Node itself, but disregards its children.
 
	def __str__(self):
 
@@ -99,22 +105,23 @@ class Node:
 

	
 
	def export(self):
 
		# there is a beautiful recursive solution, which this stack is too narrow to contain
 
		stack=[(self,1,1)]
 
		output=[]
 
		stack = [(self, 1, 1)]
 
		output = []
 

	
 
		while len(stack)>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)
src/diana/sgfParser/propValues.py
Show inline comments
 
@@ -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) and s[i]=="["
 
def singletonFits(s, i):
 
	return i < len(s) and s[i] == "["
 

	
 

	
 
def singletonEnds(s,i):
 
	return i<len(s) and s[i]=="]"
 
def singletonEnds(s, i):
 
	return i < len(s) and s[i] == "]"
 

	
 

	
 
def singleton(vType):
 
	def f(s,start):
 
		if not singletonFits(s,start):
 
			raise ParserError("expected a property value starting with '['",s,start)
 
		i,x=vType(s,start+1)
 
		if not singletonEnds(s,i):
 
			raise ParserError("expected a property value ending with ']'",s,i)
 
		i=skipWhitespace(s,i+1)
 
		return (i,x)
 
	def f(s, start):
 
		if not singletonFits(s, start):
 
			raise ParserError("expected a property value starting with '['", s, start)
 
		(i, x) = vType(s, start+1)
 
		if not singletonEnds(s, i):
 
			raise ParserError("expected a property value ending with ']'", s, i)
 
		i = skipWhitespace(s, i+1)
 
		return (i, x)
 
	return f
 

	
 

	
 
def listOf(vType,allowEmpty=False):
 
	def f(s,start):
 
		i=start
 
		if not singletonFits(s,i):
 
			raise ParserError("expected a property value starting with '['",s,i)
 
		if singletonEnds(s,i+1) and allowEmpty:
 
			i=skipWhitespace(s,i+2)
 
			return (i,[])
 
		single=singleton(vType)
 
		i,x=single(s,i)
 
		res=[x]
 
		while singletonFits(s,i):
 
			i,x=single(s,i)
 
def listOf(vType, allowEmpty=False):
 
	def f(s, start):
 
		i = start
 
		if not singletonFits(s, i):
 
			raise ParserError("expected a property value starting with '['", s, i)
 
		if singletonEnds(s, i+1) and allowEmpty:
 
			i = skipWhitespace(s, i+2)
 
			return (i, [])
 
		single = singleton(vType)
 
		(i, x) = single(s, i)
 
		res = [x]
 
		while singletonFits(s, i):
 
			(i, x) = single(s, i)
 
			res.append(x)
 
		return (i,res)
 
		return (i, res)
 
	return f
 

	
 

	
 
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))
 
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
src/diana/sgfParser/property.py
Show inline comments
 
@@ -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<len(s) and s[i].isupper()
 
	def fits(s, i):
 
		return i < len(s) and s[i].isupper()
 

	
 
	@staticmethod
 
	def create(s,start):
 
		assert Property.fits(s,start)
 
		res=Property()
 
		i,res.name=Property.ident(s,start)
 
		i=skipWhitespace(s,i)
 
	def create(s, start):
 
		assert Property.fits(s, start)
 
		res = Property()
 
		(i, res.name) = Property.ident(s, start)
 
		i = skipWhitespace(s, i)
 
		try:
 
			i,x=Property.createValue(s,i,res.name)
 
		except ParserError as e: # malformed value
 
			(i, x) = Property.createValue(s, i, res.name)
 
		except ParserError as e:  # a malformed value
 
			log.warning(e)
 
			i,x=choose(listOf(anything), singleton(anything))(s,i)
 
			res.name="_"+res.name
 
		res.value=x
 
		if res.name=="DT":
 
			res=DateProperty(x)
 
		i=skipWhitespace(s,i)
 
		return (i,res)
 
			(i, x) = choose(listOf(anything), singleton(anything))(s, i)
 
			res.name = "_"+res.name
 
		res.value = x
 
		if res.name == "DT":
 
			res = DateProperty(x)
 
		i = skipWhitespace(s, i)
 
		return (i, res)
 

	
 
	@staticmethod
 
	def ident(s,start):
 
		m=Property.identRegexp.match(s,start)
 
		if m is None: raise ParserError("expected a property identifier matching '[A-Z]+'",s,start)
 
		return (m.end(),m.group())
 
	def ident(s, start):
 
		m = Property.identRegexp.match(s, start)
 
		if m is None:
 
			raise ParserError("expected a property identifier matching '[A-Z]+'", s, start)
 
		return (m.end(), m.group())
 

	
 
	@staticmethod
 
	def createValue(s,start,name):
 
	def createValue(s, start, name):
 
		if name in Property.patterns:
 
			return Property.patterns[name](s,start)
 
			return Property.patterns[name](s, start)
 
		else:
 
			log.info("unknown property %s at position %d",name,start)
 
			return choose(listOf(anything), singleton(anything))(s,start)
 
			log.info("unknown property %s at position %d", name, start)
 
			return choose(listOf(anything), singleton(anything))(s, start)
 

	
 
	@property
 
	def type(self):
 
		gameInfo={"AN","BR","BT","CP","DT","EV","GN","GC","ON","OT","PB","PC","PW","RE","RO","RU","SO","TM","US","WR","WT"}
 
		if self.name in gameInfo: return GAME_INFO
 
		else: return UNKNOWN
 
		gameInfo = {"AN", "BR", "BT", "CP", "DT", "EV", "GN", "GC", "ON", "OT", "PB", "PC", "PW", "RE", "RO", "RU", "SO", "TM", "US", "WR", "WT"}
 
		if self.name in gameInfo:
 
			return GAME_INFO
 
		else:
 
			return UNKNOWN
 

	
 
	def copy(self):
 
		res=Property()
 
		res.name=self.name
 
		res.value=self.value if not isinstance(self.value,list) else self.value[:]
 
		res = Property()
 
		res.name = self.name
 
		res.value = self.value if not isinstance(self.value, list) else self.value[:]
 
		return res
 

	
 
	def __str__(self):
 
		name=self.name.lstrip("_")
 
		val="[{0}]".format(self.value) if not isinstance(self.value,list) else "".join("[{0}]".format(x) for x in self.value)
 
		return "{0}{1}".format(name,val)
 
		name = self.name.lstrip("_")
 
		val = "[{0}]".format(self.value) \
 
			if not isinstance(self.value, list) \
 
			else "".join("[{0}]".format(x) for x in self.value)
 
		return "{0}{1}".format(name, val)
 

	
 
	patterns={
 
		"B":singleton(move),
 
		"KO":singleton(empty),
 
		"MN":singleton(number),
 
		"W":singleton(move),
 
		"AB":listOf(stone), #
 
		"AE":listOf(point), #
 
		"AW":listOf(stone), #
 
		"PL":singleton(color),
 
		"C":singleton(text(simple=False)),
 
		"DM":singleton(double),
 
		"GB":singleton(double),
 
		"GW":singleton(double),
 
		"HO":singleton(double),
 
		"N":singleton(text()),
 
		"UC":singleton(double),
 
		"V":singleton(real),
 
		"BM":singleton(double),
 
		"DO":singleton(empty),
 
		"IT":singleton(empty),
 
		"TE":singleton(double),
 
		"AR":listOf(compose(point,point)), #
 
		"CR":listOf(point), #
 
		"DD":listOf(point,allowEmpty=True), #
 
		"LB":listOf(compose(point,text())), #
 
		"LN":listOf(compose(point,point)), #
 
		"MA":listOf(point), #
 
		"SL":listOf(point), #
 
		"SQ":listOf(point), #
 
		"TR":listOf(point), #
 
		"AP":singleton(compose(text(composed=True),text())), #
 
		"CA":singleton(text()),
 
		"FF":singleton(number),
 
		"GM":singleton(number),
 
		"ST":singleton(number),
 
		"SZ":choose(singleton(number),singleton(compose(number,number))), #
 
		"AN":singleton(text()),
 
		"BR":singleton(text()),
 
		"BT":singleton(text()),
 
		"CP":singleton(text()),
 
		"DT":singleton(text()),
 
		"EV":singleton(text()),
 
		"GN":singleton(text()),
 
		"GC":singleton(text(simple=False)),
 
		"ON":singleton(text()),
 
		"OT":singleton(text()),
 
		"PB":singleton(text()),
 
		"PC":singleton(text()),
 
		"PW":singleton(text()),
 
		"RE":singleton(text()),
 
		"RO":singleton(text()),
 
		"RU":singleton(text()),
 
		"SO":singleton(text()),
 
		"TM":singleton(real),
 
		"US":singleton(text()),
 
		"WR":singleton(text()),
 
		"WT":singleton(text()),
 
		"BL":singleton(real),
 
		"OB":singleton(number),
 
		"OW":singleton(number),
 
		"WL":singleton(real),
 
		"FG":choose(singleton(empty),singleton(compose(number,text()))), #
 
		"PM":singleton(number),
 
		"VW":listOf(point,allowEmpty=True), #
 
	patterns = {
 
		"B": singleton(move),
 
		"KO": singleton(empty),
 
		"MN": singleton(number),
 
		"W": singleton(move),
 
		"AB": listOf(stone),  #
 
		"AE": listOf(point),  #
 
		"AW": listOf(stone),  #
 
		"PL": singleton(color),
 
		"C": singleton(text(simple=False)),
 
		"DM": singleton(double),
 
		"GB": singleton(double),
 
		"GW": singleton(double),
 
		"HO": singleton(double),
 
		"N": singleton(text()),
 
		"UC": singleton(double),
 
		"V": singleton(real),
 
		"BM": singleton(double),
 
		"DO": singleton(empty),
 
		"IT": singleton(empty),
 
		"TE": singleton(double),
 
		"AR": listOf(compose(point, point)),  #
 
		"CR": listOf(point),  #
 
		"DD": listOf(point, allowEmpty=True),  #
 
		"LB": listOf(compose(point, text())),  #
 
		"LN": listOf(compose(point, point)),  #
 
		"MA": listOf(point),  #
 
		"SL": listOf(point),  #
 
		"SQ": listOf(point),  #
 
		"TR": listOf(point),  #
 
		"AP": singleton(compose(text(composed=True), text())),  #
 
		"CA": singleton(text()),
 
		"FF": singleton(number),
 
		"GM": singleton(number),
 
		"ST": singleton(number),
 
		"SZ": choose(singleton(number), singleton(compose(number, number))),  #
 
		"AN": singleton(text()),
 
		"BR": singleton(text()),
 
		"BT": singleton(text()),
 
		"CP": singleton(text()),
 
		"DT": singleton(text()),
 
		"EV": singleton(text()),
 
		"GN": singleton(text()),
 
		"GC": singleton(text(simple=False)),
 
		"ON": singleton(text()),
 
		"OT": singleton(text()),
 
		"PB": singleton(text()),
 
		"PC": singleton(text()),
 
		"PW": singleton(text()),
 
		"RE": singleton(text()),
 
		"RO": singleton(text()),
 
		"RU": singleton(text()),
 
		"SO": singleton(text()),
 
		"TM": singleton(real),
 
		"US": singleton(text()),
 
		"WR": singleton(text()),
 
		"WT": singleton(text()),
 
		"BL": singleton(real),
 
		"OB": singleton(number),
 
		"OW": singleton(number),
 
		"WL": singleton(real),
 
		"FG": choose(singleton(empty), singleton(compose(number, text()))),  #
 
		"PM": singleton(number),
 
		"VW": listOf(point, allowEmpty=True),  #
 

	
 
		# go specific
 
		"HA":singleton(number),
 
		"KM":singleton(real),
 
		"TB":listOf(point,allowEmpty=True),
 
		"TW":listOf(point,allowEmpty=True)
 
		"HA": singleton(number),
 
		"KM": singleton(real),
 
		"TB": listOf(point, allowEmpty=True),
 
		"TW": listOf(point, allowEmpty=True)
 
	}
 

	
 

	
 
class DateProperty(Property):
 
	def __init__(self,value):
 
	def __init__(self, value):
 
		super().__init__()
 
		self.name="DT"
 
		self.value=[]
 
		self.rawValue=value
 
		self.name = "DT"
 
		self.value = []
 
		self.rawValue = value
 
		self.parse(value)
 

	
 
	def parse(self,s):
 
		regexp=re.compile(r"\d{4}(-\d\d){0,2}(,(\d{4}(-\d\d){0,2}|\d\d(-\d\d)?))*")
 
		match=re.search(regexp,s)
 
	def parse(self, s):
 
		regexp = re.compile(r"\d{4}(-\d\d){0,2}(,(\d{4}(-\d\d){0,2}|\d\d(-\d\d)?))*")
 
		match = re.search(regexp, s)
 
		if not match:
 
			raise DateException('Could not parse a DT value: "{0}"'.format(s))
 
		substr=match.group(0)
 
		dateStrs=substr.split(",")
 
		dates=[]
 
		prevFormat=None
 
		substr = match.group(0)
 
		dateStrs = substr.split(",")
 
		dates = []
 
		prevFormat = None
 

	
 
		for s in dateStrs:
 
			try:
 
				(prevFormat,d)=DateProperty.parseSingle(s,prevFormat,dates[-1] if dates else None)
 
				(prevFormat, d) = DateProperty.parseSingle(s, prevFormat, dates[-1] if dates else None)
 
			except ValueError:
 
				raise DateException('Could not parse a DT value: "{0}"'.format(s))
 
			dates.append(d)
 
		self.value=dates
 
		self.value = dates
 

	
 
	@staticmethod
 
	def parseSingle(dateStr,prevFormat,prev=None):
 
		tokens=dateStr.split("-")
 
		num_tokens=list(map(int,tokens))
 
		if len(tokens)==3:
 
			return ("YMD",date(*num_tokens))
 
		elif len(tokens)==2:
 
			if len(tokens[0])==4: return ("YM",date(*num_tokens,1))
 
			else: return ("MD",date(prev.year,*num_tokens))
 
	def parseSingle(dateStr, prevFormat, prev=None):
 
		tokens = dateStr.split("-")
 
		num_tokens = list(map(int, tokens))
 
		if len(tokens) == 3:
 
			return ("YMD", date(*num_tokens))
 
		elif len(tokens) == 2:
 
			if len(tokens[0]) == 4:
 
				return ("YM", date(*num_tokens, 1))
 
			else:
 
				return ("MD", date(prev.year, *num_tokens))
 
		else:
 
			if len(tokens[0])==4: return ("Y",date(*num_tokens,1,1))
 
			elif prevFormat in ("YM","M"): return ("M",date(prev.year,*num_tokens,1))
 
			else: return ("D",date(prev.year,prev.month,*num_tokens))
 
			if len(tokens[0]) == 4:
 
				return ("Y", date(*num_tokens, 1, 1))
 
			elif prevFormat in ("YM","M"):
 
				return ("M", date(prev.year, *num_tokens, 1))
 
			else:
 
				return ("D", date(prev.year, prev.month, *num_tokens))
src/diana/tests/testSgfParser.py
Show inline comments
 
@@ -6,88 +6,88 @@ import os
 

	
 
from diana.sgfParser import strRowCol
 
from diana.sgfParser.collection import Collection
 
from diana.sgfParser.property import Property,DateProperty,DateException
 
from diana.sgfParser.propValues import text,compose
 
from diana.sgfParser.property import Property, DateProperty, DateException
 
from diana.sgfParser.propValues import text, compose
 

	
 

	
 
dataDir=os.path.join(os.path.dirname(__file__), "data")
 
dataDir = os.path.join(os.path.dirname(__file__), "data")
 

	
 

	
 
class TestUtils(TestCase):
 
	def testTextPos(self):
 
		s="abc\ndef\rgh\r\nij\n\rklmn"
 
		rc=[
 
			[1,2,3,4],
 
			[1,2,3,4],
 
			[1,2,3,4],
 
			[1,2,3], [1], # don't care about LFCR, we unicode now
 
			[1,2,3,4]
 
		s = "abc\ndef\rgh\r\nij\n\rklmn"
 
		rc = [
 
			[1, 2, 3, 4],
 
			[1, 2, 3, 4],
 
			[1, 2, 3, 4],
 
			[1, 2, 3], [1],  # don't care about LFCR, we unicode now
 
			[1, 2, 3, 4]
 
		]
 
		res=chain((r+1,c) for (r,row) in enumerate(rc) for c in row)
 
		for (i,(r,c)) in zip(range(len(s)+1), res):
 
		res = chain((r+1, c) for (r, row) in enumerate(rc) for c in row)
 
		for (i, (r, c)) in zip(range(len(s)+1), res):
 
			self.assertEqual(strRowCol(s, i), (r, c))
 

	
 

	
 
class TestProperty(TestCase):
 
	def testName(self):
 
		with self.assertRaises(AssertionError):
 
			Property.create("[99]",0)
 
			Property.create("[99]", 0)
 
		with self.assertRaises(AssertionError):
 
			Property.create("99[99]",0)
 
			Property.create("99[99]", 0)
 

	
 
		i,prop=Property.create("MN[99]",0)
 
		self.assertNotEqual((i,prop), (0,None))
 
		self.assertEqual((i,prop.name), (6,"MN"))
 
		(i, prop) = Property.create("MN[99]", 0)
 
		self.assertNotEqual((i, prop), (0, None))
 
		self.assertEqual((i, prop.name), (6, "MN"))
 

	
 
	def testText(self):
 
		s=r"""[abc\
 
		s = r"""[abc\
 
def
 
ghi]"""
 
		self.assertEqual(text()(s,1)[1], "abcdef ghi")
 
		self.assertEqual(text(False)(s,1)[1], "abcdef\nghi")
 
		self.assertEqual(text()(s, 1)[1], "abcdef ghi")
 
		self.assertEqual(text(False)(s, 1)[1], "abcdef\nghi")
 

	
 
		s="""[m\\no\\\tpqr\\]\\\\]"""
 
		self.assertEqual(text()(s,1)[1], "mno pqr]\\")
 
		self.assertEqual(text(False)(s,1)[1], "mno pqr]\\")
 
		s = """[m\\no\\\tpqr\\]\\\\]"""
 
		self.assertEqual(text()(s, 1)[1], "mno pqr]\\")
 
		self.assertEqual(text(False)(s, 1)[1], "mno pqr]\\")
 

	
 
		s="""[abc:def]"""
 
		parsed=compose(text(composed=True),text(composed=True))(s,1)
 
		s = """[abc:def]"""
 
		parsed = compose(text(composed=True), text(composed=True))(s, 1)
 
		self.assertEqual(str(parsed[1]), "abc:def")
 

	
 

	
 
class TestDateProperty(TestCase):
 
	def testSingle(self):
 
		self.assertEqual(DateProperty.parseSingle("2019","Y")[1], date(2019,1,1))
 
		self.assertEqual(DateProperty.parseSingle("2019-06","YM")[1], date(2019,6,1))
 
		self.assertEqual(DateProperty.parseSingle("2019-06-22","YMD")[1], date(2019,6,22))
 
		d=date(2019,6,21)
 
		self.assertEqual(DateProperty.parseSingle("22","D",d)[1], date(2019,6,22))
 
		self.assertEqual(DateProperty.parseSingle("07-22","MD",d)[1], date(2019,7,22))
 
		self.assertEqual(DateProperty.parseSingle("2020-07-22","YMD",d)[1], date(2020,7,22))
 
		self.assertEqual(DateProperty.parseSingle("2019", "Y")[1], date(2019, 1, 1))
 
		self.assertEqual(DateProperty.parseSingle("2019-06", "YM")[1], date(2019, 6, 1))
 
		self.assertEqual(DateProperty.parseSingle("2019-06-22", "YMD")[1], date(2019, 6, 22))
 
		d = date(2019, 6, 21)
 
		self.assertEqual(DateProperty.parseSingle("22", "D", d)[1], date(2019, 6, 22))
 
		self.assertEqual(DateProperty.parseSingle("07-22", "MD", d)[1], date(2019, 7, 22))
 
		self.assertEqual(DateProperty.parseSingle("2020-07-22", "YMD", d)[1], date(2020, 7, 22))
 
		with self.assertRaises(ValueError):
 
			DateProperty.parseSingle("2019-31","YMD")
 
			DateProperty.parseSingle("2019-31", "YMD")
 

	
 
	def testParse(self):
 
		self.assertEqual(DateProperty("1996-05,06").value, [date(1996,5,1),date(1996,6,1)])
 
		self.assertEqual(DateProperty("1996-05-06,07,08").value, [date(1996,5,6),date(1996,5,7),date(1996,5,8)])
 
		self.assertEqual(DateProperty("1996,1997").value, [date(1996,1,1),date(1997,1,1)])
 
		self.assertEqual(DateProperty("1996-12-27,28,1997-01-03,04").value, [date(1996,12,27),date(1996,12,28),date(1997,1,3),date(1997,1,4)])
 
		self.assertEqual(DateProperty("1997-05-05,1997-05-06").value, [date(1997,5,5),date(1997,5,6)])
 
		self.assertEqual(DateProperty("Published on 1997-05-06").value, [date(1997,5,6)])
 
		self.assertEqual(DateProperty("1996-05,06").value, [date(1996, 5, 1), date(1996, 6, 1)])
 
		self.assertEqual(DateProperty("1996-05-06,07,08").value, [date(1996, 5, 6), date(1996, 5, 7), date(1996, 5, 8)])
 
		self.assertEqual(DateProperty("1996,1997").value, [date(1996, 1, 1), date(1997, 1, 1)])
 
		self.assertEqual(DateProperty("1996-12-27,28,1997-01-03,04").value, [date(1996, 12, 27), date(1996, 12, 28), date(1997, 1, 3), date(1997, 1, 4)])
 
		self.assertEqual(DateProperty("1997-05-05,1997-05-06").value, [date(1997, 5, 5), date(1997, 5, 6)])
 
		self.assertEqual(DateProperty("Published on 1997-05-06").value, [date(1997, 5, 6)])
 
		with self.assertRaises(DateException):
 
			DateProperty("unknown")
 

	
 

	
 
class TestCollection(TestCase):
 
	def testSubtrees(self):
 
		c=Collection("""
 
		c = Collection("""
 
(;B[aa]
 
	(;W[ab]PB[Some Black]PW[Some White];B[ac])
 
	(;W[bb]PB[Other Black]PW[Other White])
 
)""")
 
		games=list(c.listGames())
 
		games = list(c.listGames())
 

	
 
		self.assertEqual(len(games),2)
 
		self.assertEqual(len(games), 2)
 
		self.assertRegex(games[0].export(), r"^\(;B\[aa]\n;(PB\[Some Black]|PW\[Some White]|W\[ab]){3}\n;B\[ac]\n\)\n$")
 
		self.assertRegex(games[1].export(), r"^\(;B\[aa]\n;(PB\[Other Black]|PW\[Other White]|W\[bb]){3}\n\)\n$")
 

	
 
@@ -102,5 +102,6 @@ class TestCollection(TestCase):
 
		with open(os.path.join(dataDir, "kogos.sgf")) as f:
 
			Collection(f.read())
 

	
 

	
 
if __name__ == '__main__':
 
	unittest.main()
0 comments (0 inline, 0 general)