diff --git a/pysollib/app.py b/pysollib/app.py index 380b9b6d..d0b7ef88 100644 --- a/pysollib/app.py +++ b/pysollib/app.py @@ -33,6 +33,7 @@ from pysollib.actions import PysolMenubar from pysollib.actions import PysolToolbar from pysollib.app_stat_result import GameStatResult from pysollib.app_statistics import Statistics +from pysollib.cardsetparser import read_cardset_config from pysollib.gamedb import GAME_DB, GI, loadGame from pysollib.help import destroy_help_html, help_about from pysollib.images import Images, SubsampledImages @@ -53,7 +54,7 @@ from pysollib.pysoltk import SelectCardsetDialogWithPreview from pysollib.pysoltk import SelectDialogTreeData from pysollib.pysoltk import destroy_find_card_dialog from pysollib.pysoltk import loadImage, wm_withdraw -from pysollib.resource import CSI, Cardset, CardsetConfig, CardsetManager +from pysollib.resource import CSI, CardsetManager from pysollib.resource import Music, MusicManager from pysollib.resource import Sample, SampleManager from pysollib.resource import Tile, TileManager @@ -1070,125 +1071,14 @@ Please select a %(correct_type)s type cardset. # read & parse a cardset config.txt file - see class Cardset in resource.py def _readCardsetConfig(self, dirname, filename): - with open(filename, "r") as f: - lines = f.readlines() - lines = [line.strip() for line in lines] - if not lines[0].startswith("PySol"): - return None - config = CardsetConfig() - if not self._parseCardsetConfig(config, lines): - # print filename, 'invalid config' - return None - if config.CARDD > self.top.winfo_screendepth(): - return None - cs = Cardset() - cs.dir = dirname - cs.update(config.__dict__) - return cs - - def _parseCardsetConfig(self, cs, line): - def perr(line, field=None, msg=''): - if not DEBUG: - return - if field: - print_err('_parseCardsetConfig error: line #%d, field #%d %s' - % (line, field, msg)) - else: - print_err('_parseCardsetConfig error: line #%d: %s' - % (line, msg)) - if len(line) < 6: - perr(1, msg='number of lines') - return 0 - # line[0]: magic identifier, possible version information - fields = [f.strip() for f in line[0].split(';')] - if len(fields) >= 2: - m = re.search(r"^(\d+)$", fields[1]) - if m: - cs.version = int(m.group(1)) - if cs.version >= 3: - if len(fields) < 5: - perr(1, msg='number of fields') - return 0 - cs.ext = fields[2] - m = re.search(r"^(\d+)$", fields[3]) - if not m: - perr(1, 3, 'not integer') - return 0 - cs.type = int(m.group(1)) - m = re.search(r"^(\d+)$", fields[4]) - if not m: - perr(1, 4, 'not integer') - return 0 - cs.ncards = int(m.group(1)) - if cs.version >= 4: - if len(fields) < 6: - perr(1, msg='number of fields') - return 0 - styles = fields[5].split(",") - for s in styles: - m = re.search(r"^\s*(\d+)\s*$", s) - if not m: - perr(1, 5, 'not integer') - return 0 - s = int(m.group(1)) - if s not in cs.styles: - cs.styles.append(s) - if cs.version >= 5: - if len(fields) < 7: - perr(1, msg='number of fields') - return 0 - m = re.search(r"^(\d+)$", fields[6]) - if not m: - perr(1, 6, 'not integer') - return 0 - cs.year = int(m.group(1)) - if len(cs.ext) < 2 or cs.ext[0] != ".": - perr(1, msg='invalid extention') - return 0 - # line[1]: identifier/name - if not line[1]: - perr(2, msg='empty line') - return 0 - cs.ident = line[1] - m = re.search(r"^(.*;)?([^;]+)$", cs.ident) - if not m: - perr(2, msg='invalid format') - return 0 - cs.name = m.group(2).strip() - # line[2]: CARDW, CARDH, CARDD - m = re.search(r"^(\d+)\s+(\d+)\s+(\d+)", line[2]) - if not m: - perr(3, msg='invalid format') - return 0 - cs.CARDW, cs.CARDH, cs.CARDD = \ - int(m.group(1)), int(m.group(2)), int(m.group(3)) - # line[3]: CARD_UP_YOFFSET, CARD_DOWN_YOFFSET, - # SHADOW_XOFFSET, SHADOW_YOFFSET - m = re.search(r"^(\d+)\s+(\d+)\s+(\d+)\s+(\d+)", line[3]) - if not m: - perr(4, msg='invalid format') - return 0 - cs.CARD_XOFFSET = int(m.group(1)) - cs.CARD_YOFFSET = int(m.group(2)) - cs.SHADOW_XOFFSET = int(m.group(3)) - cs.SHADOW_YOFFSET = int(m.group(4)) - # line[4]: default background - back = line[4] - if not back: - perr(5, msg='empty line') - return 0 - # line[5]: all available backgrounds - cs.backnames = [f.strip() for f in line[5].split(';')] - if back in cs.backnames: - cs.backindex = cs.backnames.index(back) - else: - cs.backnames.insert(0, back) - cs.backindex = 0 + cs = read_cardset_config(dirname, filename) # set offsets from options.cfg if cs.ident in self.opt.offsets: cs.CARD_XOFFSET, cs.CARD_YOFFSET = self.opt.offsets[cs.ident] - # if cs.type != 1: print cs.type, cs.name - return 1 + + if cs.CARDD > self.top.winfo_screendepth(): + return None + return cs def initCardsets(self): manager = self.cardset_manager @@ -1232,9 +1122,8 @@ Please select a %(correct_type)s type cardset. # print '+', cs.name fnames[cs.name] = 1 else: - print_err('fail _readCardsetConfig: %s %s' - % (d, f)) - pass + print_err('failed to parse cardset file: %s' + % f) except Exception: # traceback.print_exc() pass diff --git a/pysollib/cardsetparser.py b/pysollib/cardsetparser.py new file mode 100644 index 00000000..65a698f1 --- /dev/null +++ b/pysollib/cardsetparser.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python +# -*- mode: python; coding: utf-8; -*- +# ---------------------------------------------------------------------------## +# +# Copyright (C) 1998-2003 Markus Franz Xaver Johannes Oberhumer +# Copyright (C) 2003 Mt. Hood Playing Card Co. +# Copyright (C) 2005-2009 Skomoroh +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# ---------------------------------------------------------------------------## + +import re + +from pysollib.mfxutil import print_err +from pysollib.resource import Cardset, CardsetConfig + + +def read_cardset_config(dirname, filename): + """Parse a cardset config file and produce a Cardset object. + + This function returns None if any errors occurred. + """ + with open(filename, "r") as f: + lines = f.readlines() + lines = [line.strip() for line in lines] + if not lines[0].startswith("PySol"): + return None + config = parse_cardset_config(lines) + if not config: + print_err('invalid cardset: %s' % filename) + return None + cs = Cardset() + cs.dir = dirname + cs.update(config.__dict__) + return cs + + +def parse_cardset_config(line): + def perr(line_no, field=None, msg=''): + if field: + print_err('cannot parse cardset config: line #%d, field #%d: %s' + % (line_no, field, msg)) + else: + print_err('cannot parse cardset config: line #%d: %s' + % (line_no, msg)) + + cs = CardsetConfig() + if len(line) < 6: + perr(1, msg='not enough lines in file') + return None + # line[0]: magic identifier, possible version information + fields = [f.strip() for f in line[0].split(';')] + if len(fields) >= 2: + m = re.search(r"^(\d+)$", fields[1]) + if m: + cs.version = int(m.group(1)) + if cs.version >= 3: + if len(fields) < 5: + perr(1, msg='not enough fields') + return None + cs.ext = fields[2] + m = re.search(r"^(\d+)$", fields[3]) + if not m: + perr(1, 3, 'not integer') + return None + cs.type = int(m.group(1)) + m = re.search(r"^(\d+)$", fields[4]) + if not m: + perr(1, 4, 'not integer') + return None + cs.ncards = int(m.group(1)) + if cs.version >= 4: + if len(fields) < 6: + perr(1, msg='not enough fields') + return None + styles = fields[5].split(",") + for s in styles: + m = re.search(r"^\s*(\d+)\s*$", s) + if not m: + perr(1, 5, 'not integer') + return None + s = int(m.group(1)) + if s not in cs.styles: + cs.styles.append(s) + if cs.version >= 5: + if len(fields) < 7: + perr(1, msg='not enough fields') + return None + m = re.search(r"^(\d+)$", fields[6]) + if not m: + perr(1, 6, 'not integer') + return None + cs.year = int(m.group(1)) + if len(cs.ext) < 2 or cs.ext[0] != ".": + perr(1, msg='specifies an invalid file extension') + return None + # line[1]: identifier/name + if not line[1]: + perr(2, msg='unexpected empty line') + return None + cs.ident = line[1] + m = re.search(r"^(.*;)?([^;]+)$", cs.ident) + if not m: + perr(2, msg='invalid format') + return None + cs.name = m.group(2).strip() + # line[2]: CARDW, CARDH, CARDD + m = re.search(r"^(\d+)\s+(\d+)\s+(\d+)", line[2]) + if not m: + perr(3, msg='invalid format') + return None + cs.CARDW, cs.CARDH, cs.CARDD = \ + int(m.group(1)), int(m.group(2)), int(m.group(3)) + # line[3]: CARD_UP_YOFFSET, CARD_DOWN_YOFFSET, + # SHADOW_XOFFSET, SHADOW_YOFFSET + m = re.search(r"^(\d+)\s+(\d+)\s+(\d+)\s+(\d+)", line[3]) + if not m: + perr(4, msg='invalid format') + return None + cs.CARD_XOFFSET = int(m.group(1)) + cs.CARD_YOFFSET = int(m.group(2)) + cs.SHADOW_XOFFSET = int(m.group(3)) + cs.SHADOW_YOFFSET = int(m.group(4)) + # line[4]: default background + back = line[4] + if not back: + perr(5, msg='unexpected empty line') + return None + # line[5]: all available backgrounds + cs.backnames = [f.strip() for f in line[5].split(';')] + if back in cs.backnames: + cs.backindex = cs.backnames.index(back) + else: + cs.backnames.insert(0, back) + cs.backindex = 0 + + # if cs.type != 1: print cs.type, cs.name + return cs diff --git a/scripts/gen_individual_importing_tests.py b/scripts/gen_individual_importing_tests.py index ed425d44..a4d529ba 100644 --- a/scripts/gen_individual_importing_tests.py +++ b/scripts/gen_individual_importing_tests.py @@ -44,6 +44,7 @@ print('ok 1 - imported') for ver in PY_VERS: for mod in [ 'pysol_tests.acard_unit', + 'pysol_tests.cardsetparser', 'pysol_tests.game_drag', 'pysol_tests.hint', 'pysol_tests.import_file1', diff --git a/tests/lib/pysol_tests/cardsetparser.py b/tests/lib/pysol_tests/cardsetparser.py new file mode 100644 index 00000000..1c506f9c --- /dev/null +++ b/tests/lib/pysol_tests/cardsetparser.py @@ -0,0 +1,62 @@ +# Written by Juhani Numminen, under the MIT Expat License. + +import unittest + +from pysollib.cardsetparser import parse_cardset_config +from pysollib.resource import CSI, CardsetConfig + + +class MyTests(unittest.TestCase): + + def _assertCcEqual(self, a, b, msg=None): + """Assert that CardsetConfig objects a and b have equal attributes.""" + return self.assertDictEqual(a.__dict__, b.__dict__, msg) + + def test_good_cardset(self): + config_txt = """\ +PySolFC solitaire cardset;4;.gif;1;52;7 +123-dondorf;Dondorf +79 123 8 +16 25 7 7 +back01.gif +back01.gif +""" + + reference = CardsetConfig() + reference.update(dict( + version=4, + ext='.gif', + type=CSI.TYPE_FRENCH, + ncards=52, + styles=[7], + ident='123-dondorf;Dondorf', + name='Dondorf', + CARDW=79, + CARDH=123, + CARDD=8, + CARD_XOFFSET=16, + CARD_YOFFSET=25, + SHADOW_XOFFSET=7, + SHADOW_YOFFSET=7, + backindex=0, + backnames=['back01.gif'], + )) + self._assertCcEqual( + parse_cardset_config(config_txt.split('\n')), + reference, + 'parse_cardset_config should parse well-formed v4 config.txt ' + + 'correctly') + + def test_reject_too_few_fields(self): + config_txt = """\ +PySolFC solitaire cardset;4;.gif;1;52 +123-dondorf;Dondorf +79 123 8 +16 25 7 7 +back01.gif +back01.gif +""" + self.assertIsNone( + parse_cardset_config(config_txt.split('\n')), + 'parse_cardset_config should reject v4 config.txt with ' + + 'a missing field on the first line')