diff --git a/src/system.ts b/src/system.ts index 4628d64..370998a 100644 --- a/src/system.ts +++ b/src/system.ts @@ -18,16 +18,16 @@ declare global { export type NarrowRepos = ExcludeSome; } -export * from './system/array'; -export * from './system/date'; +export * as Arrays from './system/array'; +export * as Dates from './system/date'; export * from './system/decorators/gate'; export * from './system/decorators/log'; export * from './system/decorators/memoize'; export * from './system/decorators/timeout'; -export * from './system/function'; -export * from './system/iterable'; -export * from './system/object'; -export * from './system/promise'; +export * as Functions from './system/function'; +export * as Iterables from './system/iterable'; +export * as Objects from './system/object'; +export * as Promises from './system/promise'; export * from './system/searchTree'; -export * from './system/string'; -export * from './system/version'; +export * as Strings from './system/string'; +export * as Versions from './system/version'; diff --git a/src/system/array.ts b/src/system/array.ts index 8b63bf7..5b7a0ec 100644 --- a/src/system/array.ts +++ b/src/system/array.ts @@ -6,195 +6,193 @@ import { xor as _xor, } from 'lodash-es'; -export namespace Arrays { - export function countUniques(source: T[], accessor: (item: T) => string): Record { - const uniqueCounts = Object.create(null) as Record; - for (const item of source) { - const value = accessor(item); - uniqueCounts[value] = (uniqueCounts[value] ?? 0) + 1; - } - return uniqueCounts; +export function countUniques(source: T[], accessor: (item: T) => string): Record { + const uniqueCounts = Object.create(null) as Record; + for (const item of source) { + const value = accessor(item); + uniqueCounts[value] = (uniqueCounts[value] ?? 0) + 1; } + return uniqueCounts; +} - export function filterMap( - source: T[], - predicateMapper: (item: T, index: number) => TMapped | null | undefined, - ): TMapped[] { - let index = 0; - return source.reduce((accumulator, current) => { - const mapped = predicateMapper(current, index++); - if (mapped != null) { - accumulator.push(mapped); - } - return accumulator; - }, [] as TMapped[]); - } +export function filterMap( + source: T[], + predicateMapper: (item: T, index: number) => TMapped | null | undefined, +): TMapped[] { + let index = 0; + return source.reduce((accumulator, current) => { + const mapped = predicateMapper(current, index++); + if (mapped != null) { + accumulator.push(mapped); + } + return accumulator; + }, [] as TMapped[]); +} - export function filterMapAsync( - source: T[], - predicateMapper: (item: T, index: number) => Promise, - ): Promise { - let index = 0; - return source.reduce(async (accumulator, current) => { - const mapped = await predicateMapper(current, index++); - if (mapped != null) { - accumulator.push(mapped); - } - return accumulator; - }, [] as any); - } +export function filterMapAsync( + source: T[], + predicateMapper: (item: T, index: number) => Promise, +): Promise { + let index = 0; + return source.reduce(async (accumulator, current) => { + const mapped = await predicateMapper(current, index++); + if (mapped != null) { + accumulator.push(mapped); + } + return accumulator; + }, [] as any); +} - export const findLastIndex = _findLastIndex; +export const findLastIndex = _findLastIndex; - export function groupBy(source: T[], accessor: (item: T) => string): Record { - return source.reduce((groupings, current) => { - const value = accessor(current); - groupings[value] = groupings[value] ?? []; - groupings[value].push(current); - return groupings; - }, Object.create(null) as Record); - } +export function groupBy(source: T[], accessor: (item: T) => string): Record { + return source.reduce((groupings, current) => { + const value = accessor(current); + groupings[value] = groupings[value] ?? []; + groupings[value].push(current); + return groupings; + }, Object.create(null) as Record); +} - export function groupByMap(source: TValue[], accessor: (item: TValue) => TKey): Map { - return source.reduce((groupings, current) => { +export function groupByMap(source: TValue[], accessor: (item: TValue) => TKey): Map { + return source.reduce((groupings, current) => { + const value = accessor(current); + const group = groupings.get(value) ?? []; + groupings.set(value, group); + group.push(current); + return groupings; + }, new Map()); +} + +export function groupByFilterMap( + source: TValue[], + accessor: (item: TValue) => TKey, + predicateMapper: (item: TValue) => TMapped | null | undefined, +): Map { + return source.reduce((groupings, current) => { + const mapped = predicateMapper(current); + if (mapped != null) { const value = accessor(current); const group = groupings.get(value) ?? []; groupings.set(value, group); - group.push(current); - return groupings; - }, new Map()); - } + group.push(mapped); + } + return groupings; + }, new Map()); +} - export function groupByFilterMap( - source: TValue[], - accessor: (item: TValue) => TKey, - predicateMapper: (item: TValue) => TMapped | null | undefined, - ): Map { - return source.reduce((groupings, current) => { - const mapped = predicateMapper(current); - if (mapped != null) { - const value = accessor(current); - const group = groupings.get(value) ?? []; - groupings.set(value, group); - group.push(mapped); - } - return groupings; - }, new Map()); - } +export const intersection = _intersectionWith; +export const isEqual = _isEqual; - export const intersection = _intersectionWith; - export const isEqual = _isEqual; +export function areEquivalent(value: T[], other: T[]) { + return _xor(value, other).length === 0; +} - export function areEquivalent(value: T[], other: T[]) { - return _xor(value, other).length === 0; - } +export function isStringArray(array: string[] | T): array is string[] { + return typeof array[0] === 'string'; +} - export function isStringArray(array: string[] | T): array is string[] { - return typeof array[0] === 'string'; - } +export interface HierarchicalItem { + name: string; + relativePath: string; + value?: T; - export interface HierarchicalItem { - name: string; - relativePath: string; - value?: T; + parent?: HierarchicalItem; + children: Map> | undefined; + descendants: T[] | undefined; +} - parent?: HierarchicalItem; - children: Map> | undefined; - descendants: T[] | undefined; - } +export function makeHierarchical( + values: T[], + splitPath: (i: T) => string[], + joinPath: (...paths: string[]) => string, + compact: boolean = false, + canCompact?: (i: T) => boolean, +): HierarchicalItem { + const seed = { + name: '', + relativePath: '', + children: new Map(), + descendants: [], + }; + + let hierarchy = values.reduce((root: HierarchicalItem, value) => { + let folder = root; + + let relativePath = ''; + for (const folderName of splitPath(value)) { + relativePath = joinPath(relativePath, folderName); + + if (folder.children === undefined) { + folder.children = new Map(); + } - export function makeHierarchical( - values: T[], - splitPath: (i: T) => string[], - joinPath: (...paths: string[]) => string, - compact: boolean = false, - canCompact?: (i: T) => boolean, - ): HierarchicalItem { - const seed = { - name: '', - relativePath: '', - children: new Map(), - descendants: [], - }; - - let hierarchy = values.reduce((root: HierarchicalItem, value) => { - let folder = root; - - let relativePath = ''; - for (const folderName of splitPath(value)) { - relativePath = joinPath(relativePath, folderName); - - if (folder.children === undefined) { - folder.children = new Map(); - } - - let f = folder.children.get(folderName); - if (f === undefined) { - f = { - name: folderName, - relativePath: relativePath, - parent: folder, - children: undefined, - descendants: undefined, - }; - folder.children.set(folderName, f); - } - - if (folder.descendants === undefined) { - folder.descendants = []; - } - folder.descendants.push(value); - folder = f; + let f = folder.children.get(folderName); + if (f === undefined) { + f = { + name: folderName, + relativePath: relativePath, + parent: folder, + children: undefined, + descendants: undefined, + }; + folder.children.set(folderName, f); } - folder.value = value; + if (folder.descendants === undefined) { + folder.descendants = []; + } + folder.descendants.push(value); + folder = f; + } - return root; - }, seed); + folder.value = value; - if (compact) { - hierarchy = compactHierarchy(hierarchy, joinPath, true, canCompact); - } + return root; + }, seed); - return hierarchy; + if (compact) { + hierarchy = compactHierarchy(hierarchy, joinPath, true, canCompact); } - export function compactHierarchy( - root: HierarchicalItem, - joinPath: (...paths: string[]) => string, - isRoot: boolean = true, - canCompact?: (i: T) => boolean, - ): HierarchicalItem { - if (root.children === undefined) return root; - - const children = [...root.children.values()]; - for (const child of children) { - compactHierarchy(child, joinPath, false, canCompact); - } + return hierarchy; +} - if (!isRoot && children.length === 1) { - const child = children[0]; - if (child.value === undefined || canCompact?.(child.value)) { - root.name = joinPath(root.name, child.name); - root.relativePath = child.relativePath; - root.children = child.children; - root.descendants = child.descendants; - root.value = child.value; - } - } +export function compactHierarchy( + root: HierarchicalItem, + joinPath: (...paths: string[]) => string, + isRoot: boolean = true, + canCompact?: (i: T) => boolean, +): HierarchicalItem { + if (root.children === undefined) return root; + + const children = [...root.children.values()]; + for (const child of children) { + compactHierarchy(child, joinPath, false, canCompact); + } - return root; + if (!isRoot && children.length === 1) { + const child = children[0]; + if (child.value === undefined || canCompact?.(child.value)) { + root.name = joinPath(root.name, child.name); + root.relativePath = child.relativePath; + root.children = child.children; + root.descendants = child.descendants; + root.value = child.value; + } } - export function uniqueBy(source: T[], accessor: (item: T) => any, predicate?: (item: T) => boolean): T[] { - const uniqueValues = Object.create(null) as Record; - return source.filter(item => { - const value = accessor(item); - // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions - if (uniqueValues[value]) return false; + return root; +} - uniqueValues[value] = accessor; - return predicate?.(item) ?? true; - }); - } +export function uniqueBy(source: T[], accessor: (item: T) => any, predicate?: (item: T) => boolean): T[] { + const uniqueValues = Object.create(null) as Record; + return source.filter(item => { + const value = accessor(item); + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions + if (uniqueValues[value]) return false; + + uniqueValues[value] = accessor; + return predicate?.(item) ?? true; + }); } diff --git a/src/system/date.ts b/src/system/date.ts index 4ce806e..bf92640 100644 --- a/src/system/date.ts +++ b/src/system/date.ts @@ -6,17 +6,15 @@ import * as relativeTime from 'dayjs/plugin/relativeTime'; dayjs.extend(advancedFormat); dayjs.extend(relativeTime); -export namespace Dates { - export const MillisecondsPerMinute = 60000; // 60 * 1000 - export const MillisecondsPerHour = 3600000; // 60 * 60 * 1000 - export const MillisecondsPerDay = 86400000; // 24 * 60 * 60 * 1000 +export const MillisecondsPerMinute = 60000; // 60 * 1000 +export const MillisecondsPerHour = 3600000; // 60 * 60 * 1000 +export const MillisecondsPerDay = 86400000; // 24 * 60 * 60 * 1000 - export interface DateFormatter { - fromNow(): string; - format(format: string): string; - } +export interface DateFormatter { + fromNow(): string; + format(format: string): string; +} - export function getFormatter(date: Date): DateFormatter { - return dayjs(date); - } +export function getFormatter(date: Date): DateFormatter { + return dayjs(date); } diff --git a/src/system/decorators/gate.ts b/src/system/decorators/gate.ts index 4681e62..718ff67 100644 --- a/src/system/decorators/gate.ts +++ b/src/system/decorators/gate.ts @@ -1,5 +1,5 @@ 'use strict'; -import { Promises } from '../promise'; +import { is as isPromise } from '../promise'; const emptyStr = ''; @@ -48,7 +48,7 @@ export function gate any>(resolver?: (...args: Parame let result; try { result = fn!.apply(this, args); - if (result == null || !Promises.is(result)) { + if (result == null || !isPromise(result)) { return result; } diff --git a/src/system/decorators/log.ts b/src/system/decorators/log.ts index eb94ed5..12f3fb2 100644 --- a/src/system/decorators/log.ts +++ b/src/system/decorators/log.ts @@ -1,9 +1,9 @@ 'use strict'; +import { filterMap } from '../array'; import { LogCorrelationContext, Logger, TraceLevel } from '../../logger'; -import { Functions } from '../function'; -import { Promises } from '../promise'; -import { Strings } from '../string'; -import { Arrays } from '../array'; +import { getParameters } from '../function'; +import { is as isPromise } from '../promise'; +import { getDurationMilliseconds } from '../string'; const emptyStr = ''; @@ -99,7 +99,7 @@ export function log any>( } if (fn == null || fnKey == null) throw new Error('Not supported'); - const parameters = Functions.getParameters(fn); + const parameters = getParameters(fn); descriptor[fnKey] = function (this: any, ...args: Parameters) { const correlationId = getNextCorrelationId(); @@ -165,7 +165,7 @@ export function log any>( const argFns = typeof options.args === 'object' ? options.args : undefined; let argFn; let loggable; - loggableParams = Arrays.filterMap(args, (v: any, index: number) => { + loggableParams = filterMap(args, (v: any, index: number) => { const p = parameters[index]; argFn = argFns !== undefined ? argFns[index] : undefined; @@ -192,8 +192,7 @@ export function log any>( const start = options.timed ? process.hrtime() : undefined; const logError = (ex: Error) => { - const timing = - start !== undefined ? ` \u2022 ${Strings.getDurationMilliseconds(start)} ms` : emptyStr; + const timing = start !== undefined ? ` \u2022 ${getDurationMilliseconds(start)} ms` : emptyStr; if (options.singleLine) { Logger.error( ex, @@ -227,8 +226,7 @@ export function log any>( } const logResult = (r: any) => { - const timing = - start !== undefined ? ` \u2022 ${Strings.getDurationMilliseconds(start)} ms` : emptyStr; + const timing = start !== undefined ? ` \u2022 ${getDurationMilliseconds(start)} ms` : emptyStr; let exit; if (options.exit != null) { try { @@ -269,7 +267,7 @@ export function log any>( } }; - if (result != null && Promises.is(result)) { + if (result != null && isPromise(result)) { const promise = result.then(logResult); promise.catch(logError); } else { diff --git a/src/system/decorators/timeout.ts b/src/system/decorators/timeout.ts index b705430..6f7f173 100644 --- a/src/system/decorators/timeout.ts +++ b/src/system/decorators/timeout.ts @@ -1,5 +1,5 @@ 'use strict'; -import { Promises } from '../promise'; +import { CancellationError, is as isPromise } from '../promise'; export function timeout(timeout: number): any; export function timeout(timeoutFromLastArg: true, defaultTimeout?: number): any; @@ -30,7 +30,7 @@ export function timeout(timeoutOrTimeoutFromLastArg: number | boolean, defaultTi } const result = fn?.apply(this, args); - if (timeout == null || timeout < 1 || !Promises.is(result)) return result; + if (timeout == null || timeout < 1 || !isPromise(result)) return result; // const cc = Logger.getCorrelationContext(); @@ -48,7 +48,7 @@ export function timeout(timeoutOrTimeoutFromLastArg: number | boolean, defaultTi new Promise((_, reject) => { const id = setTimeout(() => { clearTimeout(id); - reject(new Promises.CancellationError(result, `Timed out after ${timeout} ms`)); + reject(new CancellationError(result, `Timed out after ${timeout} ms`)); }, timeout!); }), ]); diff --git a/src/system/function.ts b/src/system/function.ts index d19893d..dd4ed1a 100644 --- a/src/system/function.ts +++ b/src/system/function.ts @@ -14,209 +14,203 @@ interface PropOfValue { value: string | undefined; } -export namespace Functions { - export function cachedOnce(fn: (...args: any[]) => Promise, seed: T): (...args: any[]) => Promise { - let cached: T | undefined = seed; - return (...args: any[]) => { - if (cached !== undefined) { - const promise = Promise.resolve(cached); - cached = undefined; - - return promise; - } - return fn(...args); - }; - } +export function cachedOnce(fn: (...args: any[]) => Promise, seed: T): (...args: any[]) => Promise { + let cached: T | undefined = seed; + return (...args: any[]) => { + if (cached !== undefined) { + const promise = Promise.resolve(cached); + cached = undefined; + + return promise; + } + return fn(...args); + }; +} - export interface DebounceOptions { - leading?: boolean; - maxWait?: number; - track?: boolean; - trailing?: boolean; - } +export interface DebounceOptions { + leading?: boolean; + maxWait?: number; + track?: boolean; + trailing?: boolean; +} - export function debounce any>( - fn: T, - wait?: number, - options?: DebounceOptions, - ): Deferrable { - const { track, ...opts }: DebounceOptions = { - track: false, - ...(options ?? {}), - }; - - if (track !== true) return _debounce(fn, wait, opts); - - let pending = false; - - const debounced = _debounce( - (function (this: any, ...args: any[]) { - pending = false; - return fn.apply(this, args); - } as any) as T, - wait, - options, - ); +export function debounce any>( + fn: T, + wait?: number, + options?: DebounceOptions, +): Deferrable { + const { track, ...opts }: DebounceOptions = { + track: false, + ...(options ?? {}), + }; + + if (track !== true) return _debounce(fn, wait, opts); + + let pending = false; + + const debounced = _debounce( + (function (this: any, ...args: any[]) { + pending = false; + return fn.apply(this, args); + } as any) as T, + wait, + options, + ); + + const tracked: Deferrable = function (this: any, ...args: Parameters) { + pending = true; + return debounced.apply(this, args); + } as any; + + tracked.pending = function () { + return pending; + }; + tracked.cancel = function () { + return debounced.cancel.apply(debounced); + }; + tracked.flush = function () { + return debounced.flush.apply(debounced); + }; + + return tracked; +} - const tracked: Deferrable = function (this: any, ...args: Parameters) { - pending = true; - return debounced.apply(this, args); - } as any; - - tracked.pending = function () { - return pending; - }; - tracked.cancel = function () { - return debounced.cancel.apply(debounced); - }; - tracked.flush = function () { - return debounced.flush.apply(debounced); - }; - - return tracked; - } +// export function debounceMemoized any>( +// fn: T, +// wait?: number, +// options?: DebounceOptions & { resolver?(...args: any[]): any } +// ): T { +// const { resolver, ...opts } = options || ({} as DebounceOptions & { resolver?: T }); + +// const memo = _memoize(() => { +// return debounce(fn, wait, opts); +// }, resolver); - // export function debounceMemoized any>( - // fn: T, - // wait?: number, - // options?: DebounceOptions & { resolver?(...args: any[]): any } - // ): T { - // const { resolver, ...opts } = options || ({} as DebounceOptions & { resolver?: T }); +// return function(this: any, ...args: []) { +// return memo.apply(this, args).apply(this, args); +// } as T; +// } - // const memo = _memoize(() => { - // return debounce(fn, wait, opts); - // }, resolver); +const comma = ','; +const emptyStr = ''; +const equals = '='; +const openBrace = '{'; +const openParen = '('; +const closeParen = ')'; - // return function(this: any, ...args: []) { - // return memo.apply(this, args).apply(this, args); - // } as T; - // } +const fnBodyRegex = /\(([\s\S]*)\)/; +const fnBodyStripCommentsRegex = /(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/gm; +const fnBodyStripParamDefaultValueRegex = /\s?=.*$/; - const comma = ','; - const emptyStr = ''; - const equals = '='; - const openBrace = '{'; - const openParen = '('; - const closeParen = ')'; +export function getParameters(fn: Function): string[] { + if (typeof fn !== 'function') throw new Error('Not supported'); - const fnBodyRegex = /\(([\s\S]*)\)/; - const fnBodyStripCommentsRegex = /(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/gm; - const fnBodyStripParamDefaultValueRegex = /\s?=.*$/; + if (fn.length === 0) return []; - export function getParameters(fn: Function): string[] { - if (typeof fn !== 'function') throw new Error('Not supported'); + let fnBody: string = Function.prototype.toString.call(fn); + fnBody = fnBody.replace(fnBodyStripCommentsRegex, emptyStr) || fnBody; + fnBody = fnBody.slice(0, fnBody.indexOf(openBrace)); - if (fn.length === 0) return []; + let open = fnBody.indexOf(openParen); + let close = fnBody.indexOf(closeParen); - let fnBody: string = Function.prototype.toString.call(fn); - fnBody = fnBody.replace(fnBodyStripCommentsRegex, emptyStr) || fnBody; - fnBody = fnBody.slice(0, fnBody.indexOf(openBrace)); + open = open >= 0 ? open + 1 : 0; + close = close > 0 ? close : fnBody.indexOf(equals); - let open = fnBody.indexOf(openParen); - let close = fnBody.indexOf(closeParen); + fnBody = fnBody.slice(open, close); + fnBody = `(${fnBody})`; - open = open >= 0 ? open + 1 : 0; - close = close > 0 ? close : fnBody.indexOf(equals); + const match = fnBodyRegex.exec(fnBody); + return match != null + ? match[1].split(comma).map(param => param.trim().replace(fnBodyStripParamDefaultValueRegex, emptyStr)) + : []; +} - fnBody = fnBody.slice(open, close); - fnBody = `(${fnBody})`; +export function is(o: T | null | undefined): o is T; +export function is(o: object, prop: keyof T, value?: any): o is T; +export function is(o: object, matcher: (o: object) => boolean): o is T; +export function is(o: object, propOrMatcher?: keyof T | ((o: any) => boolean), value?: any): o is T { + if (propOrMatcher == null) return o != null; + if (typeof propOrMatcher === 'function') return propOrMatcher(o); - const match = fnBodyRegex.exec(fnBody); - return match != null - ? match[1].split(comma).map(param => param.trim().replace(fnBodyStripParamDefaultValueRegex, emptyStr)) - : []; - } + return value === undefined ? (o as any)[propOrMatcher] !== undefined : (o as any)[propOrMatcher] === value; +} - export function is(o: T | null | undefined): o is T; - export function is(o: object, prop: keyof T, value?: any): o is T; - export function is(o: object, matcher: (o: object) => boolean): o is T; - export function is( - o: object, - propOrMatcher?: keyof T | ((o: any) => boolean), - value?: any, - ): o is T { - if (propOrMatcher == null) return o != null; - if (typeof propOrMatcher === 'function') return propOrMatcher(o); - - return value === undefined ? (o as any)[propOrMatcher] !== undefined : (o as any)[propOrMatcher] === value; - } +export function once any>(fn: T): T { + return _once(fn); +} - export function once any>(fn: T): T { - return _once(fn); - } +export function propOf>(o: T, key: K) { + const propOfCore = >(o: T, key: K) => { + const value: string = + (propOfCore as PropOfValue).value === undefined ? key : `${(propOfCore as PropOfValue).value}.${key}`; + (propOfCore as PropOfValue).value = value; + const fn = >(k: Y) => propOfCore(o[key], k); + return Object.assign(fn, { value: value }); + }; + return propOfCore(o, key); +} - export function propOf>(o: T, key: K) { - const propOfCore = >(o: T, key: K) => { - const value: string = - (propOfCore as PropOfValue).value === undefined ? key : `${(propOfCore as PropOfValue).value}.${key}`; - (propOfCore as PropOfValue).value = value; - const fn = >(k: Y) => propOfCore(o[key], k); - return Object.assign(fn, { value: value }); - }; - return propOfCore(o, key); - } +export function interval(fn: (...args: any[]) => void, ms: number): Disposable { + let timer: NodeJS.Timer | undefined; + const disposable = { + dispose: () => { + if (timer !== undefined) { + clearInterval(timer); + timer = undefined; + } + }, + }; + timer = global.setInterval(fn, ms); + + return disposable; +} - export function interval(fn: (...args: any[]) => void, ms: number): Disposable { +export function progress(promise: Promise, intervalMs: number, onProgress: () => boolean): Promise { + return new Promise((resolve, reject) => { let timer: NodeJS.Timer | undefined; - const disposable = { - dispose: () => { + timer = global.setInterval(() => { + if (onProgress()) { if (timer !== undefined) { clearInterval(timer); timer = undefined; } - }, - }; - timer = global.setInterval(fn, ms); + } + }, intervalMs); - return disposable; - } + promise.then( + () => { + if (timer !== undefined) { + clearInterval(timer); + timer = undefined; + } - export function progress(promise: Promise, intervalMs: number, onProgress: () => boolean): Promise { - return new Promise((resolve, reject) => { - let timer: NodeJS.Timer | undefined; - timer = global.setInterval(() => { - if (onProgress()) { - if (timer !== undefined) { - clearInterval(timer); - timer = undefined; - } + resolve(promise); + }, + ex => { + if (timer !== undefined) { + clearInterval(timer); + timer = undefined; } - }, intervalMs); - - promise.then( - () => { - if (timer !== undefined) { - clearInterval(timer); - timer = undefined; - } - - resolve(promise); - }, - ex => { - if (timer !== undefined) { - clearInterval(timer); - timer = undefined; - } - - reject(ex); - }, - ); - }); - } - export async function wait(ms: number) { - await new Promise(resolve => setTimeout(resolve, ms)); - } + reject(ex); + }, + ); + }); +} - export async function waitUntil(fn: (...args: any[]) => boolean, timeout: number): Promise { - const max = Math.round(timeout / 100); - let counter = 0; - while (true) { - if (fn()) return true; - if (counter > max) return false; +export async function wait(ms: number) { + await new Promise(resolve => setTimeout(resolve, ms)); +} - await wait(100); - counter++; - } +export async function waitUntil(fn: (...args: any[]) => boolean, timeout: number): Promise { + const max = Math.round(timeout / 100); + let counter = 0; + while (true) { + if (fn()) return true; + if (counter > max) return false; + + await wait(100); + counter++; } } diff --git a/src/system/iterable.ts b/src/system/iterable.ts index 1d4ae43..5b82d11 100644 --- a/src/system/iterable.ts +++ b/src/system/iterable.ts @@ -1,168 +1,161 @@ 'use strict'; -export namespace Iterables { - export function count(source: Iterable | IterableIterator, predicate?: (item: T) => boolean): number { - let count = 0; - let next: IteratorResult; - - while (true) { - next = (source as IterableIterator).next(); - if (next.done) break; - - if (predicate === undefined || predicate(next.value)) { - count++; - } - } +export function count(source: Iterable | IterableIterator, predicate?: (item: T) => boolean): number { + let count = 0; + let next: IteratorResult; - return count; - } + while (true) { + next = (source as IterableIterator).next(); + if (next.done) break; - export function every(source: Iterable | IterableIterator, predicate: (item: T) => boolean): boolean { - for (const item of source) { - if (!predicate(item)) return false; + if (predicate === undefined || predicate(next.value)) { + count++; } - return true; } - export function filter( - source: Iterable | IterableIterator, - ): Iterable; - export function filter(source: Iterable | IterableIterator, predicate: (item: T) => boolean): Iterable; - export function* filter( - source: Iterable | IterableIterator, - predicate?: (item: T) => boolean, - ): Iterable { - if (predicate === undefined) { - for (const item of source) { - if (item != null) yield item; - } - } else { - for (const item of source) { - if (predicate(item)) yield item; - } - } - } + return count; +} - export function* filterMap( - source: Iterable | IterableIterator, - predicateMapper: (item: T) => TMapped | undefined | null, - ): Iterable { - for (const item of source) { - const mapped = predicateMapper(item); - if (mapped != null) yield mapped; - } +export function every(source: Iterable | IterableIterator, predicate: (item: T) => boolean): boolean { + for (const item of source) { + if (!predicate(item)) return false; } + return true; +} - export function forEach(source: Iterable | IterableIterator, fn: (item: T, index: number) => void): void { - let i = 0; +export function filter(source: Iterable | IterableIterator): Iterable; +export function filter(source: Iterable | IterableIterator, predicate: (item: T) => boolean): Iterable; +export function* filter(source: Iterable | IterableIterator, predicate?: (item: T) => boolean): Iterable { + if (predicate === undefined) { for (const item of source) { - fn(item, i); - i++; + if (item != null) yield item; } - } - - export function find(source: Iterable | IterableIterator, predicate: (item: T) => boolean): T | null { + } else { for (const item of source) { - if (predicate(item)) return item; + if (predicate(item)) yield item; } - return null; } +} - export function first(source: Iterable): T { - return source[Symbol.iterator]().next().value; +export function* filterMap( + source: Iterable | IterableIterator, + predicateMapper: (item: T) => TMapped | undefined | null, +): Iterable { + for (const item of source) { + const mapped = predicateMapper(item); + if (mapped != null) yield mapped; } +} - export function* flatMap( - source: Iterable | IterableIterator, - mapper: (item: T) => Iterable, - ): Iterable { - for (const item of source) { - yield* mapper(item); - } +export function forEach(source: Iterable | IterableIterator, fn: (item: T, index: number) => void): void { + let i = 0; + for (const item of source) { + fn(item, i); + i++; } +} - export function has(source: Iterable | IterableIterator, item: T): boolean { - return some(source, i => i === item); +export function find(source: Iterable | IterableIterator, predicate: (item: T) => boolean): T | null { + for (const item of source) { + if (predicate(item)) return item; } + return null; +} + +export function first(source: Iterable): T { + return source[Symbol.iterator]().next().value; +} - export function isIterable(source: Iterable): boolean { - return typeof source[Symbol.iterator] === 'function'; +export function* flatMap( + source: Iterable | IterableIterator, + mapper: (item: T) => Iterable, +): Iterable { + for (const item of source) { + yield* mapper(item); } +} - export function join(source: Iterable, separator: string): string { - let value = ''; +export function has(source: Iterable | IterableIterator, item: T): boolean { + return some(source, i => i === item); +} - const iterator = source[Symbol.iterator](); - let next = iterator.next(); - if (next.done) return value; +export function isIterable(source: Iterable): boolean { + return typeof source[Symbol.iterator] === 'function'; +} + +export function join(source: Iterable, separator: string): string { + let value = ''; - while (true) { - const s = next.value.toString(); + const iterator = source[Symbol.iterator](); + let next = iterator.next(); + if (next.done) return value; - next = iterator.next(); - if (next.done) { - value += s; - break; - } + while (true) { + const s = next.value.toString(); - value += `${s}${separator}`; + next = iterator.next(); + if (next.done) { + value += s; + break; } - return value; + value += `${s}${separator}`; } - export function last(source: Iterable): T | undefined { - let item: T | undefined; - for (item of source) { - /* noop */ - } - return item; + return value; +} + +export function last(source: Iterable): T | undefined { + let item: T | undefined; + for (item of source) { + /* noop */ } + return item; +} - export function* map( - source: Iterable | IterableIterator, - mapper: (item: T) => TMapped, - ): Iterable { - for (const item of source) { - yield mapper(item); - } +export function* map( + source: Iterable | IterableIterator, + mapper: (item: T) => TMapped, +): Iterable { + for (const item of source) { + yield mapper(item); + } +} + +export function next(source: IterableIterator): T { + return source.next().value; +} + +export function* skip(source: Iterable | IterableIterator, count: number): IterableIterator { + let i = 0; + for (const item of source) { + if (i >= count) yield item; + i++; } +} - export function next(source: IterableIterator): T { - return source.next().value; +export function some(source: Iterable | IterableIterator, predicate: (item: T) => boolean): boolean { + for (const item of source) { + if (predicate(item)) return true; } + return false; +} - export function* skip(source: Iterable | IterableIterator, count: number): IterableIterator { +export function* take(source: Iterable | IterableIterator, count: number): Iterable { + if (count > 0) { let i = 0; for (const item of source) { - if (i >= count) yield item; + yield item; i++; + if (i >= count) break; } } +} - export function some(source: Iterable | IterableIterator, predicate: (item: T) => boolean): boolean { +export function* union(...sources: (Iterable | IterableIterator)[]): Iterable { + for (const source of sources) { for (const item of source) { - if (predicate(item)) return true; - } - return false; - } - - export function* take(source: Iterable | IterableIterator, count: number): Iterable { - if (count > 0) { - let i = 0; - for (const item of source) { - yield item; - i++; - if (i >= count) break; - } - } - } - - export function* union(...sources: (Iterable | IterableIterator)[]): Iterable { - for (const source of sources) { - for (const item of source) { - yield item; - } + yield item; } } } diff --git a/src/system/object.ts b/src/system/object.ts index fac1210..eba3538 100644 --- a/src/system/object.ts +++ b/src/system/object.ts @@ -1,86 +1,84 @@ 'use strict'; import { isEqual as _isEqual } from 'lodash-es'; -import { Arrays } from './array'; +import { areEquivalent as arraysAreEquivalent } from './array'; -export namespace Objects { - export function entries(o: Record): IterableIterator<[string, T]>; - export function entries(o: Record): IterableIterator<[string, T]>; - export function* entries(o: any): IterableIterator<[string, T]> { - for (const key in o) { - yield [key, o[key]]; - } +export function entries(o: Record): IterableIterator<[string, T]>; +export function entries(o: Record): IterableIterator<[string, T]>; +export function* entries(o: any): IterableIterator<[string, T]> { + for (const key in o) { + yield [key, o[key]]; } +} - export function flatten(o: any, prefix: string = '', stringify: boolean = false): Record { - const flattened = Object.create(null) as Record; - _flatten(flattened, prefix, o, stringify); - return flattened; - } +export function flatten(o: any, prefix: string = '', stringify: boolean = false): Record { + const flattened = Object.create(null) as Record; + _flatten(flattened, prefix, o, stringify); + return flattened; +} - function _flatten(flattened: Record, key: string, value: any, stringify: boolean = false) { - if (Object(value) !== value) { - if (stringify) { - if (value == null) { - flattened[key] = null; - } else if (typeof value === 'string') { - flattened[key] = value; - } else { - flattened[key] = JSON.stringify(value); - } - } else { - flattened[key] = value; - } - } else if (Array.isArray(value)) { - const len = value.length; - for (let i = 0; i < len; i++) { - _flatten(flattened, `${key}[${i}]`, value[i], stringify); - } - if (len === 0) { +function _flatten(flattened: Record, key: string, value: any, stringify: boolean = false) { + if (Object(value) !== value) { + if (stringify) { + if (value == null) { flattened[key] = null; + } else if (typeof value === 'string') { + flattened[key] = value; + } else { + flattened[key] = JSON.stringify(value); } } else { - let isEmpty = true; - for (const p in value) { - isEmpty = false; - _flatten(flattened, key ? `${key}.${p}` : p, value[p], stringify); - } - if (isEmpty && key) { - flattened[key] = null; - } + flattened[key] = value; + } + } else if (Array.isArray(value)) { + const len = value.length; + for (let i = 0; i < len; i++) { + _flatten(flattened, `${key}[${i}]`, value[i], stringify); + } + if (len === 0) { + flattened[key] = null; + } + } else { + let isEmpty = true; + for (const p in value) { + isEmpty = false; + _flatten(flattened, key ? `${key}.${p}` : p, value[p], stringify); + } + if (isEmpty && key) { + flattened[key] = null; } } +} - export function isEqual(value: any, other: any) { - return _isEqual(value, other); - } +export function isEqual(value: any, other: any) { + return _isEqual(value, other); +} - export function areEquivalent(value: any, other: any) { - if (Array.isArray(value) && Array.isArray(other)) { - return Arrays.areEquivalent(value, other); - } - return isEqual(value, other); +export function areEquivalent(value: any, other: any) { + if (Array.isArray(value) && Array.isArray(other)) { + return arraysAreEquivalent(value, other); } + return isEqual(value, other); +} - export function paths(o: Record, path?: string): string[] { - const results = []; +export function paths(o: Record, path?: string): string[] { + const results = []; - for (const key in o) { - const child = o[key]; - if (typeof child === 'object') { - results.push(...paths(child, path === undefined ? key : `${path}.${key}`)); - } else { - results.push(path === undefined ? key : `${path}.${key}`); - } + for (const key in o) { + const child = o[key]; + if (typeof child === 'object') { + results.push(...paths(child, path === undefined ? key : `${path}.${key}`)); + } else { + results.push(path === undefined ? key : `${path}.${key}`); } - - return results; } - export function values(o: Record): IterableIterator; - export function values(o: Record): IterableIterator; - export function* values(o: any): IterableIterator { - for (const key in o) { - yield o[key]; - } + return results; +} + +export function values(o: Record): IterableIterator; +export function values(o: Record): IterableIterator; +export function* values(o: any): IterableIterator { + for (const key in o) { + yield o[key]; } } diff --git a/src/system/promise.ts b/src/system/promise.ts index 93fad2b..95c76c7 100644 --- a/src/system/promise.ts +++ b/src/system/promise.ts @@ -1,157 +1,149 @@ 'use strict'; import { CancellationToken } from 'vscode'; -import { Iterables } from './iterable'; +import { map } from './iterable'; -export namespace Promises { - export class CancellationError extends Error { - constructor(public readonly promise: TPromise, message: string) { - super(message); - } +export class CancellationError extends Error { + constructor(public readonly promise: TPromise, message: string) { + super(message); } +} - export class CancellationErrorWithId extends CancellationError { - constructor(public readonly id: T, promise: TPromise, message: string) { - super(promise, message); - } +export class CancellationErrorWithId extends CancellationError { + constructor(public readonly id: T, promise: TPromise, message: string) { + super(promise, message); } +} - export function cancellable( - promise: Thenable, - timeoutOrToken: number | CancellationToken, - options: { - cancelMessage?: string; - onDidCancel?( - resolve: (value?: T | PromiseLike | undefined) => void, - reject: (reason?: any) => void, - ): void; - } = {}, - ): Promise { - return new Promise((resolve, reject) => { - let fulfilled = false; - let timer: NodeJS.Timer | undefined; - if (typeof timeoutOrToken === 'number') { - timer = global.setTimeout(() => { - if (typeof options.onDidCancel === 'function') { - options.onDidCancel(resolve, reject); - } else { - reject(new CancellationError(promise, options.cancelMessage ?? 'TIMED OUT')); - } - }, timeoutOrToken); - } else { - timeoutOrToken.onCancellationRequested(() => { - if (fulfilled) return; +export function cancellable( + promise: Thenable, + timeoutOrToken: number | CancellationToken, + options: { + cancelMessage?: string; + onDidCancel?(resolve: (value?: T | PromiseLike | undefined) => void, reject: (reason?: any) => void): void; + } = {}, +): Promise { + return new Promise((resolve, reject) => { + let fulfilled = false; + let timer: NodeJS.Timer | undefined; + if (typeof timeoutOrToken === 'number') { + timer = global.setTimeout(() => { + if (typeof options.onDidCancel === 'function') { + options.onDidCancel(resolve, reject); + } else { + reject(new CancellationError(promise, options.cancelMessage ?? 'TIMED OUT')); + } + }, timeoutOrToken); + } else { + timeoutOrToken.onCancellationRequested(() => { + if (fulfilled) return; - if (typeof options.onDidCancel === 'function') { - options.onDidCancel(resolve, reject); - } else { - reject(new CancellationError(promise, options.cancelMessage ?? 'CANCELLED')); - } - }); - } + if (typeof options.onDidCancel === 'function') { + options.onDidCancel(resolve, reject); + } else { + reject(new CancellationError(promise, options.cancelMessage ?? 'CANCELLED')); + } + }); + } - promise.then( - () => { - fulfilled = true; - if (timer !== undefined) { - clearTimeout(timer); - } - resolve(promise); - }, - ex => { - fulfilled = true; - if (timer !== undefined) { - clearTimeout(timer); + promise.then( + () => { + fulfilled = true; + if (timer !== undefined) { + clearTimeout(timer); + } + resolve(promise); + }, + ex => { + fulfilled = true; + if (timer !== undefined) { + clearTimeout(timer); + } + reject(ex); + }, + ); + }); +} + +export function first(promises: Promise[], predicate: (value: T) => boolean): Promise { + const newPromises: Promise[] = promises.map( + p => + new Promise((resolve, reject) => + p.then(value => { + if (predicate(value)) { + resolve(value); } - reject(ex); - }, - ); - }); - } + }, reject), + ), + ); + newPromises.push(Promise.all(promises).then(() => undefined)); + return Promise.race(newPromises); +} - export function first(promises: Promise[], predicate: (value: T) => boolean): Promise { - const newPromises: Promise[] = promises.map( - p => - new Promise((resolve, reject) => - p.then(value => { - if (predicate(value)) { - resolve(value); - } - }, reject), - ), - ); - newPromises.push(Promise.all(promises).then(() => undefined)); - return Promise.race(newPromises); - } +export function is(obj: T | Promise): obj is Promise { + return obj != null && typeof (obj as Promise).then === 'function'; +} - export function is(obj: T | Promise): obj is Promise { - return obj != null && typeof (obj as Promise).then === 'function'; +export function raceAll( + promises: Promise[], + timeout?: number, +): Promise<(TPromise | CancellationError>)[]>; +export function raceAll( + promises: Map>, + timeout?: number, +): Promise>>>; +export function raceAll( + ids: Iterable, + fn: (id: T) => Promise, + timeout?: number, +): Promise>>>; +export async function raceAll( + promisesOrIds: Promise[] | Map> | Iterable, + timeoutOrFn?: number | ((id: T) => Promise), + timeout?: number, +) { + let promises; + if (timeoutOrFn != null && typeof timeoutOrFn !== 'number') { + promises = new Map( + map]>(promisesOrIds as Iterable, id => [id, timeoutOrFn(id)]), + ); + } else { + timeout = timeoutOrFn; + promises = promisesOrIds as Promise[] | Map>; } - export function raceAll( - promises: Promise[], - timeout?: number, - ): Promise<(TPromise | Promises.CancellationError>)[]>; - export function raceAll( - promises: Map>, - timeout?: number, - ): Promise>>>; - export function raceAll( - ids: Iterable, - fn: (id: T) => Promise, - timeout?: number, - ): Promise>>>; - export async function raceAll( - promisesOrIds: Promise[] | Map> | Iterable, - timeoutOrFn?: number | ((id: T) => Promise), - timeout?: number, - ) { - let promises; - if (timeoutOrFn != null && typeof timeoutOrFn !== 'number') { - promises = new Map( - Iterables.map]>(promisesOrIds as Iterable, id => [id, timeoutOrFn(id)]), - ); - } else { - timeout = timeoutOrFn; - promises = promisesOrIds as Promise[] | Map>; - } - - if (promises instanceof Map) { - return new Map( - await Promise.all( - Iterables.map< - [T, Promise], - Promise<[T, TPromise | CancellationErrorWithId>]> - >( - promises.entries(), - timeout == null - ? ([id, promise]) => promise.then(p => [id, p]) - : ([id, promise]) => - Promise.race([ - promise, + if (promises instanceof Map) { + return new Map( + await Promise.all( + map<[T, Promise], Promise<[T, TPromise | CancellationErrorWithId>]>>( + promises.entries(), + timeout == null + ? ([id, promise]) => promise.then(p => [id, p]) + : ([id, promise]) => + Promise.race([ + promise, - new Promise>>(resolve => - setTimeout( - () => resolve(new CancellationErrorWithId(id, promise, 'TIMED OUT')), - timeout!, - ), + new Promise>>(resolve => + setTimeout( + () => resolve(new CancellationErrorWithId(id, promise, 'TIMED OUT')), + timeout!, ), - ]).then(p => [id, p]), - ), + ), + ]).then(p => [id, p]), ), - ); - } - - return Promise.all( - timeout == null - ? promises - : promises.map(p => - Promise.race([ - p, - new Promise>>(resolve => - setTimeout(() => resolve(new CancellationError(p, 'TIMED OUT')), timeout!), - ), - ]), - ), + ), ); } + + return Promise.all( + timeout == null + ? promises + : promises.map(p => + Promise.race([ + p, + new Promise>>(resolve => + setTimeout(() => resolve(new CancellationError(p, 'TIMED OUT')), timeout!), + ), + ]), + ), + ); } diff --git a/src/system/searchTree.ts b/src/system/searchTree.ts index 9a5715e..d3b4e58 100644 --- a/src/system/searchTree.ts +++ b/src/system/searchTree.ts @@ -1,8 +1,8 @@ /* eslint-disable @typescript-eslint/prefer-nullish-coalescing */ /* eslint-disable @typescript-eslint/strict-boolean-expressions */ 'use strict'; -import { Iterables } from './iterable'; -import { Strings } from './string'; +import { count, map, some } from './iterable'; +import { CharCode } from './string'; // Code stolen from https://github.com/Microsoft/vscode/blob/b3e6d5bb039a4a9362b52a2c8726267ca68cf64e/src/vs/base/common/map.ts#L352 @@ -70,7 +70,7 @@ export class PathIterator implements IKeyIterator { let justSeps = true; for (; this._to < this._value.length; this._to++) { const ch = this._value.charCodeAt(this._to); - if (ch === Strings.CharCode.Slash || ch === Strings.CharCode.Backslash) { + if (ch === CharCode.Slash || ch === CharCode.Backslash) { if (justSeps) { this._from++; } else { @@ -374,7 +374,7 @@ export class TernarySearchTree { count(predicate?: (entry: E) => boolean): number { if (this._root === undefined || this._root.isEmpty()) return 0; - return Iterables.count(this.entries(), predicate === undefined ? undefined : ([e]) => predicate(e)); + return count(this.entries(), predicate === undefined ? undefined : ([e]) => predicate(e)); } entries(): Iterable<[E, string]> { @@ -382,7 +382,7 @@ export class TernarySearchTree { } values(): Iterable { - return Iterables.map(this.entries(), ([e]) => e); + return map(this.entries(), ([e]) => e); } highlander(): [E, string] | undefined { @@ -410,7 +410,7 @@ export class TernarySearchTree { some(predicate: (entry: E) => boolean): boolean { if (this._root === undefined || this._root.isEmpty()) return false; - return Iterables.some(this.entries(), ([e]) => predicate(e)); + return some(this.entries(), ([e]) => predicate(e)); } private *_iterator(node: TernarySearchTreeNode | undefined): IterableIterator<[E, string]> { diff --git a/src/system/string.ts b/src/system/string.ts index 40a4d3d..e424f84 100644 --- a/src/system/string.ts +++ b/src/system/string.ts @@ -4,418 +4,408 @@ import { isWindows } from '../git/shell'; const emptyStr = ''; -export namespace Strings { - export const enum CharCode { - /** - * The `/` character. - */ - Slash = 47, - /** - * The `\` character. - */ - Backslash = 92, - } +export const enum CharCode { + /** + * The `/` character. + */ + Slash = 47, + /** + * The `\` character. + */ + Backslash = 92, +} - export function base64(s: string): string { - const buffer = Buffer.from(s); - return buffer.toString('base64'); - } +export function base64(s: string): string { + const buffer = Buffer.from(s); + return buffer.toString('base64'); +} - const escapeMarkdownRegex = /[\\`*_{}[\]()#+\-.!]/g; - const escapeMarkdownHeaderRegex = /^===/gm; - // const sampleMarkdown = '## message `not code` *not important* _no underline_ \n> don\'t quote me \n- don\'t list me \n+ don\'t list me \n1. don\'t list me \nnot h1 \n=== \nnot h2 \n---\n***\n---\n___'; - const markdownQuotedRegex = /\n/g; +const escapeMarkdownRegex = /[\\`*_{}[\]()#+\-.!]/g; +const escapeMarkdownHeaderRegex = /^===/gm; +// const sampleMarkdown = '## message `not code` *not important* _no underline_ \n> don\'t quote me \n- don\'t list me \n+ don\'t list me \n1. don\'t list me \nnot h1 \n=== \nnot h2 \n---\n***\n---\n___'; +const markdownQuotedRegex = /\n/g; - export function escapeMarkdown(s: string, options: { quoted?: boolean } = {}): string { - s = s - // Escape markdown - .replace(escapeMarkdownRegex, '\\$&') - // Escape markdown header (since the above regex won't match it) - .replace(escapeMarkdownHeaderRegex, '\u200b==='); +export function escapeMarkdown(s: string, options: { quoted?: boolean } = {}): string { + s = s + // Escape markdown + .replace(escapeMarkdownRegex, '\\$&') + // Escape markdown header (since the above regex won't match it) + .replace(escapeMarkdownHeaderRegex, '\u200b==='); - if (!options.quoted) return s; + if (!options.quoted) return s; - // Keep under the same block-quote but with line breaks - return s.replace(markdownQuotedRegex, '\t\n> '); - } + // Keep under the same block-quote but with line breaks + return s.replace(markdownQuotedRegex, '\t\n> '); +} - export function escapeRegex(s: string) { - return s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); - } +export function escapeRegex(s: string) { + return s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); +} - export function getCommonBase(s1: string, s2: string, delimiter: string) { - let char; - let index = 0; - for (let i = 0; i < s1.length; i++) { - char = s1[i]; - if (char !== s2[i]) break; +export function getCommonBase(s1: string, s2: string, delimiter: string) { + let char; + let index = 0; + for (let i = 0; i < s1.length; i++) { + char = s1[i]; + if (char !== s2[i]) break; - if (char === delimiter) { - index = i; - } + if (char === delimiter) { + index = i; } - - return index > 0 ? s1.substring(0, index + 1) : undefined; } - export function getDurationMilliseconds(start: [number, number]) { - const [secs, nanosecs] = process.hrtime(start); - return secs * 1000 + Math.floor(nanosecs / 1000000); - } + return index > 0 ? s1.substring(0, index + 1) : undefined; +} - const superscripts = ['\u00B9', '\u00B2', '\u00B3', '\u2074', '\u2075', '\u2076', '\u2077', '\u2078', '\u2079']; +export function getDurationMilliseconds(start: [number, number]) { + const [secs, nanosecs] = process.hrtime(start); + return secs * 1000 + Math.floor(nanosecs / 1000000); +} - export function getSuperscript(num: number) { - return superscripts[num - 1] ?? ''; - } +const superscripts = ['\u00B9', '\u00B2', '\u00B3', '\u2074', '\u2075', '\u2076', '\u2077', '\u2078', '\u2079']; - const driveLetterNormalizeRegex = /(?<=^\/?)([A-Z])(?=:\/)/; - const pathNormalizeRegex = /\\/g; - const pathStripTrailingSlashRegex = /\/$/g; - const tokenRegex = /\$\{(".*?"|\W*)?([^|]*?)(?:\|(\d+)(-|\?)?)?(".*?"|\W*)?\}/g; - const tokenSanitizeRegex = /\$\{(?:".*?"|\W*)?(\w*?)(?:".*?"|[\W\d]*)\}/g; - // eslint-disable-next-line no-template-curly-in-string - const tokenSanitizeReplacement = '$${this.$1}'; - - export interface TokenOptions { - collapseWhitespace: boolean; - padDirection: 'left' | 'right'; - prefix: string | undefined; - suffix: string | undefined; - truncateTo: number | undefined; - } +export function getSuperscript(num: number) { + return superscripts[num - 1] ?? ''; +} - export function getTokensFromTemplate(template: string) { - const tokens: { key: string; options: TokenOptions }[] = []; - - let match; - do { - match = tokenRegex.exec(template); - if (match == null) break; - - const [, prefix, key, truncateTo, option, suffix] = match; - tokens.push({ - key: key, - options: { - collapseWhitespace: option === '?', - padDirection: option === '-' ? 'left' : 'right', - prefix: - prefix?.length > 1 && prefix?.startsWith('"') && prefix?.endsWith('"') - ? prefix.substr(1, prefix.length - 2) - : prefix, - suffix: suffix, - truncateTo: truncateTo == null ? undefined : parseInt(truncateTo, 10), - }, - }); - } while (true); - - return tokens; - } +const driveLetterNormalizeRegex = /(?<=^\/?)([A-Z])(?=:\/)/; +const pathNormalizeRegex = /\\/g; +const pathStripTrailingSlashRegex = /\/$/g; +const tokenRegex = /\$\{(".*?"|\W*)?([^|]*?)(?:\|(\d+)(-|\?)?)?(".*?"|\W*)?\}/g; +const tokenSanitizeRegex = /\$\{(?:".*?"|\W*)?(\w*?)(?:".*?"|[\W\d]*)\}/g; +// eslint-disable-next-line no-template-curly-in-string +const tokenSanitizeReplacement = '$${this.$1}'; + +export interface TokenOptions { + collapseWhitespace: boolean; + padDirection: 'left' | 'right'; + prefix: string | undefined; + suffix: string | undefined; + truncateTo: number | undefined; +} - const interpolationMap = new Map(); +export function getTokensFromTemplate(template: string) { + const tokens: { key: string; options: TokenOptions }[] = []; + + let match; + do { + match = tokenRegex.exec(template); + if (match == null) break; + + const [, prefix, key, truncateTo, option, suffix] = match; + tokens.push({ + key: key, + options: { + collapseWhitespace: option === '?', + padDirection: option === '-' ? 'left' : 'right', + prefix: + prefix?.length > 1 && prefix?.startsWith('"') && prefix?.endsWith('"') + ? prefix.substr(1, prefix.length - 2) + : prefix, + suffix: suffix, + truncateTo: truncateTo == null ? undefined : parseInt(truncateTo, 10), + }, + }); + } while (true); + + return tokens; +} - export function interpolate(template: string, context: object | undefined): string { - if (template == null || template.length === 0) return template; - if (context == null) return template.replace(tokenSanitizeRegex, emptyStr); +const interpolationMap = new Map(); - let fn = interpolationMap.get(template); - if (fn == null) { - // eslint-disable-next-line @typescript-eslint/no-implied-eval - fn = new Function(`return \`${template.replace(tokenSanitizeRegex, tokenSanitizeReplacement)}\`;`); - interpolationMap.set(template, fn); - } +export function interpolate(template: string, context: object | undefined): string { + if (template == null || template.length === 0) return template; + if (context == null) return template.replace(tokenSanitizeRegex, emptyStr); - return fn.call(context); + let fn = interpolationMap.get(template); + if (fn == null) { + // eslint-disable-next-line @typescript-eslint/no-implied-eval + fn = new Function(`return \`${template.replace(tokenSanitizeRegex, tokenSanitizeReplacement)}\`;`); + interpolationMap.set(template, fn); } - export function* lines(s: string): IterableIterator { - let i = 0; - while (i < s.length) { - let j = s.indexOf('\n', i); - if (j === -1) { - j = s.length; - } + return fn.call(context); +} - yield s.substring(i, j); - i = j + 1; +export function* lines(s: string): IterableIterator { + let i = 0; + while (i < s.length) { + let j = s.indexOf('\n', i); + if (j === -1) { + j = s.length; } - } - export function md5(s: string, encoding: HexBase64Latin1Encoding = 'base64'): string { - return createHash('md5').update(s).digest(encoding); + yield s.substring(i, j); + i = j + 1; } +} - export function normalizePath( - fileName: string, - options: { addLeadingSlash?: boolean; stripTrailingSlash?: boolean } = { stripTrailingSlash: true }, - ) { - if (fileName == null || fileName.length === 0) return fileName; +export function md5(s: string, encoding: HexBase64Latin1Encoding = 'base64'): string { + return createHash('md5').update(s).digest(encoding); +} - let normalized = fileName.replace(pathNormalizeRegex, '/'); +export function normalizePath( + fileName: string, + options: { addLeadingSlash?: boolean; stripTrailingSlash?: boolean } = { stripTrailingSlash: true }, +) { + if (fileName == null || fileName.length === 0) return fileName; - const { addLeadingSlash, stripTrailingSlash } = { stripTrailingSlash: true, ...options }; + let normalized = fileName.replace(pathNormalizeRegex, '/'); - if (stripTrailingSlash) { - normalized = normalized.replace(pathStripTrailingSlashRegex, emptyStr); - } + const { addLeadingSlash, stripTrailingSlash } = { stripTrailingSlash: true, ...options }; - if (addLeadingSlash && normalized.charCodeAt(0) !== CharCode.Slash) { - normalized = `/${normalized}`; - } + if (stripTrailingSlash) { + normalized = normalized.replace(pathStripTrailingSlashRegex, emptyStr); + } - if (isWindows) { - // Ensure that drive casing is normalized (lower case) - normalized = normalized.replace(driveLetterNormalizeRegex, (drive: string) => drive.toLowerCase()); - } + if (addLeadingSlash && normalized.charCodeAt(0) !== CharCode.Slash) { + normalized = `/${normalized}`; + } - return normalized; + if (isWindows) { + // Ensure that drive casing is normalized (lower case) + normalized = normalized.replace(driveLetterNormalizeRegex, (drive: string) => drive.toLowerCase()); } - export function pad(s: string, before: number = 0, after: number = 0, padding: string = '\u00a0') { - if (before === 0 && after === 0) return s; + return normalized; +} - return `${before === 0 ? emptyStr : padding.repeat(before)}${s}${ - after === 0 ? emptyStr : padding.repeat(after) - }`; - } +export function pad(s: string, before: number = 0, after: number = 0, padding: string = '\u00a0') { + if (before === 0 && after === 0) return s; - export function padLeft(s: string, padTo: number, padding: string = '\u00a0', width?: number) { - const diff = padTo - (width ?? getWidth(s)); - return diff <= 0 ? s : padding.repeat(diff) + s; - } + return `${before === 0 ? emptyStr : padding.repeat(before)}${s}${after === 0 ? emptyStr : padding.repeat(after)}`; +} - export function padLeftOrTruncate(s: string, max: number, padding?: string, width?: number) { - width = width ?? getWidth(s); - if (width < max) return padLeft(s, max, padding, width); - if (width > max) return truncate(s, max, undefined, width); - return s; - } +export function padLeft(s: string, padTo: number, padding: string = '\u00a0', width?: number) { + const diff = padTo - (width ?? getWidth(s)); + return diff <= 0 ? s : padding.repeat(diff) + s; +} - export function padRight(s: string, padTo: number, padding: string = '\u00a0', width?: number) { - const diff = padTo - (width ?? getWidth(s)); - return diff <= 0 ? s : s + padding.repeat(diff); - } +export function padLeftOrTruncate(s: string, max: number, padding?: string, width?: number) { + width = width ?? getWidth(s); + if (width < max) return padLeft(s, max, padding, width); + if (width > max) return truncate(s, max, undefined, width); + return s; +} - export function padOrTruncate(s: string, max: number, padding?: string, width?: number) { - const left = max < 0; - max = Math.abs(max); +export function padRight(s: string, padTo: number, padding: string = '\u00a0', width?: number) { + const diff = padTo - (width ?? getWidth(s)); + return diff <= 0 ? s : s + padding.repeat(diff); +} - width = width ?? getWidth(s); - if (width < max) return left ? padLeft(s, max, padding, width) : padRight(s, max, padding, width); - if (width > max) return truncate(s, max, undefined, width); - return s; - } +export function padOrTruncate(s: string, max: number, padding?: string, width?: number) { + const left = max < 0; + max = Math.abs(max); - export function padRightOrTruncate(s: string, max: number, padding?: string, width?: number) { - width = width ?? getWidth(s); - if (width < max) return padRight(s, max, padding, width); - if (width > max) return truncate(s, max); - return s; - } + width = width ?? getWidth(s); + if (width < max) return left ? padLeft(s, max, padding, width) : padRight(s, max, padding, width); + if (width > max) return truncate(s, max, undefined, width); + return s; +} - export function pluralize( - s: string, - count: number, - options?: { number?: string; plural?: string; suffix?: string; zero?: string }, - ) { - if (options == null) return `${count} ${s}${count === 1 ? emptyStr : 's'}`; - - return `${ - count === 0 - ? options.zero != null - ? options.zero - : count - : options.number != null - ? options.number - : count - } ${count === 1 ? s : options.plural ?? `${s}${options.suffix ?? 's'}`}`; - } +export function padRightOrTruncate(s: string, max: number, padding?: string, width?: number) { + width = width ?? getWidth(s); + if (width < max) return padRight(s, max, padding, width); + if (width > max) return truncate(s, max); + return s; +} + +export function pluralize( + s: string, + count: number, + options?: { number?: string; plural?: string; suffix?: string; zero?: string }, +) { + if (options == null) return `${count} ${s}${count === 1 ? emptyStr : 's'}`; + + return `${ + count === 0 ? (options.zero != null ? options.zero : count) : options.number != null ? options.number : count + } ${count === 1 ? s : options.plural ?? `${s}${options.suffix ?? 's'}`}`; +} + +// Removes \ / : * ? " < > | and C0 and C1 control codes +// eslint-disable-next-line no-control-regex +const illegalCharsForFSRegex = /[\\/:*?"<>|\x00-\x1f\x80-\x9f]/g; + +export function sanitizeForFileSystem(s: string, replacement: string = '_') { + if (!s) return s; + return s.replace(illegalCharsForFSRegex, replacement); +} + +export function sha1(s: string, encoding: HexBase64Latin1Encoding = 'base64'): string { + return createHash('sha1').update(s).digest(encoding); +} - // Removes \ / : * ? " < > | and C0 and C1 control codes - // eslint-disable-next-line no-control-regex - const illegalCharsForFSRegex = /[\\/:*?"<>|\x00-\x1f\x80-\x9f]/g; +export function splitLast(s: string, splitter: string) { + const index = s.lastIndexOf(splitter); + if (index === -1) return [s]; - export function sanitizeForFileSystem(s: string, replacement: string = '_') { - if (!s) return s; - return s.replace(illegalCharsForFSRegex, replacement); + return [s.substr(index), s.substring(0, index - 1)]; +} + +export function splitSingle(s: string, splitter: string) { + const parts = s.split(splitter, 1); + const first = parts[0]; + return first.length === s.length ? parts : [first, s.substr(first.length + 1)]; +} + +export function truncate(s: string, truncateTo: number, ellipsis: string = '\u2026', width?: number) { + if (!s) return s; + if (truncateTo <= 1) return ellipsis; + + width = width ?? getWidth(s); + if (width <= truncateTo) return s; + if (width === s.length) return `${s.substring(0, truncateTo - 1)}${ellipsis}`; + + // Skip ahead to start as far as we can by assuming all the double-width characters won't be truncated + let chars = Math.floor(truncateTo / (width / s.length)); + let count = getWidth(s.substring(0, chars)); + while (count < truncateTo) { + count += getWidth(s[chars++]); } - export function sha1(s: string, encoding: HexBase64Latin1Encoding = 'base64'): string { - return createHash('sha1').update(s).digest(encoding); + if (count >= truncateTo) { + chars--; } - export function splitLast(s: string, splitter: string) { - const index = s.lastIndexOf(splitter); - if (index === -1) return [s]; + return `${s.substring(0, chars)}${ellipsis}`; +} + +export function truncateLeft(s: string, truncateTo: number, ellipsis: string = '\u2026', width?: number) { + if (!s) return s; + if (truncateTo <= 1) return ellipsis; - return [s.substr(index), s.substring(0, index - 1)]; + width = width ?? getWidth(s); + if (width <= truncateTo) return s; + if (width === s.length) return `${ellipsis}${s.substring(width - truncateTo)}`; + + // Skip ahead to start as far as we can by assuming all the double-width characters won't be truncated + let chars = Math.floor(truncateTo / (width / s.length)); + let count = getWidth(s.substring(0, chars)); + while (count < truncateTo) { + count += getWidth(s[chars++]); } - export function splitSingle(s: string, splitter: string) { - const parts = s.split(splitter, 1); - const first = parts[0]; - return first.length === s.length ? parts : [first, s.substr(first.length + 1)]; + if (count >= truncateTo) { + chars--; } - export function truncate(s: string, truncateTo: number, ellipsis: string = '\u2026', width?: number) { - if (!s) return s; - if (truncateTo <= 1) return ellipsis; + return `${ellipsis}${s.substring(s.length - chars)}`; +} - width = width ?? getWidth(s); - if (width <= truncateTo) return s; - if (width === s.length) return `${s.substring(0, truncateTo - 1)}${ellipsis}`; +export function truncateMiddle(s: string, truncateTo: number, ellipsis: string = '\u2026') { + if (!s) return s; + if (truncateTo <= 1) return ellipsis; - // Skip ahead to start as far as we can by assuming all the double-width characters won't be truncated - let chars = Math.floor(truncateTo / (width / s.length)); - let count = getWidth(s.substring(0, chars)); - while (count < truncateTo) { - count += getWidth(s[chars++]); - } + const width = getWidth(s); + if (width <= truncateTo) return s; - if (count >= truncateTo) { - chars--; - } + return `${s.slice(0, Math.floor(truncateTo / 2) - 1)}${ellipsis}${s.slice(width - Math.ceil(truncateTo / 2))}`; +} - return `${s.substring(0, chars)}${ellipsis}`; - } +// Lifted from https://github.com/chalk/ansi-regex +// eslint-disable-next-line no-control-regex +const ansiRegex = /[\u001B\u009B][[\]()#;?]*(?:(?:(?:[a-zA-Z\d]*(?:;[-a-zA-Z\d/#&.:=?%@~_]*)*)?\u0007)|(?:(?:\d{1,4}(?:;\d{0,4})*)?[\dA-PR-TZcf-ntqry=><~]))/g; +const containsNonAsciiRegex = /[^\x20-\x7F\u00a0\u2026]/; - export function truncateLeft(s: string, truncateTo: number, ellipsis: string = '\u2026', width?: number) { - if (!s) return s; - if (truncateTo <= 1) return ellipsis; +export function getWidth(s: string): number { + if (s == null || s.length === 0) return 0; - width = width ?? getWidth(s); - if (width <= truncateTo) return s; - if (width === s.length) return `${ellipsis}${s.substring(width - truncateTo)}`; + // Shortcut to avoid needless string `RegExp`s, replacements, and allocations + if (!containsNonAsciiRegex.test(s)) return s.length; - // Skip ahead to start as far as we can by assuming all the double-width characters won't be truncated - let chars = Math.floor(truncateTo / (width / s.length)); - let count = getWidth(s.substring(0, chars)); - while (count < truncateTo) { - count += getWidth(s[chars++]); - } + s = s.replace(ansiRegex, emptyStr); - if (count >= truncateTo) { - chars--; - } + let count = 0; + let emoji = 0; + let joiners = 0; - return `${ellipsis}${s.substring(s.length - chars)}`; - } + const graphemes = [...s]; + for (let i = 0; i < graphemes.length; i++) { + const code = graphemes[i].codePointAt(0)!; - export function truncateMiddle(s: string, truncateTo: number, ellipsis: string = '\u2026') { - if (!s) return s; - if (truncateTo <= 1) return ellipsis; + // Ignore control characters + if (code <= 0x1f || (code >= 0x7f && code <= 0x9f)) continue; - const width = getWidth(s); - if (width <= truncateTo) return s; + // Ignore combining characters + if (code >= 0x300 && code <= 0x36f) continue; - return `${s.slice(0, Math.floor(truncateTo / 2) - 1)}${ellipsis}${s.slice(width - Math.ceil(truncateTo / 2))}`; - } + // https://stackoverflow.com/questions/30757193/find-out-if-character-in-string-is-emoji + if ( + (code >= 0x1f600 && code <= 0x1f64f) || // Emoticons + (code >= 0x1f300 && code <= 0x1f5ff) || // Misc Symbols and Pictographs + (code >= 0x1f680 && code <= 0x1f6ff) || // Transport and Map + (code >= 0x2600 && code <= 0x26ff) || // Misc symbols + (code >= 0x2700 && code <= 0x27bf) || // Dingbats + (code >= 0xfe00 && code <= 0xfe0f) || // Variation Selectors + (code >= 0x1f900 && code <= 0x1f9ff) || // Supplemental Symbols and Pictographs + (code >= 65024 && code <= 65039) || // Variation selector + (code >= 8400 && code <= 8447) // Combining Diacritical Marks for Symbols + ) { + if (code >= 0x1f3fb && code <= 0x1f3ff) continue; // emoji modifier fitzpatrick type - // Lifted from https://github.com/chalk/ansi-regex - // eslint-disable-next-line no-control-regex - const ansiRegex = /[\u001B\u009B][[\]()#;?]*(?:(?:(?:[a-zA-Z\d]*(?:;[-a-zA-Z\d/#&.:=?%@~_]*)*)?\u0007)|(?:(?:\d{1,4}(?:;\d{0,4})*)?[\dA-PR-TZcf-ntqry=><~]))/g; - const containsNonAsciiRegex = /[^\x20-\x7F\u00a0\u2026]/; - - export function getWidth(s: string): number { - if (s == null || s.length === 0) return 0; - - // Shortcut to avoid needless string `RegExp`s, replacements, and allocations - if (!containsNonAsciiRegex.test(s)) return s.length; - - s = s.replace(ansiRegex, emptyStr); - - let count = 0; - let emoji = 0; - let joiners = 0; - - const graphemes = [...s]; - for (let i = 0; i < graphemes.length; i++) { - const code = graphemes[i].codePointAt(0)!; - - // Ignore control characters - if (code <= 0x1f || (code >= 0x7f && code <= 0x9f)) continue; - - // Ignore combining characters - if (code >= 0x300 && code <= 0x36f) continue; - - // https://stackoverflow.com/questions/30757193/find-out-if-character-in-string-is-emoji - if ( - (code >= 0x1f600 && code <= 0x1f64f) || // Emoticons - (code >= 0x1f300 && code <= 0x1f5ff) || // Misc Symbols and Pictographs - (code >= 0x1f680 && code <= 0x1f6ff) || // Transport and Map - (code >= 0x2600 && code <= 0x26ff) || // Misc symbols - (code >= 0x2700 && code <= 0x27bf) || // Dingbats - (code >= 0xfe00 && code <= 0xfe0f) || // Variation Selectors - (code >= 0x1f900 && code <= 0x1f9ff) || // Supplemental Symbols and Pictographs - (code >= 65024 && code <= 65039) || // Variation selector - (code >= 8400 && code <= 8447) // Combining Diacritical Marks for Symbols - ) { - if (code >= 0x1f3fb && code <= 0x1f3ff) continue; // emoji modifier fitzpatrick type - - emoji++; - count += 2; - continue; - } - - // Ignore zero-width joiners '\u200d' - if (code === 8205) { - joiners++; - count -= 2; - continue; - } - - // Surrogates - if (code > 0xffff) { - i++; - } - - count += isFullwidthCodePoint(code) ? 2 : 1; + emoji++; + count += 2; + continue; } - const offset = emoji - joiners; - if (offset > 1) { - count += offset - 1; + // Ignore zero-width joiners '\u200d' + if (code === 8205) { + joiners++; + count -= 2; + continue; } - return count; - } - function isFullwidthCodePoint(cp: number) { - // code points are derived from: - // http://www.unix.org/Public/UNIDATA/EastAsianWidth.txt - if ( - cp >= 0x1100 && - (cp <= 0x115f || // Hangul Jamo - cp === 0x2329 || // LEFT-POINTING ANGLE BRACKET - cp === 0x232a || // RIGHT-POINTING ANGLE BRACKET - // CJK Radicals Supplement .. Enclosed CJK Letters and Months - (cp >= 0x2e80 && cp <= 0x3247 && cp !== 0x303f) || - // Enclosed CJK Letters and Months .. CJK Unified Ideographs Extension A - (cp >= 0x3250 && cp <= 0x4dbf) || - // CJK Unified Ideographs .. Yi Radicals - (cp >= 0x4e00 && cp <= 0xa4c6) || - // Hangul Jamo Extended-A - (cp >= 0xa960 && cp <= 0xa97c) || - // Hangul Syllables - (cp >= 0xac00 && cp <= 0xd7a3) || - // CJK Compatibility Ideographs - (cp >= 0xf900 && cp <= 0xfaff) || - // Vertical Forms - (cp >= 0xfe10 && cp <= 0xfe19) || - // CJK Compatibility Forms .. Small Form Variants - (cp >= 0xfe30 && cp <= 0xfe6b) || - // Halfwidth and Fullwidth Forms - (cp >= 0xff01 && cp <= 0xff60) || - (cp >= 0xffe0 && cp <= 0xffe6) || - // Kana Supplement - (cp >= 0x1b000 && cp <= 0x1b001) || - // Enclosed Ideographic Supplement - (cp >= 0x1f200 && cp <= 0x1f251) || - // CJK Unified Ideographs Extension B .. Tertiary Ideographic Plane - (cp >= 0x20000 && cp <= 0x3fffd)) - ) { - return true; + // Surrogates + if (code > 0xffff) { + i++; } - return false; + count += isFullwidthCodePoint(code) ? 2 : 1; + } + + const offset = emoji - joiners; + if (offset > 1) { + count += offset - 1; } + return count; +} + +function isFullwidthCodePoint(cp: number) { + // code points are derived from: + // http://www.unix.org/Public/UNIDATA/EastAsianWidth.txt + if ( + cp >= 0x1100 && + (cp <= 0x115f || // Hangul Jamo + cp === 0x2329 || // LEFT-POINTING ANGLE BRACKET + cp === 0x232a || // RIGHT-POINTING ANGLE BRACKET + // CJK Radicals Supplement .. Enclosed CJK Letters and Months + (cp >= 0x2e80 && cp <= 0x3247 && cp !== 0x303f) || + // Enclosed CJK Letters and Months .. CJK Unified Ideographs Extension A + (cp >= 0x3250 && cp <= 0x4dbf) || + // CJK Unified Ideographs .. Yi Radicals + (cp >= 0x4e00 && cp <= 0xa4c6) || + // Hangul Jamo Extended-A + (cp >= 0xa960 && cp <= 0xa97c) || + // Hangul Syllables + (cp >= 0xac00 && cp <= 0xd7a3) || + // CJK Compatibility Ideographs + (cp >= 0xf900 && cp <= 0xfaff) || + // Vertical Forms + (cp >= 0xfe10 && cp <= 0xfe19) || + // CJK Compatibility Forms .. Small Form Variants + (cp >= 0xfe30 && cp <= 0xfe6b) || + // Halfwidth and Fullwidth Forms + (cp >= 0xff01 && cp <= 0xff60) || + (cp >= 0xffe0 && cp <= 0xffe6) || + // Kana Supplement + (cp >= 0x1b000 && cp <= 0x1b001) || + // Enclosed Ideographic Supplement + (cp >= 0x1f200 && cp <= 0x1f251) || + // CJK Unified Ideographs Extension B .. Tertiary Ideographic Plane + (cp >= 0x20000 && cp <= 0x3fffd)) + ) { + return true; + } + + return false; } diff --git a/src/system/version.ts b/src/system/version.ts index 78fd2fe..0865ad1 100644 --- a/src/system/version.ts +++ b/src/system/version.ts @@ -1,59 +1,52 @@ 'use strict'; -export namespace Versions { - declare type VersionComparisonResult = -1 | 0 | 1; - - export interface Version { - major: number; - minor: number; - patch: number; - pre?: string; - } +declare type VersionComparisonResult = -1 | 0 | 1; - export function compare(v1: string | Version, v2: string | Version): VersionComparisonResult { - if (typeof v1 === 'string') { - v1 = fromString(v1); - } - if (typeof v2 === 'string') { - v2 = fromString(v2); - } +export interface Version { + major: number; + minor: number; + patch: number; + pre?: string; +} - if (v1.major > v2.major) return 1; - if (v1.major < v2.major) return -1; +export function compare(v1: string | Version, v2: string | Version): VersionComparisonResult { + if (typeof v1 === 'string') { + v1 = fromString(v1); + } + if (typeof v2 === 'string') { + v2 = fromString(v2); + } - if (v1.minor > v2.minor) return 1; - if (v1.minor < v2.minor) return -1; + if (v1.major > v2.major) return 1; + if (v1.major < v2.major) return -1; - if (v1.patch > v2.patch) return 1; - if (v1.patch < v2.patch) return -1; + if (v1.minor > v2.minor) return 1; + if (v1.minor < v2.minor) return -1; - if (v1.pre === undefined && v2.pre !== undefined) return 1; - if (v1.pre !== undefined && v2.pre === undefined) return -1; + if (v1.patch > v2.patch) return 1; + if (v1.patch < v2.patch) return -1; - if (v1.pre !== undefined && v2.pre !== undefined) { - return v1.pre.localeCompare(v2.pre) as VersionComparisonResult; - } + if (v1.pre === undefined && v2.pre !== undefined) return 1; + if (v1.pre !== undefined && v2.pre === undefined) return -1; - return 0; + if (v1.pre !== undefined && v2.pre !== undefined) { + return v1.pre.localeCompare(v2.pre) as VersionComparisonResult; } - export function from( - major: string | number, - minor: string | number, - patch?: string | number, - pre?: string, - ): Version { - return { - major: typeof major === 'string' ? parseInt(major, 10) : major, - minor: typeof minor === 'string' ? parseInt(minor, 10) : minor, - patch: patch == null ? 0 : typeof patch === 'string' ? parseInt(patch, 10) : patch, - pre: pre, - }; - } + return 0; +} - export function fromString(version: string): Version { - const [ver, pre] = version.split('-'); - const [major, minor, patch] = ver.split('.'); - return from(major, minor, patch, pre); - } +export function from(major: string | number, minor: string | number, patch?: string | number, pre?: string): Version { + return { + major: typeof major === 'string' ? parseInt(major, 10) : major, + minor: typeof minor === 'string' ? parseInt(minor, 10) : minor, + patch: patch == null ? 0 : typeof patch === 'string' ? parseInt(patch, 10) : patch, + pre: pre, + }; +} + +export function fromString(version: string): Version { + const [ver, pre] = version.split('-'); + const [major, minor, patch] = ver.split('.'); + return from(major, minor, patch, pre); } diff --git a/src/trackers/documentTracker.ts b/src/trackers/documentTracker.ts index 9f4f3aa..ffcb6fd 100644 --- a/src/trackers/documentTracker.ts +++ b/src/trackers/documentTracker.ts @@ -19,7 +19,7 @@ import { import { configuration } from '../configuration'; import { CommandContext, DocumentSchemes, isActiveDocument, isTextEditor, setCommandContext } from '../constants'; import { GitUri } from '../git/gitUri'; -import { Deferrable, Functions } from '../system'; +import { Functions } from '../system'; import { DocumentBlameStateChangeEvent, TrackedDocument } from './trackedDocument'; export * from './trackedDocument'; @@ -315,8 +315,12 @@ export class DocumentTracker implements Disposable { return doc; } - private _dirtyIdleTriggeredDebounced: Deferrable<(e: DocumentDirtyIdleTriggerEvent) => void> | undefined; - private _dirtyStateChangedDebounced: Deferrable<(e: DocumentDirtyStateChangeEvent) => void> | undefined; + private _dirtyIdleTriggeredDebounced: + | Functions.Deferrable<(e: DocumentDirtyIdleTriggerEvent) => void> + | undefined; + private _dirtyStateChangedDebounced: + | Functions.Deferrable<(e: DocumentDirtyStateChangeEvent) => void> + | undefined; private fireDocumentDirtyStateChanged(e: DocumentDirtyStateChangeEvent) { if (e.dirty) { setImmediate(async () => { diff --git a/src/trackers/lineTracker.ts b/src/trackers/lineTracker.ts index d2ce299..8bbec12 100644 --- a/src/trackers/lineTracker.ts +++ b/src/trackers/lineTracker.ts @@ -1,7 +1,7 @@ 'use strict'; import { Disposable, Event, EventEmitter, Selection, TextEditor, TextEditorSelectionChangeEvent, window } from 'vscode'; import { isTextEditor } from '../constants'; -import { debug, Deferrable, Functions } from '../system'; +import { debug, Functions } from '../system'; export interface LinesChangeEvent { readonly editor: TextEditor | undefined; @@ -199,7 +199,7 @@ export class LineTracker implements Disposable { this.onLinesChanged({ editor: this._editor, selections: this.selections, reason: reason }); } - private _linesChangedDebounced: Deferrable<(e: LinesChangeEvent) => void> | undefined; + private _linesChangedDebounced: Functions.Deferrable<(e: LinesChangeEvent) => void> | undefined; private onLinesChanged(e: LinesChangeEvent) { if (e.selections === undefined) {