UNPKG

@scriptables/manifest

Version:

Utilities to generate, parse, and update manifest headers in Scriptable scripts.

108 lines (88 loc) 3.27 kB
import {SCRIPT_HEADER_NOTICES} from './scriptHeaderNotices'; type HeaderParser<T> = (headerLines: string[]) => T; interface ParseOptions { maxHeaderLines?: number; headerNotices?: string[]; excludeNotices?: boolean; } interface ParsedScriptResult<T = never> { header: T | null; headerContent: string; content: string; } export function parse(script: string): ParsedScriptResult<never>; export function parse(script: string, options: ParseOptions): ParsedScriptResult<never>; export function parse<T>(script: string, headerParser: HeaderParser<T>, options?: ParseOptions): ParsedScriptResult<T>; export function parse<T = never>( script: string, headerParserOrOptions?: HeaderParser<T> | ParseOptions, options: ParseOptions = {}, ): ParsedScriptResult<T> { // Fast path: invalid script returns directly if (script.length < 2 || script.charCodeAt(0) !== 47 || script.charCodeAt(1) !== 47) { return {header: null, headerContent: '', content: script || ''}; } // Standardize options const opts = { maxHeaderLines: 20, headerNotices: SCRIPT_HEADER_NOTICES, excludeNotices: false, ...(typeof headerParserOrOptions === 'function' ? options : headerParserOrOptions), }; const lines = new Array<string>(); let pos = 0; let noticeIdx = 0; let hasValidHeader = !opts.headerNotices.length; const maxPos = script.length; const maxLines = opts.maxHeaderLines; let lineStart = 0; let lineCount = 0; let headerEndPos = 0; // Parse header lines while (lineCount < maxLines && pos < maxPos) { // Find end of current line while (pos < maxPos && script.charCodeAt(pos) !== 10) pos++; const line = script.slice(lineStart, pos).trim(); // Check if we've reached non-comment line if (!line.startsWith('//')) { headerEndPos = lineStart; break; } // Check for header notices if they exist if ( opts.headerNotices.length > 0 && noticeIdx < opts.headerNotices.length && line.includes(opts.headerNotices[noticeIdx]) ) { if (++noticeIdx === opts.headerNotices.length) hasValidHeader = true; } // When headerNotices is empty, collect all comment lines // When headerNotices exists, only collect if we have a valid header or are in the process of validating if (!opts.headerNotices.length || hasValidHeader || noticeIdx > 0) { lines.push(line); } lineCount++; pos++; lineStart = pos; } // Only validate header when headerNotices is not empty if (opts.headerNotices.length && !hasValidHeader) { return {header: null, headerContent: '', content: script}; } // If we didn't hit a non-comment line, the header ends at current position if (!headerEndPos) { headerEndPos = pos; } const result: ParsedScriptResult<T> = { header: null, headerContent: lines.join('\n'), content: script.slice(headerEndPos).replace(/^\n+/, ''), }; // Parse header if parser is provided and we have lines to parse if (typeof headerParserOrOptions === 'function' && lines.length) { const headerLines = opts.excludeNotices && opts.headerNotices.length ? lines.slice(opts.headerNotices.length) : lines; result.header = headerParserOrOptions(headerLines); } return result; }