2020-02-25 12:26:21 +01:00
|
|
|
|
2020-05-10 11:58:07 +02:00
|
|
|
import * as path from "path"
|
|
|
|
import * as fs from "fs"
|
2020-05-28 14:08:49 +02:00
|
|
|
import * as os from "os"
|
|
|
|
|
2020-05-10 23:50:42 +02:00
|
|
|
import moment from "moment"
|
|
|
|
import chalk from "chalk"
|
2020-05-28 14:08:49 +02:00
|
|
|
import { LOG_DATETIME_FORMAT } from "./constants"
|
2020-05-10 23:50:42 +02:00
|
|
|
|
2020-05-26 21:01:38 +02:00
|
|
|
export function isPowerOf(x: number, n: number):boolean {
|
|
|
|
const a = Math.log(x) / Math.log(n);
|
|
|
|
return Math.pow(a, n) == x;
|
|
|
|
}
|
|
|
|
|
2020-05-28 14:08:49 +02:00
|
|
|
|
|
|
|
export function some<T>(iterator: Iterator<T>, pred: (value: T) => boolean): boolean {
|
|
|
|
while (true) {
|
|
|
|
const { value, done } = iterator.next();
|
|
|
|
if (done) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (pred(value)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-05-26 21:01:38 +02:00
|
|
|
export function every<T>(iterator: Iterator<T>, pred: (value: T) => boolean): boolean {
|
|
|
|
while (true) {
|
|
|
|
const { value, done } = iterator.next();
|
|
|
|
if (done) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (!pred(value)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-05-23 14:18:20 +02:00
|
|
|
export function assert(test: boolean): void {
|
|
|
|
if (!test) {
|
|
|
|
throw new Error(`Invariant violation: an internal sanity check failed.`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-10 15:56:34 +02:00
|
|
|
export interface JsonArray extends Array<Json> { };
|
|
|
|
export interface JsonObject { [key: string]: Json }
|
|
|
|
export type Json = null | string | boolean | number | JsonArray | JsonObject;
|
|
|
|
|
2020-05-25 15:52:11 +02:00
|
|
|
export function isInsideDirectory(filepath: string, rootDir: string): boolean {
|
|
|
|
const relPath = path.relative(rootDir, filepath)
|
|
|
|
return !relPath.startsWith('..');
|
|
|
|
}
|
|
|
|
|
|
|
|
export function stripExtensions(filepath: string) {
|
|
|
|
const i = filepath.indexOf('.')
|
|
|
|
return i !== -1
|
|
|
|
? filepath.substring(0, i)
|
|
|
|
: filepath;
|
|
|
|
}
|
|
|
|
|
2020-05-25 11:29:19 +02:00
|
|
|
export function isString(value: any): boolean {
|
|
|
|
return typeof value === 'string';
|
|
|
|
}
|
|
|
|
|
|
|
|
export function hasOwnProperty<T extends object, K extends PropertyKey>(obj: T, key: K): boolean {
|
|
|
|
return Object.prototype.hasOwnProperty.call(obj, key);
|
|
|
|
}
|
|
|
|
|
2020-05-25 15:52:11 +02:00
|
|
|
export function countDigits(x: number, base: number = 10) {
|
|
|
|
if (x === 0) {
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
return Math.ceil(Math.log(x+1) / Math.log(base))
|
|
|
|
}
|
|
|
|
|
2020-05-10 23:50:42 +02:00
|
|
|
export function uniq<T>(elements: T[]): T[] {
|
|
|
|
const out: T[] = [];
|
|
|
|
const visited = new Set<T>();
|
|
|
|
for (const element of elements) {
|
|
|
|
if (visited.has(element)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
visited.add(element);
|
|
|
|
out.push(element);
|
|
|
|
}
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
|
2020-05-26 22:58:31 +02:00
|
|
|
function getTag(value: any) {
|
|
|
|
if (value == null) {
|
|
|
|
return value === undefined ? '[object Undefined]' : '[object Null]'
|
|
|
|
}
|
|
|
|
return toString.call(value)
|
|
|
|
}
|
|
|
|
|
|
|
|
function isObjectLike(value: any) {
|
|
|
|
return typeof value === 'object' && value !== null;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function isPlainObject(value: any): value is object {
|
|
|
|
if (!isObjectLike(value) || getTag(value) != '[object Object]') {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if (Object.getPrototypeOf(value) === null) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
let proto = value
|
|
|
|
while (Object.getPrototypeOf(proto) !== null) {
|
|
|
|
proto = Object.getPrototypeOf(proto)
|
|
|
|
}
|
|
|
|
return Object.getPrototypeOf(value) === proto
|
|
|
|
}
|
2020-05-22 19:50:47 +02:00
|
|
|
|
2020-05-28 14:08:49 +02:00
|
|
|
export function mapValues<T extends object, R extends PropertyKey>(obj: T, func: (value: keyof T) => R): { [K in keyof T]: R } {
|
|
|
|
const newObj: any = {}
|
|
|
|
for (const key of Object.keys(obj)) {
|
|
|
|
newObj[key] = func((obj as any)[key]);
|
|
|
|
}
|
|
|
|
return newObj;
|
|
|
|
}
|
|
|
|
|
|
|
|
export const prettyPrintTag = Symbol('pretty printer');
|
|
|
|
|
|
|
|
export function prettyPrint(value: any): string {
|
|
|
|
if (isObjectLike(value) && value[prettyPrintTag] !== undefined) {
|
|
|
|
return value[prettyPrintTag]();
|
|
|
|
}
|
|
|
|
return value.toString();
|
|
|
|
}
|
|
|
|
|
2020-05-22 19:50:47 +02:00
|
|
|
export class FastStringMap<K extends PropertyKey, V> {
|
|
|
|
|
|
|
|
private mapping = Object.create(null);
|
|
|
|
|
2020-05-26 22:58:31 +02:00
|
|
|
public clear(): void {
|
|
|
|
this.mapping.clear();
|
|
|
|
}
|
2020-05-28 14:08:49 +02:00
|
|
|
|
|
|
|
public *[Symbol.iterator](): IterableIterator<[K, V]> {
|
|
|
|
for (const key of Object.keys(this.mapping)) {
|
|
|
|
yield [key as K, this.mapping[key]];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-22 19:50:47 +02:00
|
|
|
public get(key: K): V {
|
|
|
|
if (!(key in this.mapping)) {
|
|
|
|
throw new Error(`No value found for key '${key}'.`);
|
|
|
|
}
|
|
|
|
return this.mapping[key];
|
|
|
|
}
|
|
|
|
|
2020-05-28 14:08:49 +02:00
|
|
|
public *keys(): IterableIterator<K> {
|
|
|
|
for (const key of Object.keys(this.mapping)) {
|
|
|
|
yield key as K;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-23 14:18:20 +02:00
|
|
|
public *values(): IterableIterator<V> {
|
|
|
|
for (const key of Object.keys(this.mapping)) {
|
|
|
|
yield this.mapping[key];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-22 19:50:47 +02:00
|
|
|
public set(key: K, value: V): void {
|
|
|
|
if (key in this.mapping) {
|
|
|
|
throw new Error(`A value for key '${key}' already exists.`);
|
|
|
|
}
|
|
|
|
this.mapping[key] = value
|
|
|
|
}
|
|
|
|
|
|
|
|
public has(key: K): boolean {
|
|
|
|
return key in this.mapping;
|
|
|
|
}
|
|
|
|
|
|
|
|
public delete(key: K): void {
|
|
|
|
if (!(key in this.mapping)) {
|
|
|
|
throw new Error(`No value found for key '${key}'.`);
|
|
|
|
}
|
|
|
|
delete this.mapping[key];
|
|
|
|
}
|
|
|
|
|
2020-02-26 18:53:28 +01:00
|
|
|
}
|
|
|
|
|
2020-05-10 23:50:42 +02:00
|
|
|
class DeepMap {
|
|
|
|
|
|
|
|
private rootMap = new Map<any, any>();
|
|
|
|
|
|
|
|
public has(key: any[]) {
|
|
|
|
let curr = this.rootMap;
|
|
|
|
for (const element of key) {
|
|
|
|
if (!curr.has(element)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
curr = curr.get(element)!;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
public get(key: any[]) {
|
|
|
|
let curr = this.rootMap;
|
|
|
|
for (const element of key) {
|
|
|
|
if (!curr.has(element)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
curr = curr.get(element)!;
|
|
|
|
}
|
|
|
|
return curr;
|
|
|
|
}
|
|
|
|
|
|
|
|
public set(key: any[], value: any) {
|
|
|
|
let curr = this.rootMap;
|
|
|
|
for (const element of key.slice(0, -1)) {
|
|
|
|
if (!curr.has(element)) {
|
|
|
|
curr.set(element, new Map());
|
|
|
|
}
|
|
|
|
curr = curr.get(element)!;
|
|
|
|
}
|
|
|
|
curr.set(key[key.length-1], value);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-05-23 23:08:24 +02:00
|
|
|
export function memoize(hasher: (...args: any[]) => string) {
|
|
|
|
return function (target: any, key: PropertyKey) {
|
|
|
|
const origMethod = target[key];
|
|
|
|
target[key] = function wrapper(...args: any[]) {
|
|
|
|
if (this.__MEMOIZE_CACHE === undefined) {
|
|
|
|
this.__MEMOIZE_CACHE = Object.create(null);
|
|
|
|
}
|
|
|
|
if (this.__MEMOIZE_CACHE[key] === undefined) {
|
|
|
|
this.__MEMOIZE_CACHE[key] = Object.create(null);
|
|
|
|
}
|
|
|
|
const hashed = hasher(...args);
|
|
|
|
const cache = this.__MEMOIZE_CACHE[key];
|
|
|
|
if (hashed in cache) {
|
|
|
|
return cache[hashed];
|
|
|
|
}
|
|
|
|
const result = origMethod.apply(this, args);
|
|
|
|
cache[hashed] = result;
|
|
|
|
return result;
|
2020-05-10 23:50:42 +02:00
|
|
|
}
|
2020-05-23 23:08:24 +02:00
|
|
|
return target;
|
2020-05-10 23:50:42 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-25 12:26:21 +01:00
|
|
|
export interface Stream<T> {
|
|
|
|
get(): T;
|
|
|
|
peek(count?: number): T;
|
|
|
|
}
|
|
|
|
|
|
|
|
export class StreamWrapper<T> {
|
|
|
|
|
|
|
|
offset = 0
|
|
|
|
|
|
|
|
constructor(protected data: T[], protected createSentry: () => T) {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
peek(count = 1) {
|
|
|
|
const offset = this.offset + (count - 1);
|
|
|
|
if (offset >= this.data.length) {
|
|
|
|
return this.createSentry();
|
|
|
|
}
|
|
|
|
return this.data[offset];
|
|
|
|
}
|
|
|
|
|
|
|
|
get() {
|
|
|
|
if (this.offset >= this.data.length) {
|
|
|
|
return this.createSentry();
|
|
|
|
}
|
|
|
|
return this.data[this.offset++];
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-05-28 14:08:49 +02:00
|
|
|
export function expandPath(filepath: string) {
|
|
|
|
let out = ''
|
|
|
|
for (const ch of filepath) {
|
|
|
|
if (ch === '~') {
|
|
|
|
out += os.homedir();
|
|
|
|
} else {
|
|
|
|
out += ch;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return out;
|
|
|
|
}
|
2020-05-10 23:50:42 +02:00
|
|
|
|
|
|
|
export function verbose(message: string) {
|
2020-05-28 14:08:49 +02:00
|
|
|
console.error(chalk.gray('[') + chalk.magenta('verb') + ' ' + chalk.gray(moment().format(LOG_DATETIME_FORMAT) + ']') + ' ' + message);
|
2020-05-10 23:50:42 +02:00
|
|
|
}
|
|
|
|
|
2020-05-25 17:46:01 +02:00
|
|
|
export function warn(message: string) {
|
|
|
|
console.error(chalk.gray('[') + chalk.red('warn') + ' ' + chalk.gray(moment().format(DATETIME_FORMAT) + ']') + ' ' + message);
|
|
|
|
}
|
|
|
|
|
2020-05-28 14:08:49 +02:00
|
|
|
export function error(message: string) {
|
|
|
|
console.error(chalk.gray('[') + chalk.red('erro') + ' ' + chalk.gray(moment().format(DATETIME_FORMAT) + ']') + ' ' + message);
|
|
|
|
}
|
|
|
|
|
2020-05-10 11:58:07 +02:00
|
|
|
export function upsearchSync(filename: string, startDir = '.') {
|
|
|
|
let currDir = startDir;
|
|
|
|
while (true) {
|
|
|
|
const filePath = path.join(currDir, filename);
|
|
|
|
if (fs.existsSync(filePath)) {
|
|
|
|
return filePath
|
|
|
|
}
|
|
|
|
const { root, dir } = path.parse(currDir);
|
|
|
|
if (dir === root) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
currDir = dir;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-10 15:56:34 +02:00
|
|
|
export function getFileStem(filepath: string): string {
|
|
|
|
return path.basename(filepath).split('.')[0];
|
2020-05-10 11:58:07 +02:00
|
|
|
}
|
|
|
|
|
2020-05-24 21:50:29 +02:00
|
|
|
export function enumerate(elements: string[]) {
|
2020-05-22 21:29:14 +02:00
|
|
|
if (elements.length === 1) {
|
|
|
|
return elements[0]
|
|
|
|
} else {
|
|
|
|
return elements.slice(0, elements.length-1).join(', ') + ' or ' + elements[elements.length-1]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-24 21:50:29 +02:00
|
|
|
export function escapeChar(ch: string) {
|
2020-05-22 21:29:14 +02:00
|
|
|
switch (ch) {
|
|
|
|
case '\a': return '\\a';
|
|
|
|
case '\b': return '\\b';
|
|
|
|
case '\f': return '\\f';
|
|
|
|
case '\n': return '\\n';
|
|
|
|
case '\r': return '\\r';
|
|
|
|
case '\t': return '\\t';
|
|
|
|
case '\v': return '\\v';
|
|
|
|
case '\0': return '\\0';
|
|
|
|
case '\'': return '\\\'';
|
|
|
|
default:
|
|
|
|
const code = ch.charCodeAt(0);
|
|
|
|
if (code >= 0x20 && code <= 0x7E) {
|
|
|
|
return ch
|
|
|
|
} else if (code < 0x7F) {
|
|
|
|
return `\\x${code.toString(16).padStart(2, '0')}`
|
|
|
|
} else {
|
|
|
|
return `\\u${code.toString(16).padStart(4, '0')}`
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-23 21:15:20 +02:00
|
|
|
export interface MapLike<T> {
|
|
|
|
[key: string]: T;
|
|
|
|
}
|
|
|
|
|
2020-05-26 21:01:38 +02:00
|
|
|
export type FormatArg = any;
|
2020-05-23 21:15:20 +02:00
|
|
|
|
|
|
|
export function format(message: string, data: MapLike<FormatArg>) {
|
|
|
|
|
|
|
|
let out = ''
|
|
|
|
|
|
|
|
let name = '';
|
|
|
|
let insideParam = false;
|
|
|
|
|
|
|
|
for (const ch of message) {
|
|
|
|
if (insideParam) {
|
|
|
|
if (ch === '}') {
|
|
|
|
out += data[name]!.toString();
|
|
|
|
reset();
|
2020-05-23 22:23:17 +02:00
|
|
|
} else {
|
|
|
|
name += ch;
|
2020-05-23 21:15:20 +02:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (ch === '{') {
|
|
|
|
insideParam = true;
|
|
|
|
} else {
|
|
|
|
out += ch;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return out;
|
|
|
|
|
|
|
|
function reset() {
|
|
|
|
name = '';
|
|
|
|
insideParam = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|