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