Skip to content

Commit 981f54f

Browse files
author
David Cooper
committed
Clean Up Unit Processor Code
1 parent ca114b6 commit 981f54f

6 files changed

Lines changed: 107 additions & 28 deletions

File tree

‎fitparse/base.py‎

Lines changed: 12 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,14 @@
1111
DataMessage, FieldData, FieldDefinition, DefinitionMessage, MessageHeader,
1212
BASE_TYPES, BASE_TYPE_BYTE
1313
)
14-
from fitparse.utils import calc_crc
14+
from fitparse.utils import calc_crc, scrub_method_name
1515

1616

1717
class FitParseError(Exception):
1818
pass
1919

2020

2121
class FitFile(object):
22-
# TODO: unit test to make sure that all units in profile.py convert to
23-
# sane function names after applying replacements (and there are no
24-
# no regressions)
25-
UNIT_NAME_TO_FUNC_REPLACEMENTS = (
26-
('/', 'per'),
27-
('%', 'percent'),
28-
)
29-
3022
def __init__(self, fileish, check_crc=True, data_processor=None):
3123
if hasattr(fileish, 'read'):
3224
self._file = fileish
@@ -287,9 +279,10 @@ def _parse_data_message(self, header):
287279
# Resolve component fields
288280
if field.components:
289281
for component in field.components:
290-
# Render it's raw value
282+
# Render its raw value
291283
cmp_raw_value = component.render(raw_value)
292284

285+
# Apply accumulated value
293286
if component.accumulate:
294287
accumulator = self._accumulators[def_mesg.mesg_num]
295288
cmp_raw_value = self._apply_compressed_accumulation(
@@ -303,6 +296,7 @@ def _parse_data_message(self, header):
303296

304297
# Extract the component's dynamic field from def_mesg
305298
cmp_field = def_mesg.mesg_type.fields[component.def_num]
299+
306300
# Resolve a possible subfield
307301
cmp_field, cmp_parent_field = self._resolve_subfield(cmp_field, def_mesg, raw_values)
308302
cmp_value = cmp_field.render(cmp_raw_value)
@@ -356,32 +350,28 @@ def _parse_data_message(self, header):
356350
# Apply data processors
357351
for field_data in field_datas:
358352
# Apply type name processor
359-
type_processor = getattr(self._processor, 'process_type_%s' % field_data.type.name, None)
353+
process_method_name = scrub_method_name('process_type_%s' % field_data.type.name)
354+
type_processor = getattr(self._processor, process_method_name, None)
360355
if type_processor:
361356
type_processor(field_data)
362357

363358
# Apply field name processor
364-
field_processor = getattr(self._processor, 'process_field_%s' % field_data.name, None)
359+
process_method_name = scrub_method_name('process_field_%s' % field_data.name)
360+
field_processor = getattr(self._processor, process_method_name, None)
365361
if field_processor:
366362
field_processor(field_data)
367363

368364
# Apply units name processor
369365
if field_data.units:
370-
process_func_name = 'process_units_%s' % field_data.units
371-
# Do unit name replacements padded with spaces
372-
for replace_from, replace_to in self.UNIT_NAME_TO_FUNC_REPLACEMENTS:
373-
process_func_name = process_func_name.replace(
374-
replace_from, ' %s ' % replace_to,
375-
)
376-
# Then strip and convert spaces to underscores
377-
process_func_name = process_func_name.strip().replace(' ', '_')
378-
units_processor = getattr(self._processor, process_func_name, None)
366+
process_method_name = scrub_method_name('process_units_%s' % field_data.units, convert_units=True)
367+
units_processor = getattr(self._processor, process_method_name, None)
379368
if units_processor:
380369
units_processor(field_data)
381370

382371
data_message = DataMessage(header=header, def_mesg=def_mesg, fields=field_datas)
383372

384-
mesg_processor = getattr(self._processor, 'process_message_%s' % def_mesg.name, None)
373+
process_method_name = scrub_method_name('process_message_%s' % def_mesg.name)
374+
mesg_processor = getattr(self._processor, process_method_name, None)
385375
if mesg_processor:
386376
mesg_processor(data_message)
387377

‎fitparse/profile.py‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
################# BEGIN AUTOMATICALLY GENERATED FIT PROFILE ##################
33
########################### DO NOT EDIT THIS FILE ############################
4-
####### EXPORTED PROFILE FROM SDK VERSION 13.0 AT 2014-11-09 15:34:10 ########
4+
####### EXPORTED PROFILE FROM SDK VERSION 13.0 AT 2014-11-09 16:44:30 ########
55
########### PARSED 64 TYPES (813 VALUES), 41 MESSAGES (519 FIELDS) ###########
66

77
from fitparse.records import (
@@ -4094,13 +4094,13 @@
40944094
name='distance_16',
40954095
type=BASE_TYPES[0x84], # uint16
40964096
def_num=8,
4097-
units='100 * m',
4097+
units='100*m',
40984098
),
40994099
9: Field(
41004100
name='cycles_16',
41014101
type=BASE_TYPES[0x84], # uint16
41024102
def_num=9,
4103-
units='2 * cycles (steps)',
4103+
units='2*cycles or steps',
41044104
),
41054105
10: Field(
41064106
name='active_time_16',
@@ -4238,7 +4238,7 @@
42384238
name='resting_metabolic_rate',
42394239
type=BASE_TYPES[0x84], # uint16
42404240
def_num=5,
4241-
units='kcal / day',
4241+
units='kcal/day',
42424242
),
42434243
253: FIELD_TYPE_TIMESTAMP,
42444244
},

‎fitparse/utils.py‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import re
2+
3+
14
CRC_TABLE = (
25
0x0000, 0xCC01, 0xD801, 0x1400, 0xF001, 0x3C00, 0x2800, 0xE401,
36
0xA001, 0x6C00, 0x7800, 0xB401, 0x5000, 0x9C01, 0x8801, 0x4400,
@@ -16,3 +19,19 @@ def calc_crc(bytes, crc=0):
1619
crc = (crc >> 4) & 0x0FFF
1720
crc = crc ^ tmp ^ CRC_TABLE[(byte_char >> 4) & 0xF]
1821
return crc
22+
23+
24+
METHOD_NAME_SCRUBBER = re.compile(r'\W|^(?=\d)')
25+
UNIT_NAME_TO_FUNC_REPLACEMENTS = (
26+
('/', ' per '),
27+
('%', 'percent'),
28+
('*', ' times '),
29+
)
30+
31+
def scrub_method_name(method_name, convert_units=False):
32+
if convert_units:
33+
for replace_from, replace_to in UNIT_NAME_TO_FUNC_REPLACEMENTS:
34+
method_name = method_name.replace(
35+
replace_from, '%s' % replace_to,
36+
)
37+
return METHOD_NAME_SCRUBBER.sub('_', method_name)

‎scripts/generate_profile.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -264,8 +264,9 @@ def fix_scale(data):
264264

265265
def fix_units(data):
266266
if isinstance(data, basestring):
267-
if data == 'kcal / min':
268-
data = 'kcal/min'
267+
data = data.replace(' / ', '/')
268+
data = data.replace(' * ', '*')
269+
data = data.replace('(steps)', 'or steps')
269270
data = data.strip()
270271
return data
271272

‎scripts/unit_tool.py‎

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#!/usr/bin/env python
2+
3+
# Tool for verifying sanity of units in Profile.xls / fitparse/profile.py
4+
5+
import os
6+
import sys
7+
8+
import xlrd # Dev requirement for parsing Excel spreadsheet
9+
10+
from fitparse.profile import MESSAGE_TYPES
11+
from fitparse.utils import scrub_method_name
12+
13+
14+
def do_profile_xls():
15+
workbook = xlrd.open_workbook(sys.argv[1])
16+
sheet = workbook.sheet_by_name('Messages')
17+
18+
all_unit_values = []
19+
for unit_value in sheet.col_values(8): # Extract unit column values
20+
unit_value = unit_value.strip()
21+
if unit_value:
22+
# Deal with comma separated components
23+
unit_values = [v.strip() for v in unit_value.split(',')]
24+
all_unit_values.extend(unit_values)
25+
26+
print 'In Profile.xls:'
27+
for unit_value in sorted(set(all_unit_values)):
28+
print ' * %s' % unit_value
29+
30+
31+
def do_fitparse_profile():
32+
unit_values = []
33+
for message_type in MESSAGE_TYPES.values():
34+
for field in message_type.fields.values():
35+
unit_values.append(field.units)
36+
if field.components:
37+
for component in field.components:
38+
unit_values.append(component.units)
39+
if field.subfields:
40+
for subfield in field.subfields:
41+
unit_values.append(subfield.units)
42+
if subfield.components:
43+
for component in subfield.components:
44+
unit_values.append(component.units)
45+
46+
unit_values = filter(None, unit_values)
47+
48+
print 'In fitparse/profile.py:'
49+
for unit_value in sorted(set(unit_values)):
50+
print ' * %s [%s]' % (
51+
unit_value,
52+
scrub_method_name('process_units_%s' % unit_value, convert_units=True),
53+
)
54+
55+
if __name__ == '__main__':
56+
if len(sys.argv) < 2:
57+
print "Usage: %s Profile.xls" % os.path.basename(__file__)
58+
sys.exit(0)
59+
60+
do_profile_xls()
61+
print
62+
do_fitparse_profile()
63+
64+

‎tests/test.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,11 @@ def test_parsing_edge_500_fit_file(self):
219219
except StopIteration:
220220
pass
221221

222+
# TODO:
223+
# * Test Processors:
224+
# - process_type_<>, process_field_<>, process_units_<>, process_message_<>
225+
# * Test subfield components (have to create a sample fit file)
226+
222227

223228
if __name__ == '__main__':
224229
unittest.main()

0 commit comments

Comments
 (0)