Skip to content

Commit 227aaf7

Browse files
authored
Add installer for standalone objdictedit (#42)
* Add installer for standalone objdictedit * Change objdictedit title * Update tests
1 parent 8ccdc10 commit 227aaf7

8 files changed

+220
-5
lines changed

README.md

+11
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,17 @@ This tool is a fork from upstream canfestival-3-asc repo:
113113
> https://github.com/Laerdal/canfestival-3-asc
114114
115115

116+
## Making objdictedit excutable
117+
118+
To be able build an executable that can be run from anywhere:
119+
120+
$ pip install pyinstaller
121+
$ pyinstaller packaging/objdictedit.spec
122+
123+
The file `dist/objdictedit.exe` can now be used anywhere. It does not need any
124+
pre-installed software.
125+
126+
116127
## License
117128

118129
Objdictgen has been based on the python tool included in CanFestival. This

packaging/filereplacer.py

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
2+
import os
3+
import re
4+
import warnings
5+
import objdictgen
6+
from setuptools import SetuptoolsDeprecationWarning
7+
from setuptools.config import read_configuration
8+
9+
warnings.filterwarnings("ignore", category=SetuptoolsDeprecationWarning)
10+
11+
12+
def convert(infile, outfile):
13+
''' Tool to replace @@{VAR_NAME} in files.'''
14+
15+
pat = re.compile(r'^(.*?)@@{(.[^}]+)}(.*)$', re.S)
16+
17+
config = read_configuration('setup.cfg')["metadata"]
18+
19+
# Some hacks
20+
config['version_tuple'] = objdictgen.__version_tuple__
21+
config['copyright'] = objdictgen.__copyright__
22+
23+
with open(infile, "r", encoding="utf-8") as fin:
24+
out = ''
25+
for line in fin:
26+
27+
while True:
28+
m = pat.fullmatch(line)
29+
if not m:
30+
out += line
31+
break
32+
33+
out += m[1]
34+
name = m[2]
35+
line = m[3] # Remainder for the next iteration
36+
if name in config:
37+
out += str(config[m[2]])
38+
39+
elif name in os.environ:
40+
out += os.environ[name]
41+
42+
else:
43+
raise KeyError(f"The variable {name} is not defined")
44+
45+
with open(outfile, 'w', encoding="utf-8") as fout:
46+
fout.write(out)
47+
48+
49+
if __name__ == '__main__':
50+
import argparse
51+
parser = argparse.ArgumentParser()
52+
parser.add_argument('input', help='Input file')
53+
parser.add_argument('output', help='Output file')
54+
args = parser.parse_args()
55+
56+
convert(args.input, args.output)

packaging/objdictedit.spec

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# -*- mode: python ; coding: utf-8 -*-
2+
import os
3+
import sys
4+
sys.path.append("packaging")
5+
from filereplacer import convert
6+
7+
basepath = os.path.dirname(os.path.dirname(os.path.abspath(SPEC)))
8+
9+
workpath = basepath
10+
os.chdir(workpath)
11+
12+
script = """
13+
from objdictgen.ui import objdictedit
14+
objdictedit.main()
15+
"""
16+
17+
os.makedirs("build", exist_ok=True)
18+
with open("build/objdictedit.py", "w", encoding="utf-8") as f:
19+
f.write(script)
20+
21+
icon = basepath + "/src/objdictgen/img/networkedit.ico"
22+
23+
convert("packaging/objdictedit.ver.in", "build/objdictedit.ver")
24+
25+
a = Analysis(
26+
[basepath + '/build/objdictedit.py'],
27+
pathex=[],
28+
binaries=[],
29+
datas=[
30+
(basepath + "/src/objdictgen/img/networkedit.ico", "objdictgen/img"),
31+
],
32+
hiddenimports=[],
33+
hookspath=[],
34+
hooksconfig={},
35+
runtime_hooks=[],
36+
excludes=[],
37+
noarchive=False,
38+
optimize=0,
39+
)
40+
pyz = PYZ(a.pure)
41+
42+
exe = EXE(
43+
pyz,
44+
a.scripts,
45+
a.binaries,
46+
a.datas,
47+
[],
48+
name='objdictedit',
49+
debug=False,
50+
bootloader_ignore_signals=False,
51+
strip=False,
52+
upx=True,
53+
upx_exclude=[],
54+
runtime_tmpdir=None,
55+
console=True,
56+
disable_windowed_traceback=False,
57+
argv_emulation=False,
58+
target_arch=None,
59+
codesign_identity=None,
60+
entitlements_file=None,
61+
icon=icon,
62+
version=basepath + '/build/objdictedit.ver',
63+
)

packaging/objdictedit.ver.in

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# UTF-8
2+
#
3+
# For more details about fixed file info 'ffi' see:
4+
# http://msdn.microsoft.com/en-us/library/ms646997.aspx
5+
VSVersionInfo(
6+
ffi=FixedFileInfo(
7+
# filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4)
8+
# Set not needed items to zero 0.
9+
filevers=@@{version_tuple},
10+
prodvers=@@{version_tuple},
11+
# Contains a bitmask that specifies the valid bits 'flags'r
12+
mask=0x3f,
13+
# Contains a bitmask that specifies the Boolean attributes of the file.
14+
flags=0x0,
15+
# The operating system for which this file was designed.
16+
# 0x4 - NT and there is no need to change it.
17+
OS=0x4,
18+
# The general type of file.
19+
# 0x1 - the file is an application.
20+
fileType=0x1,
21+
# The function of the file.
22+
# 0x0 - the function is not defined for this fileType
23+
subtype=0x0,
24+
# Creation date and time stamp.
25+
date=(0, 0)
26+
),
27+
kids=[
28+
StringFileInfo(
29+
[
30+
StringTable(
31+
u'040904B0',
32+
[StringStruct(u'CompanyName', u'Object Dictionary Editor'),
33+
StringStruct(u'FileDescription', u'@@{description}'),
34+
StringStruct(u'FileVersion', u'@@{version}'),
35+
StringStruct(u'InternalName', u'objdictedit'),
36+
StringStruct(u'LegalCopyright', u'@@{copyright}'),
37+
StringStruct(u'OriginalFilename', u'objdictedit.exe'),
38+
StringStruct(u'ProductName', u'objdictedit'),
39+
StringStruct(u'ProductVersion', u'@@{version}')])
40+
]),
41+
VarFileInfo([VarStruct(u'Translation', [1033, 1200])])
42+
]
43+
)

sonar-project.properties

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
sonar.projectKey=Laerdal_python-objdictgen
22
sonar.organization=laerdal-foss
33
sonar.python.coverage.reportPaths=coverage.xml
4-
sonar.exclusions = tests/**
4+
sonar.exclusions = tests/**, packaging/**

src/objdictgen/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
from objdictgen.nodemanager import NodeManager
2525

2626
__version__ = "3.5.1a1"
27+
__version_tuple__ = (3, 5, 1, 1)
28+
__copyright__ = "(c) 2024 Svein Seldal, Laerdal Medical AS, and several. Licensed under GPLv2.1."
2729

2830
# Shortcuts
2931
LoadFile = Node.LoadFile

src/objdictgen/ui/objdictedit.py

+6-4
Original file line numberDiff line numberDiff line change
@@ -197,9 +197,9 @@ def _init_utils(self):
197197

198198
def _init_ctrls(self, parent):
199199
wx.Frame.__init__(
200-
self, id=ID_OBJDICTEDIT, name='objdictedit',
200+
self, id=ID_OBJDICTEDIT, name=self.title,
201201
parent=parent, pos=wx.Point(149, 178), size=wx.Size(1000, 700),
202-
style=wx.DEFAULT_FRAME_STYLE, title='Objdictedit',
202+
style=wx.DEFAULT_FRAME_STYLE, title=self.title,
203203
)
204204
self._init_utils()
205205
self.SetClientSize(wx.Size(1000, 700))
@@ -229,6 +229,8 @@ def _init_ctrls(self, parent):
229229
self.SetStatusBar(self.HelpBar)
230230

231231
def __init__(self, parent, manager: NodeManager|None = None, filesopen: list[TPath]|None = None):
232+
self.title = f"Object dictionary editor v{objdictgen.__version__}"
233+
232234
filesopen = filesopen or []
233235
if manager is None:
234236
NodeEditorTemplate.__init__(self, NodeManager(), True)
@@ -323,9 +325,9 @@ def OnCloseFrame(self, event):
323325

324326
def RefreshTitle(self):
325327
if self.FileOpened.GetPageCount() > 0:
326-
self.SetTitle(f"Objdictedit - {self.Manager.GetCurrentFilename()}")
328+
self.SetTitle(self.title + f" - {self.Manager.GetCurrentFilename()}")
327329
else:
328-
self.SetTitle("Objdictedit")
330+
self.SetTitle(self.title)
329331

330332
def RefreshCurrentIndexList(self):
331333
selected = self.FileOpened.GetSelection()

tests/test_installer.py

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
2+
import pytest
3+
from unittest import mock
4+
import os
5+
import sys
6+
7+
@pytest.fixture
8+
def setenvvar(monkeypatch):
9+
with mock.patch.dict(os.environ, {"TEST": "foobar"}):
10+
yield
11+
12+
13+
def test_filereplacer(basepath, wd, setenvvar):
14+
15+
sys.path.append(str(basepath / "packaging"))
16+
os.chdir(basepath)
17+
from filereplacer import convert
18+
19+
tests = [
20+
(1, "Test data", "Test data"),
21+
(2, "@@{name}", "objdictgen"),
22+
(3, "@@{TEST}", "foobar"), # Read from the mocked environment variable
23+
(4, "@@{nonexisting}", "non-existing"),
24+
]
25+
26+
for i, data, result in tests:
27+
infile = wd / "test.txt"
28+
outfile = wd / "out.txt"
29+
with open(infile, "w", encoding="utf-8") as f:
30+
f.write(data)
31+
if i == 4:
32+
with pytest.raises(KeyError):
33+
convert(infile, outfile)
34+
continue
35+
else:
36+
convert(infile, outfile)
37+
with open(outfile, "r", encoding="utf-8") as f:
38+
assert f.read() == result

0 commit comments

Comments
 (0)