Skip to content

Commit 30d3f48

Browse files
authored
Make alphabetical sort of records and fields optional (#25)
* Make alphabetical sort of records and fields optional * Review comments
1 parent 1827879 commit 30d3f48

File tree

6 files changed

+217
-76
lines changed

6 files changed

+217
-76
lines changed

docs/api.rst

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ Initialisation
3333
Record Output
3434
-------------
3535

36-
.. function:: WriteRecords(filename, header=None)
36+
.. function:: WriteRecords(filename, header=None, alphabetical=True)
3737

3838
This should be called after creating all records. The generated records
3939
will be written out to the file `filename`. If `header` is left unspecified
@@ -45,6 +45,10 @@ Record Output
4545

4646
Note that the leading ``#`` comments are added to any header that is passed
4747

48+
If `alphabetical` then records, their fields and aliases will be sorted
49+
alphabetically, otherwise the records and aliases will be in insertion
50+
order and fields in DBD order.
51+
4852
.. function:: Disclaimer(source=None, normalise_path=True)
4953

5054
This function generates the disclaimer above. If a source file name is

epicsdbbuilder/recordbase.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import string
66
import json
7+
from collections import OrderedDict
78

89
from . import recordnames
910
from .recordset import recordset
@@ -112,8 +113,8 @@ def __init__(self, record, **fields):
112113

113114
# These assignment have to be directly into the dictionary to
114115
# bypass the tricksy use of __setattr__.
115-
self.__setattr('__fields', {})
116-
self.__setattr('__aliases', set())
116+
self.__setattr('__fields', OrderedDict())
117+
self.__setattr('__aliases', OrderedDict())
117118
self.__setattr('__comments', [])
118119
self.__setattr('__infos', [])
119120
self.__setattr('name', recordnames.RecordName(record))
@@ -132,9 +133,8 @@ def __init__(self, record, **fields):
132133

133134
recordset.PublishRecord(self.name, self)
134135

135-
136136
def add_alias(self, alias):
137-
self.__aliases.add(alias)
137+
self.__aliases[alias] = self
138138

139139
def add_comment(self, comment):
140140
self.__comments.append('# ' + comment)
@@ -145,10 +145,18 @@ def add_metadata(self, metadata):
145145
def add_info(self, name, info):
146146
self.__infos.append((name, info))
147147

148+
def __dbd_order(self, fields):
149+
field_set = set(fields)
150+
for field_name in self._validate.dbEntry.iterate_fields():
151+
if field_name in field_set:
152+
yield field_name
153+
field_set.remove(field_name)
154+
assert not field_set, "DBD for %s doesn't contain %s" % (
155+
self._type, sorted(field_set))
148156

149157
# Call to generate database description of this record. Outputs record
150158
# definition in .db file format. Hooks for meta-data can go here.
151-
def Print(self, output):
159+
def Print(self, output, alphabetical=True):
152160
print(file = output)
153161
for comment in self.__comments:
154162
print(comment, file=output)
@@ -157,14 +165,16 @@ def Print(self, output):
157165
# Print the fields in alphabetical order. This is more convenient
158166
# to the eye and has the useful side effect of bypassing a bug
159167
# where DTYPE needs to be specified before INP or OUT fields.
160-
for k in sorted(self.__fields.keys()):
168+
sort = sorted if alphabetical else self.__dbd_order
169+
for k in sort(self.__fields.keys()):
161170
value = self.__fields[k]
162171
if getattr(value, 'ValidateLater', False):
163172
self.__ValidateField(k, value)
164173
value = self.__FormatFieldForDb(k, value)
165174
padding = ''.ljust(4-len(k)) # To align field values
166175
print(' field(%s, %s%s)' % (k, padding, value), file = output)
167-
for alias in sorted(list(self.__aliases)):
176+
sort = sorted if alphabetical else list
177+
for alias in sort(self.__aliases.keys()):
168178
print(' alias("%s")' % alias, file = output)
169179
for name, info in self.__infos:
170180
value = self.__FormatFieldForDb(name, info)

epicsdbbuilder/recordset.py

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

33
from __future__ import print_function
44

5+
from collections import OrderedDict
56
import os
67
import time
78

@@ -12,7 +13,7 @@
1213

1314
class RecordSet(object):
1415
def ResetRecords(self):
15-
self.__RecordSet = {}
16+
self.__RecordSet = OrderedDict()
1617
self.__HeaderLines = []
1718
self.__BodyLines = []
1819

@@ -29,7 +30,7 @@ def LookupRecord(self, full_name):
2930
return self.__RecordSet[full_name]
3031

3132
# Output complete set of records to the given file.
32-
def Print(self, output):
33+
def Print(self, output, alphabetical):
3334
for line in self.__HeaderLines:
3435
print(line, file = output)
3536
if self.__BodyLines:
@@ -38,8 +39,9 @@ def Print(self, output):
3839
print(line, file = output)
3940
# Print the records in alphabetical order: gives the reader a fighting
4041
# chance to find their way around the generated database!
41-
for record in sorted(self.__RecordSet.keys()):
42-
self.__RecordSet[record].Print(output)
42+
sort = sorted if alphabetical else list
43+
for record in sort(self.__RecordSet):
44+
self.__RecordSet[record].Print(output, alphabetical)
4345

4446
# Returns the number of published records.
4547
def CountRecords(self):
@@ -78,12 +80,12 @@ def Disclaimer(source = None, normalise_source = True):
7880
return message
7981

8082

81-
def WriteRecords(filename, header = None):
83+
def WriteRecords(filename, header=None, alphabetical=True):
8284
if header is None:
8385
header = Disclaimer()
8486
header = header.split('\n')
8587
assert header[-1] == '', 'Terminate header with empty line'
8688
with open(filename, 'w') as output:
8789
for line in header[:-1]:
8890
print('#', line, file = output)
89-
recordset.Print(output)
91+
recordset.Print(output, alphabetical)

test/expected_output.db

Lines changed: 57 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,117 +1,117 @@
1-
# This file was automatically generated on Mon 08 Nov 2021 09:23:43 GMT.
1+
# This file was automatically generated on Thu 25 Nov 2021 10:35:33 GMT.
22
#
33
# *** Please do not edit this file: edit the source file instead. ***
44
#
55
#% macro, DEVICE, Device name
66
#% macro, P, A parameter
77
#% macro, Q, A number
88

9-
record(bi, "$(DEVICE):BOO")
9+
record(longin, "XX-YY-ZZ-01:A")
1010
{
11-
field(INP, "XX-YY-ZZ-01:TRIG")
11+
field(DESC, "blah")
1212
}
1313

14-
record(waveform, "$(DEVICE):FIELD:WITH_CONST_ARRAY")
14+
record(ai, "XX-YY-ZZ-01:B")
1515
{
16-
field(INP, ["A","B","C"])
17-
info(Q:group, {
18-
"MYTABLE": {
19-
"+id": "epics:nt/NTTable:1.0",
20-
"labels": {
21-
"+type": "plain",
22-
"+channel": "VAL"
23-
}
24-
}
25-
})
16+
field(INP, "SR-DI-DCCT-01:SIGNAL")
2617
}
2718

28-
record(ai, "$(DEVICE):FIELD:WITH_JSON_LINK")
19+
record(fanout, "XX-YY-ZZ-01:FAN")
2920
{
30-
field(INP, {
31-
"const": 3.14159265358979
32-
})
33-
info(asyn:READBACK, "1")
34-
info(autosaveFields, "PREC EGU DESC")
21+
field(SELM, "All")
22+
field(LNK1, "XX-YY-ZZ-01:A")
23+
field(LNK2, "XX-YY-ZZ-01:B")
3524
}
3625

37-
record(ai, "$(DEVICE):OPTIONS:CA")
26+
record(bi, "XX-YY-ZZ-01:TRIG")
3827
{
39-
field(INP, "$(DEVICE):TEST CA")
28+
field(SCAN, "1 second")
29+
field(FLNK, "XX-YY-ZZ-01:FAN")
4030
}
4131

42-
record(ai, "$(DEVICE):OPTIONS:CP")
32+
# comment 1
33+
#% metadata
34+
# comment 2
35+
record(ai, "XX-YY-ZZ-01:ABC:TEST")
4336
{
44-
field(INP, "$(DEVICE):TEST CP")
4537
}
4638

47-
record(ai, "$(DEVICE):OPTIONS:CPP")
39+
record(ai, "$(DEVICE):TEST")
4840
{
49-
field(INP, "$(DEVICE):TEST CPP")
41+
field(SCAN, "1 second")
42+
field(VAL, "$(Q)")
43+
field(INP, "@$(P)")
5044
}
5145

52-
record(ai, "$(DEVICE):OPTIONS:MSI")
46+
record(bi, "$(DEVICE):BOO")
5347
{
54-
field(INP, "$(DEVICE):TEST MSI")
48+
field(INP, "XX-YY-ZZ-01:TRIG")
5549
}
5650

57-
record(ai, "$(DEVICE):OPTIONS:MSS")
51+
record(ai, "$(DEVICE):OPTIONS:CA")
5852
{
59-
field(INP, "$(DEVICE):TEST MSS")
53+
field(INP, "$(DEVICE):TEST CA")
6054
}
6155

62-
record(ai, "$(DEVICE):OPTIONS:NMS")
56+
record(ai, "$(DEVICE):OPTIONS:CP")
6357
{
64-
field(INP, "$(DEVICE):TEST NMS")
58+
field(INP, "$(DEVICE):TEST CP")
6559
}
6660

67-
record(ai, "$(DEVICE):OPTIONS:NP")
61+
record(ai, "$(DEVICE):OPTIONS:CPP")
6862
{
69-
field(INP, "$(DEVICE):TEST NPP")
63+
field(INP, "$(DEVICE):TEST CPP")
7064
}
7165

72-
record(ai, "$(DEVICE):OPTIONS:PP:MS")
66+
record(ai, "$(DEVICE):OPTIONS:NP")
7367
{
74-
field(INP, "$(DEVICE):TEST MS PP")
68+
field(INP, "$(DEVICE):TEST NPP")
7569
}
7670

77-
record(stringin, "$(DEVICE):STRING")
71+
record(ai, "$(DEVICE):OPTIONS:MSS")
7872
{
79-
field(VAL, "\"\x0a\\\x01€")
73+
field(INP, "$(DEVICE):TEST MSS")
8074
}
8175

82-
record(ai, "$(DEVICE):TEST")
76+
record(ai, "$(DEVICE):OPTIONS:MSI")
8377
{
84-
field(INP, "@$(P)")
85-
field(SCAN, "1 second")
86-
field(VAL, "$(Q)")
78+
field(INP, "$(DEVICE):TEST MSI")
8779
}
8880

89-
record(longin, "XX-YY-ZZ-01:A")
81+
record(ai, "$(DEVICE):OPTIONS:NMS")
9082
{
91-
field(DESC, "blah")
83+
field(INP, "$(DEVICE):TEST NMS")
9284
}
9385

94-
# comment 1
95-
#% metadata
96-
# comment 2
97-
record(ai, "XX-YY-ZZ-01:ABC:TEST")
86+
record(ai, "$(DEVICE):OPTIONS:PP:MS")
9887
{
88+
field(INP, "$(DEVICE):TEST MS PP")
9989
}
10090

101-
record(ai, "XX-YY-ZZ-01:B")
91+
record(waveform, "$(DEVICE):FIELD:WITH_CONST_ARRAY")
10292
{
103-
field(INP, "SR-DI-DCCT-01:SIGNAL")
93+
field(INP, ["A","B","C"])
94+
info(Q:group, {
95+
"MYTABLE": {
96+
"+id": "epics:nt/NTTable:1.0",
97+
"labels": {
98+
"+type": "plain",
99+
"+channel": "VAL"
100+
}
101+
}
102+
})
104103
}
105104

106-
record(fanout, "XX-YY-ZZ-01:FAN")
105+
record(ai, "$(DEVICE):FIELD:WITH_JSON_LINK")
107106
{
108-
field(LNK1, "XX-YY-ZZ-01:A")
109-
field(LNK2, "XX-YY-ZZ-01:B")
110-
field(SELM, "All")
107+
field(INP, {
108+
"const": 3.14159265358979
109+
})
110+
info(asyn:READBACK, "1")
111+
info(autosaveFields, "PREC EGU DESC")
111112
}
112113

113-
record(bi, "XX-YY-ZZ-01:TRIG")
114+
record(stringin, "$(DEVICE):STRING")
114115
{
115-
field(FLNK, "XX-YY-ZZ-01:FAN")
116-
field(SCAN, "1 second")
116+
field(VAL, "\"\x0a\\\x01€")
117117
}

0 commit comments

Comments
 (0)