fit-file-parser
Version:
Parse your .FIT files easily, directly from JS (Garmin, Polar, Suunto)
279 lines (278 loc) • 13.9 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.line = line;
exports.comment = comment;
exports.header = header;
exports.capitalize = capitalize;
exports.snakeToCamel = snakeToCamel;
exports.unicodeToChar = unicodeToChar;
exports.generateProperty = generateProperty;
exports.generateArrayProperty = generateArrayProperty;
exports.generateTypeFromField = generateTypeFromField;
exports.generateTypes = generateTypes;
exports.generateOptions = generateOptions;
exports.generateUtilities = generateUtilities;
exports.generateFitType = generateFitType;
exports.generateMessages = generateMessages;
exports.main = main;
const typescript_1 = __importDefault(require("typescript"));
const fit_js_1 = require("./fit.js");
function line() {
return typescript_1.default.factory.createIdentifier('\n');
}
function comment(contents) {
return [
typescript_1.default.factory.createIdentifier('\n'),
typescript_1.default.factory.createIdentifier(`// ${contents}`),
typescript_1.default.factory.createIdentifier('\n'),
];
}
function header() {
return typescript_1.default.factory.createNodeArray([typescript_1.default.factory.createJSDocComment(`
this file is auto generated using src/type_generator.ts
it parses the big FIT definition object from src/fit.js into usable typescript types
do not edit this file directly, instead edit the generator and
regenerate it with "npm run codegen"
`)])[0];
}
function capitalize(s) {
if (s.length === 0) {
return s;
}
return (s[0].toUpperCase() + s.slice(1));
}
function snakeToCamel(s) {
return s.split('_').map(part => capitalize(part)).join('');
}
function unicodeToChar(text) {
return text.replace(/\\u[\dA-F]{4}/gi, (match) => {
return String.fromCharCode(Number.parseInt(match.replace(/\\u/g, ''), 16));
});
}
function generateProperty(name, type, optional = true) {
return typescript_1.default.factory.createPropertySignature(undefined, typescript_1.default.factory.createIdentifier(name), optional ? typescript_1.default.factory.createToken(typescript_1.default.SyntaxKind.QuestionToken) : undefined, type);
}
function generateArrayProperty(name, type, optional = true) {
return generateProperty(name, typescript_1.default.factory.createArrayTypeNode(type), optional);
}
function generateTypeFromField(def) {
switch (def.type) {
case 'uint32_array':
case 'uint16_array':
case 'uint8_array':
case 'sint32_array':
case 'sint16_array':
case 'sint8_array':
case 'byte_array':
return typescript_1.default.factory.createArrayTypeNode(typescript_1.default.factory.createKeywordTypeNode(typescript_1.default.SyntaxKind.NumberKeyword));
case 'bool':
return typescript_1.default.factory.createKeywordTypeNode(typescript_1.default.SyntaxKind.BooleanKeyword);
case 'date_time':
case 'string':
return typescript_1.default.factory.createKeywordTypeNode(typescript_1.default.SyntaxKind.StringKeyword);
case 'uint32':
case 'uint64':
case 'uint16':
case 'uint8':
case 'int32':
case 'int64':
case 'int16':
case 'int8':
case 'sint32':
case 'sint16':
case 'sint8':
case 'float32':
case 'float64':
case 'uint32z':
case 'uint64z':
case 'uint16z':
case 'uint8z':
case 'localtime_into_day':
case 'byte':
case 'device_index':
return typescript_1.default.factory.createKeywordTypeNode(typescript_1.default.SyntaxKind.NumberKeyword);
default:
return typescript_1.default.factory.createTypeReferenceNode(snakeToCamel(def.type));
}
}
function generateTypes(types) {
const nodes = [];
const typeNames = Object.keys(types);
typeNames.forEach((name) => {
if (name === 'message_index') {
return;
}
const type = types[name];
const names = Object.values(type);
const typeAlias = typescript_1.default.factory.createTypeAliasDeclaration([
typescript_1.default.factory.createModifier(typescript_1.default.SyntaxKind.ExportKeyword),
], snakeToCamel(name), undefined, typescript_1.default.factory.createUnionTypeNode([...names.map(n => typescript_1.default.factory.createLiteralTypeNode(typescript_1.default.factory.createStringLiteral(String(n)))), ...(name === 'mesg_num'
? [
typescript_1.default.factory.createLiteralTypeNode(typescript_1.default.factory.createStringLiteral('definition')),
]
: [])]));
nodes.push(typeAlias);
});
return nodes;
}
function generateOptions(options) {
const nodes = [];
const optionNames = Object.keys(options);
// generate type aliases for each option (all unit values)
optionNames.forEach((name) => {
const option = options[name];
const names = Object.keys(option);
const unit = typescript_1.default.factory.createTypeAliasDeclaration([
typescript_1.default.factory.createModifier(typescript_1.default.SyntaxKind.ExportKeyword),
], capitalize(name), undefined, typescript_1.default.factory.createUnionTypeNode(names.map(n => typescript_1.default.factory.createLiteralTypeNode(typescript_1.default.factory.createStringLiteral(unicodeToChar(n))))));
nodes.push(unit);
});
nodes.push(line());
// generate FitOptions interface which contains all options and their respective types (from above)
const fitOptions = typescript_1.default.factory.createInterfaceDeclaration([
typescript_1.default.factory.createModifier(typescript_1.default.SyntaxKind.ExportKeyword),
], 'FitOptions', undefined, undefined, [
...optionNames.map(name => generateProperty(name, typescript_1.default.factory.createTypeReferenceNode(typescript_1.default.factory.createIdentifier('Unit'), [typescript_1.default.factory.createTypeReferenceNode(typescript_1.default.factory.createIdentifier(capitalize(name)))]), false)),
]);
nodes.push(fitOptions);
return nodes;
}
function generateUtilities() {
const nodes = [];
const unitType = typescript_1.default.factory.createTypeAliasDeclaration([typescript_1.default.factory.createModifier(typescript_1.default.SyntaxKind.ExportKeyword)], 'Unit', [typescript_1.default.factory.createTypeParameterDeclaration(undefined, 'T', typescript_1.default.factory.createTypeReferenceNode('string'))], typescript_1.default.factory.createTypeReferenceNode('Record', [
typescript_1.default.factory.createTypeReferenceNode('T'),
typescript_1.default.factory.createTypeLiteralNode([
generateProperty('multiplier', typescript_1.default.factory.createKeywordTypeNode(typescript_1.default.SyntaxKind.NumberKeyword), false),
generateProperty('offset', typescript_1.default.factory.createKeywordTypeNode(typescript_1.default.SyntaxKind.NumberKeyword), false),
]),
]));
nodes.push(unitType);
const messageIndex = typescript_1.default.factory.createInterfaceDeclaration([typescript_1.default.factory.createModifier(typescript_1.default.SyntaxKind.ExportKeyword)], 'MessageIndex', undefined, undefined, [
generateProperty('0', typescript_1.default.factory.createKeywordTypeNode(typescript_1.default.SyntaxKind.BooleanKeyword), false),
generateProperty('value', typescript_1.default.factory.createKeywordTypeNode(typescript_1.default.SyntaxKind.NumberKeyword), false),
generateProperty('reserved', typescript_1.default.factory.createKeywordTypeNode(typescript_1.default.SyntaxKind.BooleanKeyword), false),
generateProperty('selected', typescript_1.default.factory.createKeywordTypeNode(typescript_1.default.SyntaxKind.BooleanKeyword), false),
]);
nodes.push(messageIndex);
return nodes;
}
function generateAdditionalFields(msg) {
if (msg.name === 'lap') {
return [
generateArrayProperty('records', typescript_1.default.factory.createTypeReferenceNode(snakeToCamel('parsed_record'))),
generateArrayProperty('lengths', typescript_1.default.factory.createTypeReferenceNode(snakeToCamel('parsed_length'))),
];
}
if (msg.name === 'session') {
return [
generateArrayProperty('laps', typescript_1.default.factory.createTypeReferenceNode(snakeToCamel('parsed_lap'))),
];
}
if (msg.name === 'activity') {
const props = {
sessions: 'parsed_session',
events: 'parsed_event',
hrv: 'parsed_hrv',
device_infos: 'parsed_device_info',
developer_data_ids: 'parsed_developer_data_id',
field_descriptions: 'parsed_field_description',
sports: 'parsed_sport',
splits: 'parsed_split',
split_summaries: 'parsed_split_summary',
};
return Object.keys(props).map(prop => generateArrayProperty(prop, typescript_1.default.factory.createTypeReferenceNode(snakeToCamel(props[prop]))));
}
return [];
}
function generateFitType() {
const collectionProperties = {
laps: 'parsed_lap',
records: 'parsed_record',
sessions: 'parsed_session',
lengths: 'parsed_length',
events: 'parsed_event',
device_infos: 'parsed_device_info',
developer_data_ids: 'parsed_developer_data_id',
field_descriptions: 'parsed_field_description',
hrv: 'parsed_hrv',
hr_zone: 'parsed_hr_zone',
power_zone: 'parsed_power_zone',
dive_gases: 'parsed_dive_gas',
course_points: 'parsed_course_point',
sports: 'parsed_sport',
monitors: 'parsed_monitoring',
stress: 'parsed_stress_level',
file_ids: 'parsed_file_id',
monitor_info: 'parsed_monitoring_info',
definitions: 'unknown',
tank_updates: 'parsed_tank_update',
tank_summaries: 'parsed_tank_summary',
jumps: 'parsed_jump',
splits: 'parsed_split',
split_summaries: 'parsed_split_summary',
time_in_zone: 'parsed_time_in_zone',
activity_metrics: 'parsed_activity_metrics',
user_metrics: 'parsed_user_metrics',
};
const referenceProperties = {
file_creator: 'parsed_file_creator',
device_settings: 'parsed_device_settings',
dive_summary: '?parsed_dive_summary',
dive_settings: '?parsed_dive_settings',
software: 'parsed_software',
user_profile: 'parsed_user_profile',
activity: 'parsed_activity',
zones_target: '?parsed_zones_target',
};
return typescript_1.default.factory.createInterfaceDeclaration([typescript_1.default.factory.createModifier(typescript_1.default.SyntaxKind.ExportKeyword)], 'ParsedFit', undefined, undefined, [
generateProperty('protocolVersion', typescript_1.default.factory.createKeywordTypeNode(typescript_1.default.SyntaxKind.NumberKeyword)),
generateProperty('profileVersion', typescript_1.default.factory.createKeywordTypeNode(typescript_1.default.SyntaxKind.NumberKeyword)),
...Object.keys(referenceProperties).map(prop => generateProperty(prop, typescript_1.default.factory.createTypeReferenceNode(snakeToCamel(referenceProperties[prop].replace('?', ''))), referenceProperties[prop].startsWith('?'))),
...Object.keys(collectionProperties).map(prop => generateArrayProperty(prop, collectionProperties[prop] === 'unknown'
? typescript_1.default.factory.createKeywordTypeNode(typescript_1.default.SyntaxKind.UnknownKeyword)
: typescript_1.default.factory.createTypeReferenceNode(snakeToCamel(collectionProperties[prop])))),
]);
}
function generateMessages(messages) {
const nodes = [];
Object.keys(messages).forEach((name) => {
const msg = fit_js_1.FIT.messages[Number(name)];
const usedFields = new Set();
const messageType = typescript_1.default.factory.createInterfaceDeclaration([
typescript_1.default.factory.createModifier(typescript_1.default.SyntaxKind.ExportKeyword),
], snakeToCamel(`parsed_${msg.name}`), undefined, undefined, [
...Object.keys(msg).filter(n => n !== 'name').reduce((acc, id) => {
const def = msg[Number(id)];
if (!usedFields.has(def.field)) {
usedFields.add(def.field);
acc.push(generateProperty(def.field, generateTypeFromField(def), !['start_time', 'timestamp'].includes(def.field)));
}
return acc;
}, []),
...generateAdditionalFields(msg),
]);
nodes.push(messageType);
});
return nodes;
}
function main() {
const sourceFile = typescript_1.default.createSourceFile('', '', typescript_1.default.ScriptTarget.Latest);
const nodes = [];
const printer = typescript_1.default.createPrinter({ newLine: typescript_1.default.NewLineKind.LineFeed });
nodes.push(header());
nodes.push(...comment('utility types used internally'));
nodes.push(...generateUtilities());
nodes.push(...comment('parsed from Fit.types'));
nodes.push(...generateTypes(fit_js_1.FIT.types));
nodes.push(...comment('parsed from Fit.options'));
nodes.push(...generateOptions(fit_js_1.FIT.options));
nodes.push(...comment('parsed from Fit.messages'));
nodes.push(...generateMessages(fit_js_1.FIT.messages));
nodes.push(...comment('the returned type after parsing a .fit file'));
nodes.push(generateFitType());
const nodesArray = typescript_1.default.factory.createNodeArray(nodes);
return printer.printList(typescript_1.default.ListFormat.MultiLine, nodesArray, sourceFile);
}