From de2c017f0a40e251cba94c98640eb730b4e31471 Mon Sep 17 00:00:00 2001
From: Yngve Levinsen <yngve.levinsen@ess.eu>
Date: Tue, 10 Nov 2020 19:45:58 +0100
Subject: [PATCH] added first iteration of ess.TraceWin.project

---
 ess/TraceWin.py                               | 128 ++++++++++++++++++
 .../tw_project_file_reverse_engineered.json   |  97 +++++++++++++
 setup.py                                      |   1 +
 3 files changed, 226 insertions(+)
 create mode 100644 ess/data/tw_project_file_reverse_engineered.json

diff --git a/ess/TraceWin.py b/ess/TraceWin.py
index 445f812..eda0eaf 100644
--- a/ess/TraceWin.py
+++ b/ess/TraceWin.py
@@ -1492,3 +1492,131 @@ class field_map:
         data = self.map.reshape(totmapshape)
         for j in data:
             fout.write("{}\n".format(j))
+
+
+class project:
+    def __init__(self, project_fname=None, settings_fname=None):
+        """
+        Read and modify project files
+
+        Example::
+            p = project('SPK.ini')
+            for diff in p.compare_to('MEBT.ini'):
+                print(diff)
+            p.set('main:beam1_energy', 89e6)
+            p.save()
+        """
+        import json
+        import pkg_resources
+
+        if settings_fname is None:
+            self._refdict = json.loads(pkg_resources.resource_string(__name__, "data/tw_project_file_reverse_engineered.json"))
+        else:
+            self._refdict = json.loads(open(self._settings_fname, "r").read())
+
+        self._settings_fname = settings_fname
+        self._project_fname = project_fname
+
+        self._dict = {}
+        if self._project_fname is not None:
+            self._read_settings()
+
+    def _read_settings(self):
+        import struct
+        import textwrap
+
+        with open(self._project_fname, "rb") as f:
+            hexlist = textwrap.wrap(f.read().hex(), 2)
+        for key in self._refdict:
+            o = self._refdict[key]
+            current = "".join(hexlist[o[0] : o[0] + o[2]])
+            if o[1] == "bool":
+                if current == o[3]:
+                    self._dict[key] = False
+                elif current == o[4]:
+                    self._dict[key] = True
+                else:
+                    raise ValueError(f"Wrong setting found for {key}, found {current}, expected one of {o[3:]}")
+            elif o[1] in ["d", "f", "i"]:
+                self._dict[key] = struct.unpack(o[1], b"".fromhex(current))[0]
+
+    def print_settings(self, settings=None):
+        """
+        Print the settings given, or all by default
+
+        :param settings: List of the settings to print
+        """
+        if settings is None:
+            settings = self.keys()
+
+        for setting in settings:
+            print(setting, self._dict[setting])
+
+    def keys(self):
+        return self._dict.keys()
+
+    def get(self, parameter):
+        """
+        Get the setting of the parameter
+        """
+        return self._dict[parameter]
+
+    def set(self, parameter, value):
+        """
+        Set the new value for parameter
+        """
+        current = self.get(parameter)
+        if isinstance(current, bool):
+            if not isinstance(value, bool):
+                raise ValueError(f"{parameter} should be True or False")
+        elif isinstance(current, (float, int)):
+            if not isinstance(value, (float, int)):
+                raise ValueError(f"{parameter} should be a number")
+        self._dict[parameter] = value
+
+    def save(self, fname=None):
+        """
+        Save the project file
+
+        If fname not given, overwrite original file
+        """
+        import struct
+        from textwrap import wrap
+
+        if fname is None:
+            fname = self._project_fname
+
+        with open(self._project_fname, "rb") as f:
+            hexlist = wrap(f.read().hex(), 2)
+
+        for key in self._dict:
+            o = self._refdict[key]
+            v = self._dict[key]
+            if isinstance(v, bool):
+                if v:
+                    v = [o[-1]]
+                else:
+                    v = [o[-2]]
+            else:
+                v = wrap(struct.pack(o[1], v).hex(), 2)
+            for i in range(len(v)):
+                hexlist[o[0] + i] = v[i]
+
+        open(fname, "wb").write(bytes.fromhex("".join(hexlist)))
+
+    def compare_to(self, other):
+        """
+        Compare the settings of this file to a
+        different project file
+
+        :param other: project object, or file path to other project file
+        """
+        diffs = []
+
+        if isinstance(other, str):
+            other = project(other)
+
+        for key in self.keys():
+            if self.get(key) != other.get(key):
+                diffs.append([key, self.get(key), other.get(key)])
+        return diffs
diff --git a/ess/data/tw_project_file_reverse_engineered.json b/ess/data/tw_project_file_reverse_engineered.json
new file mode 100644
index 0000000..4e22f43
--- /dev/null
+++ b/ess/data/tw_project_file_reverse_engineered.json
@@ -0,0 +1,97 @@
+{
+  "partran": [
+    109,
+    "bool",
+    1,
+    "00",
+    "01"
+  ],
+  "toutatis": [
+    135,
+    "bool",
+    1,
+    "00",
+    "01"
+  ],
+  "match:input_matched_beam": [
+    128,
+    "bool",
+    1,
+    "00",
+    "01"
+  ],
+  "match:family_twiss": [
+    113,
+    "bool",
+    1,
+    "00",
+    "01"
+  ],
+  "match:diag": [
+    118,
+    "bool",
+    1,
+    "00",
+    "01"
+  ],
+  "match:limit_criterion": [
+    12260,
+    "d",
+    8
+  ],
+  "main:beam1_npart": [
+    11984,
+    "i",
+    4
+  ],
+  "main:beam2_npart": [
+    11988,
+    "i",
+    4
+  ],
+  "main:beam1_energy": [
+    12100,
+    "d",
+    8
+  ],
+  "main:beam1_freq": [
+    12068,
+    "d",
+    8
+  ],
+  "main:beam1_curr": [
+    12084,
+    "d",
+    8
+  ],
+  "main:beam1_dcycle": [
+    12648,
+    "d",
+    8
+  ],
+  "main:beam1_emit_xxp": [
+    12116,
+    "d",
+    8
+  ],
+  "main:beam1_emit_yyp": [
+    12132,
+    "d",
+    8
+  ],
+  "main:beam1_rms_energy_spread": [
+    22752,
+    "d",
+    8
+  ],
+  "main:beam1_twiss_alphaz": [
+    12572,
+    "d",
+    8
+  ],
+  "main:beam1_twiss_betaz": [
+    12580,
+    "d",
+    8
+  ]
+}
diff --git a/setup.py b/setup.py
index 1052449..b8dc58d 100644
--- a/setup.py
+++ b/setup.py
@@ -26,5 +26,6 @@ setup(
         "Programming Language :: Python :: 3.8",
     ],
     packages=["ess"],
+    package_data={"ess": ["data/*.json"]},
     scripts=["scripts/twcli", "scripts/tracewin_errorstudy", "scripts/ibsimu2tw"],
 )
-- 
GitLab