// Copyright 2022 The Pigweed Authors // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy of // the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations under // the License. /** Parses CSV Database for easier lookups */ import * as Papa from 'papaparse'; interface TokenData { token: number; removalDate: Date | null; domain: string; template: string; } function parseTokenNumber( num: string, lineNumber?: number, ): number | undefined { if (!/^[a-fA-F0-9]+$/.test(num)) { // Malformed number console.error( new Error( `TokenDatabase number ${num} ` + lineNumber ? `at line ${lineNumber} ` : '' + `is not a valid hex number`, ), ); return undefined; } try { return parseInt(num, 16); } catch { console.error( new Error( `TokenDatabase number ${num} ` + lineNumber ? `at line ${lineNumber} ` : '' + `could not be parsed`, ), ); return undefined; } } function parseRemovalDate( dateString: string, lineNumber?: number, ): Date | null | undefined { const dateContent = dateString.trim(); if (dateContent === '') return null; try { return new Date(dateContent); } catch { console.error( new Error( `TokenDatabase removal date ${dateString} ` + lineNumber ? `at line ${lineNumber} ` : '' + `could not be parsed`, ), ); return undefined; } } export function parseCsvEntry( data: string[], lineNumber?: number, ): TokenData | undefined { if (data.length < 3) { console.error( new Error( `TokenDatabase entry ${data} ` + lineNumber ? `at line ${lineNumber} ` : '' + `could not be parsed`, ), ); return undefined; } // Column 0: Token const token = parseTokenNumber(data.shift()!, lineNumber); // Column 1: Removal date const removalDate = parseRemovalDate(data.shift()!, lineNumber); if (token === undefined || removalDate === undefined) { return undefined; } // Modern 4-column databases will have the domain in this position. const domain = data.length > 1 ? data.shift()! : ''; // Last column: Template strings const template = data.shift() ?? ''; return { token, removalDate, domain, template, }; } export class TokenDatabase { private tokens: Map = new Map(); constructor(readonly csv: string) { this.parseTokensToTokensMap(csv); } has(token: number): boolean { return this.tokens.has(token); } get(token: number): string | undefined { return this.tokens.get(token)?.template; } private parseTokensToTokensMap(csv: string) { const parsedCsv = Papa.parse(csv, { header: false }); if (parsedCsv.errors.length > 0) { console.error( new Error( `TokenDatabase could not be parsed: ${parsedCsv.errors.join(', ')}`, ), ); } const csvData = parsedCsv.data as string[][]; for (const [lineNumber, line] of csvData.entries()) { const entry = parseCsvEntry(line, lineNumber); entry && this.tokens.set(entry.token, entry); } } }