UNPKG

fit-file-parser

Version:

Parse your .FIT files easily, directly from JS (Garmin, Polar, Suunto)

279 lines (278 loc) 13.9 kB
"use strict"; 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); }