Changeset - d57d0d4ede15
[Not reviewed]
default
0 13 0
Laman - 3 years ago 2022-03-05 18:37:25

changed the naming convention to follow standards
13 files changed with 217 insertions and 216 deletions:
0 comments (0 inline, 0 general)
src/diana/config.py
Show inline comments
 
@@ -2,10 +2,10 @@ import os
 
from argparse import ArgumentParser
 

	
 

	
 
progName = "DianaXO"
 
version = (0, 1, 0)
 
PROG_NAME = "DianaXO"
 
VERSION = (0, 1, 0)
 

	
 
curDir = os.path.dirname(__file__)
 
cur_dir = os.path.dirname(__file__)
 

	
 
parser = ArgumentParser()
 
parser.add_argument("--encoding")
 
@@ -17,34 +17,34 @@ parser.add_argument("--format", choices=
 
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(PROG_NAME, *VERSION))
 

	
 

	
 
inputFiles = []
 
input_files = []
 
encoding = "utf-8-sig"
 
sgfSuffix = True
 
sgf_suffix = True
 
recursive = False
 
outputDir = curDir
 
outputFormat = "svg"
 
keepBroken = False
 
output_dir = cur_dir
 
output_format = "svg"
 
keep_broken = False
 

	
 
movesPerDiagram = 100
 
minMovesPerDiagram = 10
 
moves_per_diagram = 100
 
min_moves_per_diagram = 10
 

	
 

	
 
def parseArgs():
 
	global inputFiles, encoding, sgfSuffix, recursive, outputDir, outputFormat, keepBroken, movesPerDiagram
 
	global minMovesPerDiagram
 
def parse_args():
 
	global input_files, encoding, sgf_suffix, recursive, output_dir, output_format, keep_broken, moves_per_diagram
 
	global min_moves_per_diagram
 

	
 
	args=parser.parse_args()
 

	
 
	inputFiles=args.input
 
	input_files = args.input
 
	if args.encoding: encoding = args.encoding
 
	if args.suffix: sgfSuffix = True
 
	if args.suffix: sgf_suffix = 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.output: output_dir = args.output
 
	if args.format: output_format = args.format
 
	if args.keep_broken is not None: keep_broken = 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: moves_per_diagram = args.moves_p_d
 
	if args.min_p_d: min_moves_per_diagram = args.min_p_d
src/diana/diana.py
Show inline comments
 
@@ -10,11 +10,11 @@ from .drawer.svg import Svg
 
from .drawer.tikz import Tikz
 

	
 

	
 
def collectMoves(root):
 
def collect_moves(root):
 
	node = root
 
	while len(node.children) > 0:
 
		b = node.getProp("B")
 
		w = node.getProp("W")
 
		b = node.get_prop("B")
 
		w = node.get_prop("W")
 
		if b is not None:
 
			yield ("b", b)
 
		elif w is not None:
 
@@ -25,46 +25,46 @@ def collectMoves(root):
 

	
 

	
 
class SourceFile:
 
	def __init__(self, fileName):
 
		self.fileName = fileName
 
		self._shortName = "".join(re.split(r'[/\\]', fileName)[-1].split('.')[:-1])
 
	def __init__(self, file_name):
 
		self.file_name = file_name
 
		self._short_name = "".join(re.split(r'[/\\]', file_name)[-1].split('.')[:-1])
 
		self._game = go.Go()
 

	
 
		with open(self.fileName, 'r', encoding=cfg.encoding) as f:
 
			games = Collection(f.read()).listGames()
 
		with open(self.file_name, 'r', encoding=cfg.encoding) as f:
 
			games = Collection(f.read()).list_games()
 
		self._record = list(games)[0]
 
		self._moves = list(collectMoves(self._record.root))
 
		self._moves = list(collect_moves(self._record.root))
 

	
 
	def process(self):
 
		print("{0}... ".format(self.fileName), end="")
 
		print("{0}... ".format(self.file_name), 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")
 
		for k in range(0, len(self._moves), cfg.moves_per_diagram):
 
			filename = os.path.join(cfg.output_dir, "{0}-{1}".format(self._short_name, i))
 
			self.create_diagram(k, k + cfg.moves_per_diagram).save(filename, "templ-pleb.svg")
 
			i += 1
 

	
 
		infoStr = """{GN}
 
		info_str = """{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')
 
		notes.write(infoStr)
 
{RE}""".format(**self.fetch_game_info(["GN", "PB", "BR", "PW", "WR", "DT", "RE"], ""))
 
		notes = open(os.path.join(cfg.output_dir, "{0}.txt".format(self._short_name)), 'w')
 
		notes.write(info_str)
 
		notes.close()
 
		print("done")
 

	
 
	def createDiagram(self, start, end):
 
	def create_diagram(self, start, end):
 
		# initialize the diagram
 
		template = Svg()
 

	
 
		self._setMove(start)
 
		self._set_move(start)
 

	
 
		# draw current state
 
		for lineNumber, line in enumerate(self._game.board):
 
			for itemNumber, item in enumerate(line):
 
		for (line_number, line) in enumerate(self._game.board):
 
			for (item_number, item) in enumerate(line):
 
				if item != EMPTY:
 
					template.addStone(itemNumber, lineNumber, "b" if item==BLACK else "w")
 
					template.add_stone(item_number, line_number, "b" if item == BLACK else "w")
 

	
 
		# draw the moves
 
		for k in range(start, end):
 
@@ -79,29 +79,29 @@ W: {PW} {WR}
 
				(c, r) = move
 

	
 
			if not self._move(color, c, r):
 
				if cfg.keepBroken:
 
				if cfg.keep_broken:
 
					continue
 
				else:
 
					return False
 

	
 
			# draw the move
 
			template.addMove(c, r, color, k+1)
 
			template.add_move(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 fetch_game_info(self, field_names, default=None):
 
		return {k: self._record.get(k, default) for k in field_names}
 

	
 
	def _setMove(self, k):
 
	def _set_move(self, k):
 
		self._game = go.Go()
 

	
 
		blackStones = self._record.root.getProp("AB")
 
		whiteStones = self._record.root.getProp("AW")
 
		if blackStones:
 
			for p in blackStones:
 
		black_stones = self._record.root.get_prop("AB")
 
		white_stones = self._record.root.get_prop("AW")
 
		if black_stones:
 
			for p in black_stones:
 
				self._game.board[p.r][p.c] = BLACK
 
		if whiteStones:
 
			for p in whiteStones:
 
		if white_stones:
 
			for p in white_stones:
 
				self._game.board[p.r][p.c] = WHITE
 

	
 
		for i in range(k):
 
@@ -113,8 +113,8 @@ W: {PW} {WR}
 
	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)
 
			if cfg.keepBroken:
 
			msg = "illegal move: {0} at {1},{2}".format(self._game.move_count + 1, c, r)
 
			if cfg.keep_broken:
 
				print(msg)
 
			else:
 
				msg += ". aborted"
 
@@ -124,9 +124,9 @@ W: {PW} {WR}
 

	
 

	
 
def main():
 
	cfg.parseArgs()
 
	cfg.parse_args()
 
	print("processing:")
 
	files = cfg.inputFiles[:]
 
	files = cfg.input_files[:]
 

	
 
	for item in files:
 
		if os.path.isfile(item):
src/diana/drawer/base.py
Show inline comments
 
@@ -16,31 +16,31 @@ class DiagramPoint:
 

	
 

	
 
class Base:
 
	highNumbers = True
 
	high_numbers = True
 

	
 
	def __init__(self, start=0):
 
		self.overlays = []
 
		self._letter = "a"
 

	
 
		self._index = dict()
 
		self._indexGen = count(start)
 
		self._index_gen = count(start)
 

	
 
		curDir = os.path.dirname(__file__)
 
		templateDir = os.path.join(curDir, "..", "templ")
 
		self._env = Environment(loader=FileSystemLoader(templateDir))
 
		cur_dir = os.path.dirname(__file__)
 
		template_dir = os.path.join(cur_dir, "..", "templ")
 
		self._env = Environment(loader=FileSystemLoader(template_dir))
 
		self._env.trim_blocks = True
 
		self._env.lstrip_blocks = True
 

	
 
	def addStone(self, x, y, color):
 
	def add_stone(self, x, y, color):
 
		assert (x, y) not in self._index
 
		self._index[(x, y)] = (next(self._indexGen), DiagramPoint(x, y, color))
 
		self._index[(x, y)] = (next(self._index_gen), DiagramPoint(x, y, color))
 

	
 
	def addMove(self, x, y, color, label):
 
		if (not self.highNumbers) and isinstance(label, int) and label%100 != 0:
 
	def add_move(self, x, y, color, label):
 
		if (not self.high_numbers) 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))
 
			self._index[(x, y)] = (next(self._index_gen), DiagramPoint(x, y, color, label))
 
		else:
 
			(_, point) = self._index[(x, y)]
 
			if not point.label:
 
@@ -48,8 +48,8 @@ class Base:
 
				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 add_label(self, x, y, label):
 
		self._index[(x, y)] = (next(self._index_gen), DiagramPoint(x, y, "", label))
 

	
 
	def save(self, filename):
 
		notes=open(filename+".txt", 'w')
src/diana/drawer/svg.py
Show inline comments
 
from .base import Base
 

	
 

	
 
def adjustFont(base,text):
 
def adjust_font(base, text):
 
	text = str(text)
 
	if len(text) < 2:
 
		return round(0.7*base)
 
@@ -15,14 +15,14 @@ class Svg(Base):
 
	extension = "svg"
 

	
 
	padding = 15
 
	highNumbers = True
 
	high_numbers = True
 
	
 
	def __init__(self, start=0):
 
		super().__init__(start)
 
		self.boardSize = 480
 
		self.padding = 30
 

	
 
	def render(self, templateName, bgcolor=""):
 
	def render(self, template_name, 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 == ""]
 
@@ -31,9 +31,9 @@ class Svg(Base):
 

	
 
		params = {
 
			"boardSize": self.boardSize, "padding": self.padding, "stones": stones, "moves": moves,
 
			"labels": labels, "adjustFont": adjustFont, "bgcolor": bgcolor}
 
			"labels": labels, "adjustFont": adjust_font, "bgcolor": bgcolor}
 

	
 
		return self._env.get_template(templateName).render(params)
 
		return self._env.get_template(template_name).render(params)
 

	
 
	def save(self, filename, template="templ.svg", bgcolor=""):
 
		file = open(filename+".svg", 'w')
src/diana/drawer/tikz.py
Show inline comments
 
@@ -3,7 +3,7 @@ class Tikz:
 
	footer = ""
 
	extension = "tex"
 

	
 
	highNumbers = True
 
	high_numbers = True
 

	
 
	def __init__(self):
 
		self.content = r'''\begin{tikzpicture}
 
@@ -23,17 +23,17 @@ class Tikz:
 
	def __str__(self):
 
		return self.content+self.footer
 

	
 
	def drawStone(self, x, y, color):
 
	def draw_stone(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):
 
	def draw_move(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_color = "white" if color == "b" else "black"
 
		if (not self.high_numbers) 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, label_color, label)+'\n'
 

	
 
	def getContent(self):
 
	def get_content(self):
 
		return self.content+self.footer
src/diana/go.py
Show inline comments
 
@@ -8,7 +8,8 @@ class Go:
 
	
 
	def __init__(self):
 
		self.board = [[EMPTY]*19 for i in range(19)]
 
		self.moveCount = 0
 
		self.move_count = 0
 
		self.temp = [[]]
 
	
 
	def move(self, color, y, x):
 
		if self.board[x][y] != EMPTY:
 
@@ -18,16 +19,16 @@ class Go:
 

	
 
		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):
 
			if not self._flood_fill(-color, x + i, y + j):
 
				self._remove()
 
		self.temp = [[False]*19 for i in range(19)]
 
		if not self._floodFill(color, x, y):
 
		if not self._flood_fill(color, x, y):
 
			self.board[x][y] = EMPTY
 
			return False
 
		self.moveCount += 1
 
		self.move_count += 1
 
		return True
 

	
 
	def _floodFill(self, color, x, y):
 
	def _flood_fill(self, color, x, y):
 
		if x < 0 or x > 18 or y < 0 or y > 18:
 
			return False
 
		if self.temp[x][y]:
 
@@ -38,10 +39,10 @@ class Go:
 
			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)
 
		return self._flood_fill(color, x - 1, y) or \
 
			self._flood_fill(color, x + 1, y) or \
 
			self._flood_fill(color, x, y - 1) or \
 
			self._flood_fill(color, x, y + 1)
 
	
 
	def _remove(self):
 
		for i in range(19):
src/diana/sgfParser/__init__.py
Show inline comments
 
def skipWhitespace(s, start):
 
def skip_whitespace(s, start):
 
	i = start
 
	while i < len(s) and s[i].isspace():
 
		i+=1
 
@@ -6,7 +6,7 @@ def skipWhitespace(s, start):
 
	return i
 

	
 

	
 
def strRowCol(s, i):
 
def str_row_col(s, i):
 
	k = 0
 
	(r, c) = (0, 0)
 
	for (r, line) in enumerate(s.splitlines(True)):
 
@@ -21,7 +21,7 @@ def strRowCol(s, i):
 
class ParserError(Exception):
 
	def __init__(self, msg, s, i):
 
		self.msg = msg
 
		(self.row, self.col) = strRowCol(s, i)
 
		(self.row, self.col) = str_row_col(s, i)
 
		self.context = s[i:i+16]
 

	
 
	def __str__(self):
src/diana/sgfParser/collection.py
Show inline comments
 
from .node import Node
 
from . import skipWhitespace, ParserError
 
from . import skip_whitespace, ParserError
 
from .gameRecord import GameRecord
 

	
 

	
 
class Collection:
 
	def __init__(self, s):
 
		self.gameTrees = []
 
		i = skipWhitespace(s, 0)
 
		self.game_trees = []
 
		i = skip_whitespace(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)
 
			self.game_trees.append(x)
 
		if i < len(s):
 
			raise ParserError("expected EOF", s, i)
 

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

	
 

	
 
@@ -37,7 +37,7 @@ class GameTree:
 
		assert GameTree.fits(s, start)
 
		res = GameTree()
 

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

	
 
@@ -46,48 +46,48 @@ class GameTree:
 
			(i, x) = Node.create(s, i)
 
			res.nodes.append(x)
 
			if y:
 
				y.addChild(x)
 
				y.add_child(x)
 
			x.parent = y
 
			y = x
 
			i = skipWhitespace(s, i)
 
			i = skip_whitespace(s, i)
 

	
 
		while GameTree.fits(s, i):
 
			(i, x) = GameTree.create(s, i)
 
			res.branches.append(x)
 
			subroot = x.getNode(0)
 
			subroot = x.get_node(0)
 
			subroot.parent = y
 
			if y:
 
				y.addChild(subroot)
 
			i = skipWhitespace(s, i)
 
				y.add_child(subroot)
 
			i = skip_whitespace(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)
 
		i = skip_whitespace(s, i + 1)
 
		return (i, res)
 

	
 
	## Expand multiple games into distinct GameTrees and yield each.
 
	def listGames(self):
 
	def list_games(self):
 
		if len(self.nodes) == 0:
 
			return None
 
		for node in self.nodes[0].listGINodes():
 
			yield GameRecord(self._buildSubtree(node))
 
		for node in self.nodes[0].list_gi_nodes():
 
			yield GameRecord(self._build_subtree(node))
 

	
 
	def getNode(self, i):
 
	def get_node(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()
 
		node.setChildren(seedNode.children)
 
		seedNode.children = []
 
	# Ancestor nodes are copied, descendants are moved from the seed_node.
 
	def _build_subtree(self, seed_node):
 
		node = seed_node.copy()
 
		node.set_children(seed_node.children)
 
		seed_node.children = []
 

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

	
 
		return node
src/diana/sgfParser/gameRecord.py
Show inline comments
 
@@ -5,13 +5,13 @@ from .node import Node
 
class GameRecord:
 
	def __init__(self, root=None):
 
		self.root = root or Node()
 
		self._gameInfoNode = next(root.listGINodes())
 
		self._game_info_node = next(root.list_gi_nodes())
 

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

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

	
 
	def get(self, name, default=None):
 
		return self._gameInfoNode.getProp(name, default)
 
		return self._game_info_node.get_prop(name, default)
src/diana/sgfParser/node.py
Show inline comments
 
from collections import deque
 
import logging as log
 

	
 
from . import skipWhitespace, ParserWarning
 
from . import skip_whitespace, ParserWarning
 
from .property import Property, GAME_INFO
 

	
 

	
 
@@ -20,65 +20,65 @@ class Node:
 
		assert Node.fits(s, start)
 
		res = Node()
 

	
 
		i = skipWhitespace(s, start+1)
 
		i = skip_whitespace(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))
 
			else:
 
				res.properties[x.name] = x
 
			i = skipWhitespace(s, i)
 
			i = skip_whitespace(s, i)
 
		return (i, res)
 

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

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

	
 
		queue = deque(self.children)
 
		while len(queue) > 0:
 
			node = queue.popleft()
 
			if node.isGINode():
 
			if node.is_gi_node():
 
				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
 

	
 
	def isGINode(self):
 
	def is_gi_node(self):
 
		return any(prop.type == GAME_INFO for prop in self.properties.values())
 

	
 
	def setProp(self, name, value):
 
	def set_prop(self, name, value):
 
		self.properties[name] = value
 
		# check value type
 

	
 
	def setChildren(self, children):
 
	def set_children(self, children):
 
		self.children = children
 
		for child in children:
 
			child.parent = self
 

	
 
	def addChild(self, node):
 
	def add_child(self, node):
 
		if node in self.children:
 
			return node
 
		node.parent = self
 
		self.children.append(node)
 
		return node
 

	
 
	def removeChild(self, node):
 
	def remove_child(self, node):
 
		if node not in self.children:
 
			return None
 
		del self.children[self.children.index(node)]
 
		node.parent = None
 
		return node
 

	
 
	def removeChildAt(self, i):
 
	def remove_child_at(self, i):
 
		if -len(self.children) < i < len(self.children):
 
			res = self.children[i]
 
			del self.children[i]
 
@@ -93,7 +93,7 @@ class Node:
 
		res.parent = self.parent
 
		return res
 

	
 
	def getProp(self, name, default=None):
 
	def get_prop(self, name, default=None):
 
		if name in self.properties:
 
			return self.properties[name].value
 
		else:
 
@@ -114,14 +114,14 @@ class Node:
 
				output.append("("*left)
 
			output.append(str(node)+"\n")
 

	
 
			childCount = len(node.children)
 
			if childCount == 0:  # a leaf
 
			child_count = len(node.children)
 
			if child_count == 0:  # a leaf
 
				output.append(")"*right+"\n")
 
			elif childCount == 1:  # a line
 
			elif child_count == 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])
 
				children = zip(node.children, [1]*child_count, [1]*(child_count-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
 
from . import ParserError, skip_whitespace
 

	
 

	
 
class Regexp:
 
@@ -8,14 +8,14 @@ class Regexp:
 
	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)
 
	composed_text = re.compile(r"(?:.*?[^\\])??(?:\\\\)*(?=]|:)", re.DOTALL)
 

	
 
	class Text:
 
		softBreaks = re.compile(r"(^|[^\\])((\\\\)*)\\((\n\r)|(\r\n)|\r|\n)")
 
		soft_breaks = 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"\\\\")
 
		simple_whitespace = re.compile(r"[\t\f\v\n\r]")
 
		remove_slashes = re.compile(r"(^|[^\\])((\\\\)*)\\($|[^\\])")
 
		unescape_slashes = re.compile(r"\\\\")
 

	
 

	
 
class Composed:
 
@@ -44,9 +44,9 @@ class Point:
 
## Metatype matching one of the provided types.
 
#
 
# Returns the first match, so the order is important.
 
def choose(*vTypes):
 
def choose(*v_types):
 
	def f(s, start):
 
		for vType in vTypes:
 
		for vType in v_types:
 
			try:
 
				(i, x) = vType(s, start)
 
				return (i, x)
 
@@ -56,50 +56,50 @@ def choose(*vTypes):
 
	return f
 

	
 

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

	
 

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

	
 

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

	
 

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

	
 

	
 
def compose(vTypeA, vTypeB):
 
def compose(v_type_a, v_type_b):
 
	def f(s, start):
 
		(i, a) = vTypeA(s, start)
 
		(i, a) = v_type_a(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)
 
		(i, b) = v_type_b(s, i + 1)
 
		return (i, Composed(a, b))
 
	return f
 

	
 
@@ -137,15 +137,15 @@ def color(s,start):
 
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)
 
		m = Regexp.composed_text.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
 
		res = regexps.soft_breaks.sub(r"\1\2", res)  # remove soft line breaks
 
		if simple:
 
			res = regexps.simpleWhitespace.sub(" ", res)  # convert whitespace to spaces, no escapes
 
			res = regexps.simple_whitespace.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.remove_slashes.sub(r"\1\2\4", res)
 
		res = regexps.unescape_slashes.sub(r"\\", res)  # unescape slashes
 

	
 
		return (m.end(), res)
 
	return f
src/diana/sgfParser/property.py
Show inline comments
 
@@ -2,8 +2,8 @@ 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
 
from .propValues import choose, singleton, list_of, compose, number, real, double, color, text, empty, anything, point, move, stone
 
from . import skip_whitespace, ParserError
 

	
 
GAME_INFO = 1
 
UNKNOWN = 99
 
@@ -14,7 +14,7 @@ class DateException(Exception):
 

	
 

	
 
class Property:
 
	identRegexp = re.compile(r"[A-Z]+")
 
	ident_regexp = re.compile(r"[A-Z]+")
 

	
 
	def __init__(self):
 
		self.name = ""
 
@@ -29,38 +29,38 @@ class Property:
 
		assert Property.fits(s, start)
 
		res = Property()
 
		(i, res.name) = Property.ident(s, start)
 
		i = skipWhitespace(s, i)
 
		i = skip_whitespace(s, i)
 
		try:
 
			(i, x) = Property.createValue(s, i, res.name)
 
			(i, x) = Property.create_value(s, i, res.name)
 
		except ParserError as e:  # a malformed value
 
			log.warning(e)
 
			(i, x) = choose(listOf(anything), singleton(anything))(s, i)
 
			(i, x) = choose(list_of(anything), singleton(anything))(s, i)
 
			res.name = "_"+res.name
 
		res.value = x
 
		if res.name == "DT":
 
			res = DateProperty(x)
 
		i = skipWhitespace(s, i)
 
		i = skip_whitespace(s, i)
 
		return (i, res)
 

	
 
	@staticmethod
 
	def ident(s, start):
 
		m = Property.identRegexp.match(s, start)
 
		m = Property.ident_regexp.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 create_value(s, start, name):
 
		if name in Property.patterns:
 
			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)
 
			return choose(list_of(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:
 
		game_info = {"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 game_info:
 
			return GAME_INFO
 
		else:
 
			return UNKNOWN
 
@@ -83,9 +83,9 @@ class Property:
 
		"KO": singleton(empty),
 
		"MN": singleton(number),
 
		"W": singleton(move),
 
		"AB": listOf(stone),  #
 
		"AE": listOf(point),  #
 
		"AW": listOf(stone),  #
 
		"AB": list_of(stone),  #
 
		"AE": list_of(point),  #
 
		"AW": list_of(stone),  #
 
		"PL": singleton(color),
 
		"C": singleton(text(simple=False)),
 
		"DM": singleton(double),
 
@@ -99,15 +99,15 @@ class Property:
 
		"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),  #
 
		"AR": list_of(compose(point, point)),  #
 
		"CR": list_of(point),  #
 
		"DD": list_of(point, allow_empty=True),  #
 
		"LB": list_of(compose(point, text())),  #
 
		"LN": list_of(compose(point, point)),  #
 
		"MA": list_of(point),  #
 
		"SL": list_of(point),  #
 
		"SQ": list_of(point),  #
 
		"TR": list_of(point),  #
 
		"AP": singleton(compose(text(composed=True), text())),  #
 
		"CA": singleton(text()),
 
		"FF": singleton(number),
 
@@ -141,13 +141,13 @@ class Property:
 
		"WL": singleton(real),
 
		"FG": choose(singleton(empty), singleton(compose(number, text()))),  #
 
		"PM": singleton(number),
 
		"VW": listOf(point, allowEmpty=True),  #
 
		"VW": list_of(point, allow_empty=True),  #
 

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

	
 

	
 
@@ -156,7 +156,7 @@ class DateProperty(Property):
 
		super().__init__()
 
		self.name = "DT"
 
		self.value = []
 
		self.rawValue = value
 
		self.raw_value = value
 
		self.parse(value)
 

	
 
	def parse(self, s):
 
@@ -165,21 +165,21 @@ class DateProperty(Property):
 
		if not match:
 
			raise DateException('Could not parse a DT value: "{0}"'.format(s))
 
		substr = match.group(0)
 
		dateStrs = substr.split(",")
 
		date_strs = substr.split(",")
 
		dates = []
 
		prevFormat = None
 
		prev_format = None
 

	
 
		for s in dateStrs:
 
		for s in date_strs:
 
			try:
 
				(prevFormat, d) = DateProperty.parseSingle(s, prevFormat, dates[-1] if dates else None)
 
				(prev_format, d) = DateProperty.parse_single(s, prev_format, 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
 

	
 
	@staticmethod
 
	def parseSingle(dateStr, prevFormat, prev=None):
 
		tokens = dateStr.split("-")
 
	def parse_single(date_str, prev_format, prev=None):
 
		tokens = date_str.split("-")
 
		num_tokens = list(map(int, tokens))
 
		if len(tokens) == 3:
 
			return ("YMD", date(*num_tokens))
 
@@ -191,7 +191,7 @@ class DateProperty(Property):
 
		else:
 
			if len(tokens[0]) == 4:
 
				return ("Y", date(*num_tokens, 1, 1))
 
			elif prevFormat in ("YM","M"):
 
			elif prev_format 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
 
@@ -4,7 +4,7 @@ import unittest
 
from unittest import TestCase
 
import os
 

	
 
from ..sgfParser import strRowCol
 
from ..sgfParser import str_row_col
 
from ..sgfParser.collection import Collection
 
from ..sgfParser.property import Property, DateProperty, DateException
 
from ..sgfParser.propValues import text, compose
 
@@ -25,7 +25,7 @@ class TestUtils(TestCase):
 
		]
 
		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))
 
			self.assertEqual(str_row_col(s, i), (r, c))
 

	
 

	
 
class TestProperty(TestCase):
 
@@ -57,15 +57,15 @@ ghi]"""
 

	
 
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))
 
		self.assertEqual(DateProperty.parse_single("2019", "Y")[1], date(2019, 1, 1))
 
		self.assertEqual(DateProperty.parse_single("2019-06", "YM")[1], date(2019, 6, 1))
 
		self.assertEqual(DateProperty.parse_single("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.parse_single("22", "D", d)[1], date(2019, 6, 22))
 
		self.assertEqual(DateProperty.parse_single("07-22", "MD", d)[1], date(2019, 7, 22))
 
		self.assertEqual(DateProperty.parse_single("2020-07-22", "YMD", d)[1], date(2020, 7, 22))
 
		with self.assertRaises(ValueError):
 
			DateProperty.parseSingle("2019-31", "YMD")
 
			DateProperty.parse_single("2019-31", "YMD")
 

	
 
	def testParse(self):
 
		self.assertEqual(DateProperty("1996-05,06").value, [date(1996, 5, 1), date(1996, 6, 1)])
 
@@ -85,7 +85,7 @@ class TestCollection(TestCase):
 
	(;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.list_games())
 

	
 
		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$")
0 comments (0 inline, 0 general)