Changed class variables to lowercase; added offset argument to all read() functions to make result.read() less complex; moved to bitbucket.org
5 files changed, 95 insertions(+), 92 deletions(-)

M common.py
M fixed.py
M player.py
M result.py
M turn.py
M common.py +28 -25
@@ 9,16 9,16 @@ SUBVERSION = ('00', '01',)
 
 class PlanetsData(object):
     """Base class for all Planets data objects"""
-    FILENAME = None         # Default filename
-    COUNT = None            # Default number of objects in a data file
-    PACK_LENGTH = None      # Length of binary data for single object
-    PACK_FORMAT = None      # Format of binary data for single object
-    FIELDS = ()             # Fields for single object
+    filename = None         # Default filename
+    count = None            # Default number of objects in a data file
+    pack_length = None      # Length of binary data for single object
+    pack_format = None      # Format of binary data for single object
+    fields = ()             # Fields for single object
 
     def __init__(self):
         # Expand field list where necessary (in case of dictionary fields)
         fields = []
-        for field in self.FIELDS:
+        for field in self.fields:
             # tuple format: ('name', start, end,) start/end must be 'int'
             if isinstance(field, tuple) and (len(field) == 3):
                 for i in range(field[1], field[1]+field[2]):

          
@@ 26,10 26,10 @@ class PlanetsData(object):
             else:
                 fields.append(field)
         fields = tuple(fields)
-        if self.__class__.FIELDS is not fields:
-            self.__class__.FIELDS = fields
+        if self.__class__.fields is not fields:
+            self.__class__.fields = fields
         # Initialize fields
-        for field in self.FIELDS:
+        for field in self.fields:
             # Dictionary field: see EngSpec class in fixed.py for an example
             if isinstance(field, tuple):
                 setattr(self, str(field[0])+'['+str(field[1])+']', None)

          
@@ 39,12 39,12 @@ class PlanetsData(object):
     
     def unpack(self, data):
         """Unpacks binary data into class instance variables"""
-        if len(data) is not self.PACK_LENGTH:
+        if len(data) is not self.pack_length:
             raise ValueError('Binary data passed has incorrect length')
-        values = struct.unpack(self.PACK_FORMAT, data)
-        if len(self.FIELDS) is not len(values):
+        values = struct.unpack(self.pack_format, data)
+        if len(self.fields) is not len(values):
             raise ValueError('Incorrect number of variables in pack format')
-        for name, value in zip(self.FIELDS, values):
+        for name, value in zip(self.fields, values):
             # Dictionary field: see EngSpec class in fixed.py for an example
             if isinstance(name, tuple):
                 setattr(self, str(name[0])+'['+str(name[1])+']', value)

          
@@ 64,14 64,14 @@ class PlanetsData(object):
         """Unpacks binary data into a list of object instances"""
         result = {}
         if count is None:
-            if cls.COUNT is not None:
-                count = cls.COUNT
-            elif len(data) >= cls.PACK_LENGTH:
-                count = int(len(data) / cls.PACK_LENGTH)
+            if cls.count is not None:
+                count = cls.count
+            elif len(data) >= cls.pack_length:
+                count = int(len(data) / cls.pack_length)
         for i in range(1, count+1): # For compatibility, count from 1 and not 0
             instance = cls()
-            start = cls.PACK_LENGTH*(i-1)
-            end = cls.PACK_LENGTH*i
+            start = cls.pack_length*(i-1)
+            end = cls.pack_length*i
             instance.unpack(data[start:end])
             if instance.id is None:
                 instance.id = i

          
@@ 81,16 81,19 @@ class PlanetsData(object):
         return result
     
     @classmethod
-    def read(cls, f, has_count=False, count=None):
+    def read(cls, f, offset=None, has_count=False, count=None):
         """Reads from file and loads contents in list of object instances"""
+        # Seek to offset if set. Not recommended if has_count & count are None
+        if offset is not None:
+            f.seek(offset)
         # Data may be preceeded by a WORD indicating how much objects follow
         if has_count:
             (count,) = struct.unpack('< h', f.read(2))
-        # Load "count" objects or predetermined "COUNT" objects or whole file
+        # Load "count" objects or predetermined "count" objects or whole file
         if count is not None:
-            data = f.read(cls.PACK_LENGTH * count)
-        elif cls.COUNT is not None:
-            data = f.read(cls.PACK_LENGTH * cls.COUNT)
+            data = f.read(cls.pack_length * count)
+        elif cls.count is not None:
+            data = f.read(cls.pack_length * cls.count)
         else:
             data = f.read()
         result = cls.load(data, count)

          
@@ 103,7 106,7 @@ class PlanetsData(object):
     def open(cls, name, has_count=False, count=None):
         """Opens file and loads contents in list of object instances"""
         f = open(name,'rb')
-        result = cls.read(f, has_count, count)
+        result = cls.read(f, offset=None, has_count=has_count, count=count)
         f.close()
         return result
 

          
M fixed.py +20 -20
@@ 7,11 7,11 @@ from common import PlanetsData
 
 class BeamSpec(PlanetsData):
     """List of starship beam weapons"""
-    FILENAME = 'BEAMSPEC.DAT'
-    COUNT = 10
-    PACK_LENGTH = 36
-    PACK_FORMAT = '< 20s h h h h h h h h'
-    FIELDS = ('name',
+    filename = 'BEAMSPEC.DAT'
+    count = 10
+    pack_length = 36
+    pack_format = '< 20s h h h h h h h h'
+    fields = ('name',
               'cost',
               'tritanium',
               'duranium',

          
@@ 23,11 23,11 @@ class BeamSpec(PlanetsData):
     
 class EngSpec(PlanetsData):
     """List of starship engines"""
-    FILENAME = 'ENGSPEC.DAT'
-    COUNT = 9
-    PACK_LENGTH = 66
-    PACK_FORMAT = '< 20s h h h h h 9i'
-    FIELDS = ('name',
+    filename = 'ENGSPEC.DAT'
+    count = 9
+    pack_length = 66
+    pack_format = '< 20s h h h h h 9i'
+    fields = ('name',
               'cost',
               'tritanium',
               'duranium',

          
@@ 37,11 37,11 @@ class EngSpec(PlanetsData):
         
 class HullSpec(PlanetsData):
     """List of starship hulls"""
-    FILENAME = 'HULLSPEC.DAT'
-    # No COUNT, but typically 105 objects in default HULLSPEC.DAT
-    PACK_LENGTH = 60
-    PACK_FORMAT = '< 30s h h h h h h h h h h h h h h h'
-    FIELDS = ('name',
+    filename = 'HULLSPEC.DAT'
+    # No count, but typically 105 objects in default HULLSPEC.DAT
+    pack_length = 60
+    pack_format = '< 30s h h h h h h h h h h h h h h h'
+    fields = ('name',
               'picture',
               'damaged', # Unused field for picture of damaged ship
               'tritanium',

          
@@ 60,11 60,11 @@ class HullSpec(PlanetsData):
 
 class TorpSpec(PlanetsData):
     """List of starship torpedo weapons"""
-    FILENAME = 'TORPSPEC.DAT'
-    COUNT = 10
-    PACK_LENGTH = 38
-    PACK_FORMAT = '< 20s h h h h h h h h h'
-    FIELDS = ('name',
+    filename = 'TORPSPEC.DAT'
+    count = 10
+    pack_length = 38
+    pack_format = '< 20s h h h h h h h h h'
+    fields = ('name',
               'torpedo_cost',
               'launcher_cost',
               'tritanium',

          
M player.py +37 -31
@@ 11,11 11,11 @@ from common import PlanetsData
 
 class Base(PlanetsData):
     """List of player's starbases"""
-    # No FILENAME, but typically: BDATAx.DAT or BDATAx.DIS (x=player)
-    # No COUNT, but obviously limited by number of planets
-    PACK_LENGTH = 156
-    PACK_FORMAT = '< h h h h h h h h 9h 20h 10h 10h 10h h h h h h h h h h h h'
-    FIELDS = ('id',
+    # No filename, but typically: BDATAx.DAT or BDATAx.DIS (x=player)
+    # No count, but obviously limited by number of planets
+    pack_length = 156
+    pack_format = '< h h h h h h h h 9h 20h 10h 10h 10h h h h h h h h h h h h'
+    fields = ('id',
               'owner',
               'defense',
               'damage',

          
@@ 42,11 42,11 @@ class Base(PlanetsData):
     
 class Planet(PlanetsData):
     """List of player's planets"""
-    # No FILENAME, but typically: PDATAx.DAT or PDATAx.DIS (x=player)
-    # No COUNT, but maximum 500
-    PACK_LENGTH = 85
-    PACK_FORMAT = '<hh 3s hhh iiiiiiiiiii hhhhhhhhh i hhh'
-    FIELDS = ('owner',
+    # No filename, but typically: PDATAx.DAT or PDATAx.DIS (x=player)
+    # No count, but maximum 500
+    pack_length = 85
+    pack_format = '<hh 3s hhh iiiiiiiiiii hhhhhhhhh i hhh'
+    fields = ('owner',
               'id',
               'friendlycode',
               'mines',

          
@@ 84,11 84,11 @@ class Planet(PlanetsData):
     
 class Ship(PlanetsData):
     """List of player's ships"""
-    # No FILENAME, but typically: SHIPx.DAT or SHIPx.DIS (x=player)
-    # No COUNT, but normally maximum 500 and absolute max of 999 (host999)
-    PACK_LENGTH = 107
-    PACK_FORMAT = '<hh 3s hhhhhhhhhhhhhhhhhhh 20s hhhhhhhhhhhhhhhhhhhhh'
-    FIELDS = ('id',
+    # No filename, but typically: SHIPx.DAT or SHIPx.DIS (x=player)
+    # No count, but normally maximum 500 and absolute max of 999 (host999)
+    pack_length = 107
+    pack_format = '<hh 3s hhhhhhhhhhhhhhhhhhh 20s hhhhhhhhhhhhhhhhhhhhh'
+    fields = ('id',
               'owner',
               'friendlycode',
               'warp',

          
@@ 135,11 135,11 @@ class Ship(PlanetsData):
 
 class ShipXY(PlanetsData):
     """List of ship coordinates"""
-    # No FILENAME, byt typically: SHIPXYx.DAT (x=player)
-    COUNT = 500 # Not compatible with host999
-    PACK_LENGTH = 8
-    PACK_FORMAT = '< h h h h'
-    FIELDS = ('x',
+    # No filename, byt typically: SHIPXYx.DAT (x=player)
+    count = 500 # Not compatible with host999
+    pack_length = 8
+    pack_format = '< h h h h'
+    fields = ('x',
               'y',
               'owner',
               'mass')

          
@@ 155,11 155,11 @@ class ShipXY(PlanetsData):
 
 class Target(PlanetsData):
     """List of of visual enemy ships (contacts)"""
-    # No FILENAME, but typically: TARGETx.DAT (x=player)
-    # No COUNT, but obviously limited by number of ships
-    PACK_LENGTH = 34
-    PACK_FORMAT = '< h h h h h h h 20s'
-    FIELDS = ('id',
+    # No filename, but typically: TARGETx.DAT (x=player)
+    # No count, but obviously limited by number of ships
+    pack_length = 34
+    pack_format = '< h h h h h h h 20s'
+    fields = ('id',
               'owner',
               'warp',
               'x',

          
@@ 176,16 176,18 @@ class Target(PlanetsData):
         self.name = name
 
     @classmethod
-    def read_result(cls, f, extra_offset=None):
+    def read_result(cls, f, offset=None, extra_offset=None):
         """Reads visual enemy ship data from a RST file"""
+        if offset is not None:
+            f.seek(offset)
         (count,) = struct.unpack('< h', f.read(2))
-        data = f.read(cls.PACK_LENGTH * count)
+        data = f.read(cls.pack_length * count)
         result = cls.load(data, count)
         # Load extra data if present in WinPlay-style RST file
-        if extra_offset:
+        if extra_offset is not None:
             f.seek(extra_offset)
             (count,) = struct.unpack('< i', f.read(4))
-            data = f.read(cls.PACK_LENGTH * count)
+            data = f.read(cls.pack_length * count)
             extra = cls.load(data, count)
             # Extra data has encrypted ship name
             for k in extra.keys():

          
@@ 197,9 199,11 @@ class Target(PlanetsData):
             result[key].fix()
         return result
 
-def read_general(f, from_result=True):
+def read_general(f, offset=None, from_result=True):
     """Reads general turn data found in RST and GENx.DAT files"""
     result = {}
+    if offset is not None:
+        f.seek(offset)
     # Timestamp
     (result['timestamp_date'], result['timestamp_time'], ) = struct.unpack(
         '< 10s 8s', f.read(18))

          
@@ 235,9 239,11 @@ def read_general(f, from_result=True):
                                                                      f.read(4))
     return result
 
-def read_messages(f):
+def read_messages(f, offset=None):
     """Reads and returns list of messages found in RST and MDATAx.DAT files"""
     result = {}
+    if offset is not None:
+        f.seek(offset)
     # Message count should preceed messages
     (count,) = struct.unpack('< h', f.read(2))
     # Get pointer + size of each message (if count > 0)

          
M result.py +9 -15
@@ 18,21 18,15 @@ def read(f, filesize):
         raise InvalidResultFile('RST file size zero or not defined')
     pointers = _get_sections(f, filesize)
     # Read standard sections (should always exist, even if 0 items in section)
-    f.seek(pointers['ships'])
-    result['ships'] = Ship.read(f, has_count=True)
-    f.seek(pointers['targets'])
-    result['targets'] = Target.read_result(f,
-                            extra_offset=pointers['extratargets'])
-    f.seek(pointers['planets'])
-    result['planets'] = Planet.read(f, has_count=True)
-    f.seek(pointers['bases'])
-    result['bases'] = Base.read(f, has_count=True)
-    f.seek(pointers['messages'])
-    result['messages'] = read_messages(f)
-    f.seek(pointers['shipxy'])
-    result['shipxy'] = ShipXY.read(f)
-    f.seek(pointers['general'])
-    result['general'] = read_general(f)
+    result['ships'] = Ship.read(f, offset=pointers['ships'], has_count=True)
+    result['targets'] = Target.read_result(
+        f, offset=pointers['targets'], extra_offset=pointers['extratargets'])
+    result['planets'] = Planet.read(
+        f, offset=pointers['planets'], has_count=True)
+    result['bases'] = Base.read(f, offset=pointers['bases'], has_count=True)
+    result['messages'] = read_messages(f, offset=pointers['messages'])
+    result['shipxy'] = ShipXY.read(f, offset=pointers['shipxy'])
+    result['general'] = read_general(f, offset=pointers['general'])
     return result
     
 def _valid_pointer(pointer, filesize, datasize):

          
M turn.py +1 -1
@@ 7,7 7,7 @@ import random
 from common import SIGNATURE, SUBVERSION, get_checksum
 
 CLIENT = 'VGA Planets v3.x library for Python'
-URL = 'http://github.com/ghdpro/planets'
+URL = 'https://bitbucket.org/ghdpro/planets'
 
 # VPH35.DLL magic numbers, taken from PCC v2's "trn.cc" source file
 VPH_MAGIC = (1585242373,