1// Copyright (c) Jupyter Development Team. 2// Distributed under the terms of the Modified BSD License. 3'use strict'; 4 5export 6interface DeepCopyableObject { 7 [key: string]: any | undefined; 8 prototype?: DeepCopyableObject; 9} 10 11export 12type DeepCopyableValue = DeepCopyableObject | DeepCopyableObject[] | string | number | boolean | null; 13 14/** 15 * Check whether a value is in an array. 16 */ 17export 18function valueIn(value: any, array: Array<any>) { 19 return array.indexOf(value) >= 0; 20} 21 22 23/** 24 * Check whether array is null or empty, and type guards agains null 25 */ 26export 27function hasEntries<T>(array: T[] | null): array is T[] { 28 return array !== null && array.length !== 0; 29} 30 31 32/** 33 * Splits a multinline string into an array of lines 34 * 35 * @export 36 * @param {string} multiline 37 * @returns {string[]} 38 */ 39export 40function splitLines(multiline: string): string[] { 41 // Split lines (retaining newlines) 42 // We use !postfix, as we also match empty string, 43 // so we are guaranteed to get at elast one match 44 return multiline.match(/^.*(\r\n|\r|\n|$)/gm)!; 45} 46 47/** 48 * Deepcopy routine for JSON-able data types 49 */ 50export function deepCopy(obj: null): null; 51export function deepCopy<T extends DeepCopyableValue>(obj: T): T; 52export function deepCopy<T extends DeepCopyableValue>(obj: T | null): T | null; 53export function deepCopy<T extends DeepCopyableValue>(obj: T | null): T | null { 54 if (typeof obj !== 'object') { 55 if (valueIn(typeof obj, ['string', 'number', 'boolean'])) { 56 return obj; 57 } 58 throw new TypeError('Cannot deepcopy non-object'); 59 } 60 if (obj === null) { 61 return null; 62 } else if (Array.isArray(obj)) { 63 let l = obj.length; 64 let o = new Array(l); 65 for (let i = 0; i < l; i++) { 66 o[i] = deepCopy(obj[i]); 67 } 68 return o as T; 69 } else { 70 let a = obj as DeepCopyableObject; 71 let r: DeepCopyableObject = {}; 72 if (a.prototype !== undefined) { 73 r.prototype = a.prototype; 74 } 75 for (let k in obj) { 76 r[k] = deepCopy(a[k]); 77 } 78 return r as T; 79 } 80} 81 82/** 83 * Shallow copy routine for objects 84 */ 85export 86function shallowCopy< T extends { [key: string]: any } >(original: T): T { 87 // First create an empty object with 88 // same prototype of our original source 89 let clone = Object.create(Object.getPrototypeOf(original)); 90 91 for (let k in original) { 92 // Don't copy function 93 let ok = original[k]; 94 if (ok !== null && ok !== undefined && 95 ok.hasOwnProperty('constructor') && 96 ok.constructor === Function) { 97 continue; 98 } 99 let pDesc = Object.getOwnPropertyDescriptor(original, k); 100 // Don't copy properties with getter 101 if (!pDesc || pDesc.get) { 102 continue; 103 } 104 // copy each property into the clone 105 Object.defineProperty(clone, k, pDesc); 106 } 107 return clone; 108} 109 110/** 111 * Do a shallow, element-wise equality comparison on two arrays. 112 */ 113export 114function arraysEqual(a: any[] | null, b: any[] | null) { 115 if (a === b) { 116 return true; 117 } 118 if (a === null || b === null) { 119 return false; 120 } 121 if (a.length !== b.length) { 122 return false; 123 } 124 for (let i = 0; i < a.length; ++i) { 125 if (a[i] !== b[i]) { 126 return false; 127 } 128 } 129 return true; 130} 131 132 133/** 134 * Find the shared common starting sequence in two arrays 135 */ 136export 137function findSharedPrefix(a: any[] | null, b: any[] | null): any[] | null { 138 if (a === null || b === null) { 139 return null; 140 } 141 if (a === b) { // Only checking for instance equality 142 return a.slice(); 143 } 144 let i = 0; 145 for (; i < Math.min(a.length, b.length); ++i) { 146 if (a[i] !== b[i]) { 147 break; 148 } 149 } 150 return a.slice(0, i); 151} 152 153/** 154 * Check whether `parent` is contained within the start of `child` 155 * 156 * Note on terminology: Parent is here the shortest array, as it will 157 * be the parent in a tree-view of values, e.g. a path. In other words, parent 158 * is a subsequence of child. 159 */ 160export 161function isPrefixArray(parent: any[] | null, child: any[] | null): boolean { 162 if (parent === child) { 163 return true; 164 } 165 if (parent === null || parent.length === 0) { 166 return true; 167 } 168 if (child === null || parent.length > child.length) { 169 return false; 170 } 171 for (let i = 0; i < parent.length; ++i) { 172 if (parent[i] !== child[i]) { 173 return false; 174 } 175 } 176 return true; 177} 178 179/** 180 * Sort array by attribute `key` (i.e. compare by array[0][key] < array[1][key]). Stable. 181 */ 182export 183function sortByKey<T extends {[key: string]: any}>(array: T[], key: string): T[] { 184 return stableSort(array, function(a, b) { 185 let x = a[key]; let y = b[key]; 186 return ((x < y) ? -1 : ((x > y) ? 1 : 0)); 187 }); 188} 189 190 191/** 192 * Utility function to repeat a string 193 */ 194export 195function repeatString(str: string, count: number): string { 196 if (count < 1) { 197 return ''; 198 } 199 let result = ''; 200 let pattern = str.valueOf(); 201 while (count > 1) { 202 if (count & 1) { 203 result += pattern; 204 } 205 count >>= 1, pattern += pattern; 206 } 207 return result + pattern; 208} 209 210/** 211 * Calculate the cumulative sum of string lengths for an array of strings 212 * 213 * Example: 214 * For the arary ['ab', '123', 'y', '\t\nfoo'], the output would be 215 * [2, 5, 6, 11] 216 */ 217export 218function accumulateLengths(arr: string[]) { 219 let ret: number[] = []; 220 arr.reduce<number>(function(a: number, b: string, i: number): number { 221 return ret[i] = a + b.length; 222 }, 0); 223 return ret; 224} 225 226/** 227 * Filter for Array.filter to only have unique values 228 */ 229export 230function unique<T>(value: T, index: number, self: T[]): boolean { 231 return self.indexOf(value) === index; 232} 233 234/** 235 * Return the intersection of two arrays (with no duplicates) 236 */ 237export 238function intersection<T>(a: T[], b: T[]): T[] { 239 let ret: T[] = []; 240 // Loop over longest, so that indexOf works on shortest 241 [a, b] = a.length > b.length ? [a, b] : [b, a]; 242 for (let ia of a) { 243 if (b.indexOf(ia) !== -1) { 244 ret.push(ia); 245 } 246 } 247 return ret; 248} 249 250 251/** 252 * Similar to Array.sort, but guaranteed to keep order stable 253 * when compare function returns 0 254 */ 255export 256function stableSort<T>(arr: T[], compare: (a: T, b: T) => number): T[] { 257 let sorters: {index: number, key: T}[] = []; 258 for (let i=0; i < arr.length; ++i) { 259 sorters.push({index: i, key: arr[i]}); 260 } 261 sorters = sorters.sort((a: {index: number, key: T}, b: {index: number, key: T}): number => { 262 return compare(a.key, b.key) || a.index - b.index; 263 }); 264 let out: T[] = new Array<T>(arr.length); 265 for (let i=0; i < arr.length; ++i) { 266 out[i] = arr[sorters[i].index]; 267 } 268 return out; 269} 270 271 272/** 273 * Copy an object, possibly extending it in the process 274 */ 275export function copyObj<T extends {[key: string]: any}>(obj: T): T; 276export function copyObj<T extends {[key: string]: any}, U extends {[key: string]: any}> 277(obj: T, target?: U): T & U; 278export function copyObj(obj: {[key: string]: any}, target?: {[key: string]: any}): any { 279 if (!target) { 280 target = {}; 281 } 282 for (let prop in obj) { 283 if (obj.hasOwnProperty(prop)) { 284 target[prop] = obj[prop]; 285 } 286 } 287 return target; 288} 289 290 291/** 292 * Create or populate a select element with string options 293 */ 294export 295function buildSelect(options: string[], select?: HTMLSelectElement): HTMLSelectElement { 296 if (select === undefined) { 297 select = document.createElement('select'); 298 } 299 for (let option of options) { 300 let opt = document.createElement('option'); 301 opt.text = option; 302 select.appendChild(opt); 303 } 304 return select; 305} 306