Implement the folder records and filesystem reconstruction.
authorW. Trevor King <wking@tremily.us>
Thu, 19 Jul 2012 13:30:28 +0000 (09:30 -0400)
committerW. Trevor King <wking@tremily.us>
Thu, 19 Jul 2012 14:51:02 +0000 (10:51 -0400)
igor/packed.py
igor/record/base.py
igor/record/folder.py
test/test.py

index 48f933e086c49e39a48e8e5417da290feefdd03a..10bf2141e34f63e73a9cf4c6fc862e0210f8ec53 100644 (file)
@@ -9,8 +9,13 @@ from .util import need_to_reorder_bytes as _need_to_reorder_bytes
 from .record import RECORD_TYPE as _RECORD_TYPE
 from .record.base import UnknownRecord as _UnknownRecord
 from .record.base import UnusedRecord as _UnusedRecord
+from .record.folder import FolderStartRecord as _FolderStartRecord
+from .record.folder import FolderEndRecord as _FolderEndRecord
+from .record.variables import VariablesRecord as _VariablesRecord
+from .record.wave import WaveRecord as _WaveRecord
 
 
+# From PTN003:
 # Igor writes other kinds of records in a packed experiment file, for
 # storing things like pictures, page setup records, and miscellaneous
 # settings.  The format for these records is quite complex and is not
@@ -66,5 +71,59 @@ def load(filename, strict=True, ignore_unknown=True):
         if not hasattr(filename, 'read'):
             f.close()
 
-    return records
+    # From PTN003:
+    """The name must be a valid Igor data folder name. See Object
+    Names in the Igor Reference help file for name rules.
+
+    When Igor Pro reads the data folder start record, it creates a new
+    data folder with the specified name. Any subsequent variable, wave
+    or data folder start records cause Igor to create data objects in
+    this new data folder, until Igor Pro reads a corresponding data
+    folder end record."""
+    # From the Igor Manual, chapter 2, section 8, page II-123
+    # http://www.wavemetrics.net/doc/igorman/II-08%20Data%20Folders.pdf
+    """Like the Macintosh file system, Igor Pro's data folders use the
+    colon character (:) to separate components of a path to an
+    object. This is analogous to Unix which uses / and Windows which
+    uses \. (Reminder: Igor's data folders exist wholly in memory
+    while an experiment is open. It is not a disk file system!)
+
+    A data folder named "root" always exists and contains all other
+    data folders.
+    """
+    # From the Igor Manual, chapter 4, page IV-2
+    # http://www.wavemetrics.net/doc/igorman/IV-01%20Commands.pdf
+    """For waves and data folders only, you can also use "liberal"
+    names. Liberal names can include almost any character, including
+    spaces and dots (see Liberal Object Names on page III-415 for
+    details).
+    """
+    # From the Igor Manual, chapter 3, section 16, page III-416
+    # http://www.wavemetrics.net/doc/igorman/III-16%20Miscellany.pdf
+    """Liberal names have the same rules as standard names except you
+    may use any character except control characters and the following:
+
+      " ' : ;
+    """
+    filesystem = {'root': {}}
+    dir_stack = [('root', filesystem['root'])]
+    for record in records:
+        cwd = dir_stack[-1][-1]
+        if isinstance(record, _FolderStartRecord):
+            name = record.null_terminated_text
+            cwd[name] = {}
+            dir_stack.append((name, cwd[name]))
+        elif isinstance(record, _FolderEndRecord):
+            dir_stack.pop()
+        elif isinstance(record, (_VariablesRecord, _WaveRecord)):
+            if isinstance(record, _VariablesRecord):
+                filename = ':variables'  # start with an invalid character
+            else:                        # to avoid collisions with folder
+                filename = ':waves'      # names
+            if filename in cwd:
+                cwd[filename].append(record)
+            else:
+                cwd[filename] = [record]
+
+    return (records, filesystem)
 
index 454e7396fbc2202f4d53a917f2ccc0d41b1a6bb3..53abe24392fba1d9221cae43134b86e866028dca 100644 (file)
@@ -33,3 +33,4 @@ class TextRecord (Record):
     def __init__(self, *args, **kwargs):
         super(TextRecord, self).__init__(*args, **kwargs)
         self.text = str(self.data).replace('\r\n', '\n').replace('\r', '\n')
+        self.null_terminated_text = self.text.split('\x00', 1)[0]
index b03e283a485ea9b4fdd3194f957cc783ce9d4b03..be98c585a5ef9a9e0f14455be91edd32b309f7f7 100644 (file)
@@ -1,11 +1,11 @@
 # Copyright
 
-from .base import Record
+from .base import TextRecord
 
 
-class FolderStartRecord (Record):
+class FolderStartRecord (TextRecord):
     pass
 
 
-class FolderEndRecord (Record):
+class FolderEndRecord (TextRecord):
     pass
index 25a44ebf94b9f2efdddae5cff76a3e87a6ea3c9c..bf8d6deab26a9509f9cdb642e399637d073f6826 100644 (file)
@@ -1248,9 +1248,9 @@ record 39:
   'whpad3': 0,
   'whpad4': 0})
 record 40:
-<FolderStartRecord ...>
+'Packages'
 record 41:
-<FolderStartRecord ...>
+'WMDataBase'
 record 42:
 {'header': {'numSysVars': 21,
             'numUserStrs': 6,
@@ -1267,9 +1267,9 @@ record 42:
               'u_str': '2'},
  'userVars': {}}
 record 43:
-<FolderEndRecord ...>
+''
 record 44:
-<FolderStartRecord ...>
+'PolarGraphs'
 record 45:
 {'header': {'numSysVars': 21,
             'numUserStrs': 10,
@@ -1317,15 +1317,28 @@ record 45:
               'u_y1': 42.732577139459856,
               'u_y2': 45.081649278814126}}
 record 46:
-<FolderEndRecord ...>
+''
 record 47:
-<FolderEndRecord ...>
+''
 record 48:
 '| Platform=Windows95, IGORVersion=3.130\n\n\n\nMoveWindow/P 5.25,40.25,504.75,335\n...hook=PolarWindowHook\nEndMacro\n'
 record 49:
 ''
 record 50:
 '#include <Polar Graphs> version >= 3.0\n'
+<BLANKLINE>
+filesystem:
+{'root': {':variables': [<VariablesRecord ...>],
+          ':waves': [<WaveRecord ...>,
+                     <WaveRecord ...>,
+                     <WaveRecord ...>,
+                     <WaveRecord ...>,
+                     <WaveRecord ...>,
+                     <WaveRecord ...>,
+                     <WaveRecord ...>,
+                     <WaveRecord ...>],
+          'Packages': {'PolarGraphs': {':variables': [<VariablesRecord ...>]},
+                       'WMDataBase': {':variables': [<VariablesRecord ...>]}}}}
 """
 
 import os.path
@@ -1335,6 +1348,7 @@ import sys
 from igor.binarywave import load as loadibw
 from igor.packed import load as loadpxp
 from igor.record.base import TextRecord
+from igor.record.folder import FolderStartRecord, FolderEndRecord
 from igor.record.variables import VariablesRecord
 from igor.record.wave import WaveRecord
 
@@ -1353,10 +1367,12 @@ def dumpibw(filename, strict=True):
 def dumppxp(filename, strict=True):
     sys.stderr.write('Testing {}\n'.format(filename))
     path = os.path.join(_data_dir, filename)
-    records = loadpxp(path, strict=strict)
+    records,filesystem = loadpxp(path, strict=strict)
     for i,record in enumerate(records):
         print('record {}:'.format(i))
-        if isinstance(record, TextRecord):
+        if isinstance(record, (FolderStartRecord, FolderEndRecord)):
+            pprint(record.null_terminated_text)
+        elif isinstance(record, TextRecord):
             pprint(record.text)
         elif isinstance(record, VariablesRecord):
             pprint(record.variables)
@@ -1364,6 +1380,8 @@ def dumppxp(filename, strict=True):
             pprint(record.wave)
         else:
             pprint(record)
+    print('\nfilesystem:')
+    pprint(filesystem)
 
 def pprint(data):
     lines = pformat(data).splitlines()