Skip to content
This repository was archived by the owner on Dec 26, 2025. It is now read-only.

Commit a7ded98

Browse files
committed
Load and dump from/to file
1 parent d326d6b commit a7ded98

4 files changed

Lines changed: 112 additions & 15 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
~*
12
/.idea
23
/build
34
/dist

src/adif_file/__init__.py

Lines changed: 72 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import re
44
import datetime
5+
from collections.abc import Iterator
56

67
from adif_file.__version__ import __version__ as __version_str__
78

@@ -116,6 +117,25 @@ def loads_adi(adi: str) -> dict:
116117
return doc
117118

118119

120+
def load_adi(file_name: str):
121+
"""Turn ADI formated string to dictionary
122+
The parameters are converted to uppercase
123+
124+
{
125+
'HEADER': None,
126+
'RECORDS': [list of records]
127+
}
128+
129+
:param file_name: the file name where the ADI data is stored
130+
:return: the ADI as a dict
131+
"""
132+
133+
with open(file_name, encoding='ascii') as af:
134+
data = af.read()
135+
136+
return loads_adi(data)
137+
138+
119139
def pack(param: str, value: str, dtype: str = None) -> str:
120140
"""Generates ADI tag if value is not empty
121141
Does not generate tags for *_INTL types as required by specification.
@@ -143,7 +163,7 @@ def pack(param: str, value: str, dtype: str = None) -> str:
143163
return ''
144164

145165

146-
def dumps_adi(data_dict: dict, comment: str = 'ADIF export by ' + __proj_name__) -> str:
166+
def dumpi_adi(data_dict: dict, comment: str = 'ADIF export by ' + __proj_name__) -> Iterator[str]:
147167
"""Takes a dictionary and converts it to ADI format
148168
Parameters can be in upper or lower case. The output is upper case. The user must take care
149169
that parameters are not doubled!
@@ -155,16 +175,15 @@ def dumps_adi(data_dict: dict, comment: str = 'ADIF export by ' + __proj_name__)
155175
with datatype as "dtype" and field definition as "userdef" instead of a string value.
156176
157177
:param data_dict: the dictionary with header and records
158-
:param comment: the comment to induce the header"""
178+
:param comment: the comment to induce the header
179+
:return: an iterator of chunks of the ADI:"""
159180

160181
default = {'ADIF_VER': '3.1.4',
161182
'PROGRAMID': __proj_name__,
162183
'PROGRAMVERSION': __version__,
163184
'CREATED_TIMESTAMP': datetime.datetime.utcnow().strftime('%Y%m%d %H%M%S')
164185
}
165186

166-
data = ''
167-
168187
if 'HEADER' in data_dict:
169188
data = comment + ' \n'
170189

@@ -177,10 +196,12 @@ def dumps_adi(data_dict: dict, comment: str = 'ADIF export by ' + __proj_name__)
177196
data += pack(f'USERDEF{i}', u['userdef'], u['dtype']) + '\n'
178197
for p in default.items():
179198
data += pack(p, default[p]) + '\n'
180-
data += '<EOH>\n\n'
199+
data += '<EOH>'
200+
yield data
181201

182202
if 'RECORDS' in data_dict:
183203
for r in data_dict['RECORDS']:
204+
data = ''
184205
empty = True
185206
for i, pv in enumerate(zip(r.keys(), r.values()), 1):
186207
tag = pack(pv[0].upper(), pv[1])
@@ -191,6 +212,50 @@ def dumps_adi(data_dict: dict, comment: str = 'ADIF export by ' + __proj_name__)
191212
data += '\n'
192213

193214
if not empty:
194-
data += '<EOR>\n\n'
215+
data += '<EOR>'
216+
yield data
217+
218+
219+
def dumps_adi(data_dict: dict, comment: str = 'ADIF export by ' + __proj_name__) -> str:
220+
"""Takes a dictionary and converts it to ADI format
221+
Parameters can be in upper or lower case. The output is upper case. The user must take care
222+
that parameters are not doubled!
223+
*_INTL parameters are ignored as they are not allowed in ADI.
224+
Empty records are skipped.
195225
196-
return data.strip()
226+
If 'HEADER' is present the comment is added and missing header fields are filled with defaults.
227+
The header can contain a list of user definitions as USERDEFS. Each user definition is expected as a dictionary
228+
with datatype as "dtype" and field definition as "userdef" instead of a string value.
229+
230+
:param data_dict: the dictionary with header and records
231+
:param comment: the comment to induce the header
232+
:return: the complete ADI as a string"""
233+
234+
return '\n\n'.join(list(dumpi_adi(data_dict, comment)))
235+
236+
237+
def dump_adi(file_name: str, data_dict: dict, comment: str = 'ADIF export by ' + __proj_name__):
238+
"""Takes a dictionary and stores it to filename in ADI format
239+
Parameters can be in upper or lower case. The output is upper case. The user must take care
240+
that parameters are not doubled!
241+
*_INTL parameters are ignored as they are not allowed in ADI.
242+
Empty records are skipped.
243+
244+
If 'HEADER' is present the comment is added and missing header fields are filled with defaults.
245+
The header can contain a list of user definitions as USERDEFS. Each user definition is expected as a dictionary
246+
with datatype as "dtype" and field definition as "userdef" instead of a string value.
247+
248+
:param file_name: the filename to store the ADI data to
249+
:param data_dict: the dictionary with header and records
250+
:param comment: the comment to induce the header
251+
:return: the complete ADI as a string"""
252+
253+
with open(file_name, 'w', encoding='ascii') as af:
254+
first = True
255+
for chunk in dumpi_adi(data_dict, comment):
256+
if first:
257+
first = False
258+
else:
259+
af.write('\n\n')
260+
261+
af.write(chunk)

test/test_dumpadi.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import os
12
import unittest
23

34
from adif_file import *
@@ -82,6 +83,42 @@ def test_25_dump_records(self):
8283

8384
self.assertEqual(adi_exp, dumps_adi(adi_dict))
8485

86+
def test_30_dump_a_file(self):
87+
adi_dict = {
88+
'HEADER': {'PROGRAMID': 'TProg',
89+
'ADIF_VER': '3',
90+
'PROGRAMVERSION': '1',
91+
'CREATED_TIMESTAMP': '1234'},
92+
'RECORDS': [{'TEST1': 'test',
93+
'TEST2': 'test2'},
94+
{'TEST1': 'test3',
95+
'TEST2': 'test4'}]
96+
}
97+
98+
adi_exp = '''ADIF export by PyADIF-File
99+
<PROGRAMID:5>TProg
100+
<ADIF_VER:1>3
101+
<PROGRAMVERSION:1>1
102+
<CREATED_TIMESTAMP:4>1234
103+
<EOH>
104+
105+
<TEST1:4>test <TEST2:5>test2
106+
<EOR>
107+
108+
<TEST1:5>test3 <TEST2:5>test4
109+
<EOR>'''
110+
111+
temp_file = 'testdata/~test.adi'
112+
113+
dump_adi(temp_file, adi_dict)
114+
115+
self.assertTrue(os.path.isfile(temp_file))
116+
117+
with open(temp_file) as af:
118+
self.assertEqual(adi_exp, af.read())
119+
120+
os.remove(temp_file)
121+
85122

86123
if __name__ == '__main__':
87124
unittest.main()

test/test_loadadi.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,21 +47,15 @@ def test_20_unpack_record(self):
4747
self.assertDictEqual({'NAME': 'Test'}, unpack(adi_rec_name))
4848

4949
def test_50_goodfile(self):
50-
with open(get_file_path('testdata/goodfile.txt'), encoding='ascii') as tf:
51-
adi_txt = tf.read()
52-
53-
adi_dict = loads_adi(adi_txt)
50+
adi_dict = load_adi('testdata/goodfile.txt')
5451

5552
self.assertIn('HEADER', adi_dict)
5653
self.assertIn('RECORDS', adi_dict)
5754
self.assertEqual(3, len(adi_dict['HEADER']))
5855
self.assertEqual(5, len(adi_dict['RECORDS']))
5956

6057
def test_55_toomuchheaders(self):
61-
with open(get_file_path('testdata/toomuchheadersfile.txt'), encoding='ascii') as tf:
62-
adi_txt = tf.read()
63-
64-
self.assertRaises(TooMuchHeadersException, loads_adi, adi_txt)
58+
self.assertRaises(TooMuchHeadersException, load_adi, 'testdata/toomuchheadersfile.txt')
6559

6660

6761
if __name__ == '__main__':

0 commit comments

Comments
 (0)