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
 
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
 
@@ -8,127 +8,135 @@ from .sgfParser import ParserError
 
from .sgfParser.collection import Collection
 
from .drawer.svg import Svg
 
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
 

	
 

	
 
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
 
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):
 
	pass
src/diana/sgfParser/collection.py
Show inline comments
 
from .node import Node
 
from . import skipWhitespace, ParserError
 
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
 
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
 
@@ -4,117 +4,124 @@ import logging as log
 
from . import skipWhitespace, ParserWarning
 
from .property import Property, GAME_INFO
 

	
 

	
 
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):
 
		return ";" + "".join(str(p) for p in self.properties.values())
 

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

	
 
from . import ParserError, skipWhitespace
 

	
 

	
 
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
 
@@ -2,186 +2,196 @@ import re
 
from datetime import date
 
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):
 
	pass
 

	
 

	
 
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
 
@@ -3,94 +3,94 @@ from datetime import date
 
import unittest
 
from unittest import TestCase
 
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$")
 

	
 
	def testEmptySgf(self):
 
		Collection("(;)")
 

	
 
@@ -99,8 +99,9 @@ class TestCollection(TestCase):
 
			Collection(f.read())
 

	
 
	def testComplexSgf(self):
 
		with open(os.path.join(dataDir, "kogos.sgf")) as f:
 
			Collection(f.read())
 

	
 

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