Steven Silvester преди 8 години
родител
ревизия
2dc446ea59

+ 446 - 0
src/coreutils/nbformat.ts

@@ -0,0 +1,446 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+
+// Notebook format interfaces
+// https://nbformat.readthedocs.io/en/latest/format_description.html
+// https://github.com/jupyter/nbformat/blob/master/nbformat/v4/nbformat.v4.schema.json
+
+import {
+  JSONObject, JSONExt
+} from '@phosphor/coreutils';
+
+
+/**
+ * A namespace for nbformat interfaces.
+ */
+export
+namespace nbformat {
+  /**
+   * The major version of the notebook format.
+   */
+  export
+  const MAJOR_VERSION: number = 4;
+
+  /**
+   * The minor version of the notebook format.
+   */
+  export
+  const MINOR_VERSION: number = 2;
+
+  /**
+   * The kernelspec metadata.
+   */
+  export
+  interface IKernelspecMetadata extends JSONObject {
+    name: string;
+    display_name: string;
+  }
+
+  /**
+   * The language info metatda
+   */
+  export
+  interface ILanguageInfoMetadata extends  JSONObject {
+    name: string;
+    codemirror_mode: string | JSONObject;
+    file_extension: string;
+    mimetype: string;
+    pygments_lexer: string;
+  }
+
+  /**
+   * The default metadata for the notebook.
+   */
+  export
+  interface INotebookMetadata extends JSONObject {
+    kernelspec: IKernelspecMetadata;
+    language_info: ILanguageInfoMetadata;
+    orig_nbformat: number;
+  }
+
+  /**
+   * The notebook content.
+   */
+  export
+  interface INotebookContent extends JSONObject {
+    metadata: INotebookMetadata;
+    nbformat_minor: number;
+    nbformat: number;
+    cells: ICell[];
+  }
+
+  /**
+   * A multiline string.
+   */
+  export
+  type MultilineString = string | string[];
+
+  /**
+   * A mime-type keyed dictionary of data.
+   */
+  export
+  interface IMimeBundle extends JSONObject {
+    [key: string]: MultilineString | JSONObject;
+  }
+
+  /**
+   * Media attachments (e.g. inline images).
+   */
+  export
+  interface IAttachments {
+    [key: string]: IMimeBundle;
+  }
+
+  /**
+   * The code cell's prompt number. Will be null if the cell has not been run.
+   */
+  export
+  type ExecutionCount = number | null;
+
+  /**
+   * Cell output metadata.
+   */
+  export
+  type OutputMetadata = JSONObject;
+
+  /**
+   * Validate a mime type/value pair.
+   *
+   * @param type - The mimetype name.
+   *
+   * @param value - The value associated with the type.
+   *
+   * @returns Whether the type/value pair are valid.
+   */
+  export
+  function validateMimeValue(type: string, value: MultilineString | JSONObject): boolean {
+    // Check if "application/json" or "application/foo+json"
+    const jsonTest = /^application\/(.*?)+\+json$/;
+    const isJSONType = type === 'application/json' || jsonTest.test(type);
+
+    let isString = (x: any) => {
+      return Object.prototype.toString.call(x) === '[object String]';
+    };
+
+    // If it is an array, make sure if is not a JSON type and it is an
+    // array of strings.
+    if (Array.isArray(value)) {
+      if (isJSONType) {
+        return false;
+      }
+      let valid = true;
+      (value as string[]).forEach(v => {
+        if (!isString(v)) {
+          valid = false;
+        }
+      });
+      return valid;
+    }
+
+    // If it is a string, make sure we are not a JSON type.
+    if (isString(value)) {
+      return !isJSONType;
+    }
+
+    // It is not a string, make sure it is a JSON type.
+    if (!isJSONType) {
+      return false;
+    }
+
+    // It is a JSON type, make sure it is a valid JSON object.
+    return JSONExt.isObject(value);
+  }
+
+  /**
+   * A type which describes the type of cell.
+   */
+  export
+  type CellType = 'code' | 'markdown' | 'raw';
+
+  /**
+   * Cell-level metadata.
+   */
+  export
+  interface IBaseCellMetadata extends JSONObject {
+    /**
+     * Whether the cell is trusted.
+     *
+     * #### Notes
+     * This is not strictly part of the nbformat spec, but it is added by
+     * the contents manager.
+     *
+     * See https://jupyter-notebook.readthedocs.io/en/latest/security.html.
+     */
+    trusted: boolean;
+
+    /**
+     * The cell's name. If present, must be a non-empty string.
+     */
+    name: string;
+
+    /**
+     * The cell's tags. Tags must be unique, and must not contain commas.
+     */
+    tags: string[];
+  }
+
+  /**
+   * The base cell interface.
+   */
+  export
+  interface IBaseCell extends JSONObject {
+    /**
+     * String identifying the type of cell.
+     */
+    cell_type: string;
+
+    /**
+     * Contents of the cell, represented as an array of lines.
+     */
+    source: MultilineString;
+
+    /**
+     * Cell-level metadata.
+     */
+    metadata: ICellMetadata;
+  }
+
+  /**
+   * Metadata for the raw cell.
+   */
+  export
+  interface IRawCellMetadata extends IBaseCellMetadata {
+    /**
+     * Raw cell metadata format for nbconvert.
+     */
+    format: string;
+  }
+
+  /**
+   * A raw cell.
+   */
+  export
+  interface IRawCell extends IBaseCell {
+    /**
+     * String identifying the type of cell.
+     */
+    cell_type: 'raw';
+
+    /**
+     * Cell-level metadata.
+     */
+    metadata: IRawCellMetadata;
+
+    /**
+     * Cell attachments.
+     */
+    attachments: IAttachments;
+  }
+
+  /**
+   * A markdown cell.
+   */
+  export
+  interface IMarkdownCell extends IBaseCell {
+    /**
+     * String identifying the type of cell.
+     */
+    cell_type: 'markdown';
+
+    /**
+     * Cell attachments.
+     */
+    attachments: IAttachments;
+  }
+
+  /**
+   * Metadata for a code cell.
+   */
+  export
+  interface ICodeCellMetadata extends IBaseCellMetadata {
+    /**
+     * Whether the cell is collapsed/expanded.
+     */
+    collapsed: boolean;
+
+    /**
+     * Whether the cell's output is scrolled, unscrolled, or autoscrolled.
+     */
+    scrolled: boolean | 'auto';
+  }
+
+  /**
+   * A code cell.
+   */
+  export
+  interface ICodeCell extends IBaseCell {
+    /**
+     * String identifying the type of cell.
+     */
+    cell_type: 'code';
+
+    /**
+     * Cell-level metadata.
+     */
+    metadata: ICodeCellMetadata;
+
+    /**
+     * Execution, display, or stream outputs.
+     */
+    outputs: IOutput[];
+
+    /**
+     * The code cell's prompt number. Will be null if the cell has not been run.
+     */
+    execution_count: ExecutionCount;
+  }
+
+  /**
+   * An unrecognized cell.
+   */
+  export
+  interface IUnrecognizedCell extends IBaseCell { }
+
+  /**
+   * A cell union type.
+   */
+  export
+  type ICell = IRawCell | IMarkdownCell | ICodeCell | IUnrecognizedCell;
+
+
+  /**
+   * A union metadata type.
+   */
+  export
+  type ICellMetadata = IBaseCellMetadata | IRawCellMetadata | ICodeCellMetadata;
+
+  /**
+   * The valid output types.
+   */
+  export
+  type OutputType = 'execute_result' | 'display_data' | 'stream' | 'error';
+
+  /**
+   * The base output type.
+   */
+  export
+  interface IBaseOutput extends JSONObject {
+    /**
+     * Type of cell output.
+     */
+    output_type: string;
+  }
+
+  /**
+   * Result of executing a code cell.
+   */
+  export
+  interface IExecuteResult extends IBaseOutput {
+    /**
+     * Type of cell output.
+     */
+    output_type: 'execute_result';
+
+    /**
+     * A result's prompt number.
+     */
+    execution_count: ExecutionCount;
+
+    /**
+     * A mime-type keyed dictionary of data.
+     */
+    data: IMimeBundle;
+
+    /**
+     * Cell output metadata.
+     */
+    metadata: OutputMetadata;
+  }
+
+  /**
+   * Data displayed as a result of code cell execution.
+   */
+  export
+  interface IDisplayData extends IBaseOutput {
+    /**
+     * Type of cell output.
+     */
+    output_type: 'display_data';
+
+    /**
+     * A mime-type keyed dictionary of data.
+     */
+    data: IMimeBundle;
+
+    /**
+     * Cell output metadata.
+     */
+    metadata: OutputMetadata;
+  }
+
+  /**
+   * Stream output from a code cell.
+   */
+  export
+  interface IStream extends IBaseOutput {
+    /**
+     * Type of cell output.
+     */
+    output_type: 'stream';
+
+    /**
+     * The name of the stream.
+     */
+    name: StreamType;
+
+    /**
+     * The stream's text output.
+     */
+    text: MultilineString;
+  }
+
+  /**
+   * An alias for a stream type.
+   */
+  export
+  type StreamType = 'stdout' | 'stderr';
+
+  /**
+   * Output of an error that occurred during code cell execution.
+   */
+  export
+  interface IError extends IBaseOutput {
+    /**
+     * Type of cell output.
+     */
+    output_type: 'error';
+
+    /**
+     * The name of the error.
+     */
+    ename: string;
+
+    /**
+     * The value, or message, of the error.
+     */
+    evalue: string;
+
+    /**
+     * The error's traceback.
+     */
+    traceback: string[];
+  }
+
+  /**
+   * Unrecognized output.
+   */
+  export
+  interface IUnrecognizedOutput extends IBaseOutput { }
+
+
+  /**
+   * An output union type.
+   */
+  export
+  type IOutput = IExecuteResult | IDisplayData | IStream | IError | IUnrecognizedOutput;
+}

+ 138 - 0
src/coreutils/path.ts

@@ -0,0 +1,138 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+
+import * as posix
+ from 'path-posix';
+
+
+/**
+ * The namespace for path-related functions.
+ */
+export
+namespace PathExt {
+  /**
+   * Join all arguments together and normalize the resulting path.
+   * Arguments must be strings. In v0.8, non-string arguments were silently ignored. In v0.10 and up, an exception is thrown.
+   *
+   * @param paths - The string paths to join.
+   */
+  export
+  function join(...paths: string[]): string {
+    return posix.join(...paths);
+  }
+
+  /**
+   * Return the last portion of a path. Similar to the Unix basename command.
+   * Often used to extract the file name from a fully qualified path.
+   *
+   * @param path - The path to evaluate.
+   *
+   * @param ext - An extension to remove from the result.
+   */
+  export
+  function basename(path: string, ext?: string): string {
+    return posix.basename(path, ext);
+  }
+
+  /**
+   * Get the directory name of a path, similar to the Unix dirname command.
+   *
+   * @param path - The file path.
+   */
+  export
+  function dirname(path: string): string {
+    return posix.dirname(path);
+  }
+
+  /**
+   * Get the extension of the path.
+   *
+   * @param path - The file path.
+   *
+   * @returns the extension of the file.
+   *
+   * #### Notes
+   * The extension is the string from the last occurence of the `.`
+   * character to end of string in the last portion of the path, inclusive.
+   * If there is no `.` in the last portion of the path, or if the first
+   * character of the basename of path [[basename]] is `.`, then an
+   * empty string is returned.
+   */
+  export
+  function extname(path: string): string {
+    return posix.extname(path);
+  }
+
+  /**
+   * Normalize a string path, reducing '..' and '.' parts.
+   * When multiple slashes are found, they're replaced by a single one; when the path contains a trailing slash, it is preserved. On Windows backslashes are used.
+   *
+   * @param path - The string path to normalize.
+   */
+  export
+  function normalize(path: string): string {
+    return posix.normalize(path);
+  }
+
+  /**
+   * Resolve a sequence of paths or path segments into an absolute path.
+   *
+   * @param parts - The paths to join.
+   *
+   * #### Notes
+   * The right-most parameter is considered {to}.  Other parameters are considered an array of {from}.
+   *
+   * Starting from leftmost {from} paramter, resolves {to} to an absolute path.
+   *
+   * If {to} isn't already absolute, {from} arguments are prepended in right to left order, until an absolute path is found. If after using all {from} paths still no absolute path is found, the current working directory is used as well. The resulting path is normalized, and trailing slashes are removed unless the path gets resolved to the root directory.
+   */
+  export
+  function resolve(...parts: string[]): string {
+    return posix.resolve(...parts);
+  }
+
+  /**
+   * Solve the relative path from {from} to {to}.
+   *
+   * @param from - The source path.
+   *
+   * @param to - The target path.
+   *
+   * #### Notes
+   * If from and to each resolve to the same path (after calling
+   * path.resolve() on each), a zero-length string is returned.
+   * If a zero-length string is passed as from or to, `/`
+   * will be used instead of the zero-length strings.
+   */
+  export
+  function relative(from: string, to: string): string {
+    return posix.relative(from, to);
+  }
+
+  /**
+   * Determines whether {path} is an absolute path. An absolute path will
+   * always resolve to the same location, regardless of the working directory.
+   *
+   * @param path - The path to test.
+   */
+  export
+  function isAbsolute(path: string): boolean {
+    return posix.isAbsolute(path);
+  }
+
+  /**
+   * Normalize a file extension to be of the type `'.foo'`.
+   *
+   * @param extension - the file extension.
+   *
+   * #### Notes
+   * Adds a leading dot if not present and converts to lower case.
+   */
+  export
+  function normalizeExtension(extension: string): string {
+    if (extension.length > 0 && extension.indexOf('.') !== 0) {
+      extension = `.${extension}`;
+    }
+    return extension;
+  }
+}

+ 187 - 0
src/coreutils/url.ts

@@ -0,0 +1,187 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+
+import {
+  JSONObject
+} from '@phosphor/coreutils';
+
+import * as posix
+ from 'path-posix';
+
+import * as url
+  from 'url';
+
+
+/**
+ * The namespace for URL-related functions.
+ */
+export
+namespace URLExt {
+  /**
+   * Parse a url into a URL object.
+   *
+   * @param urlString - The URL string to parse.
+   *
+   * @param parseQueryString - If `true`, the query property will always be set
+   *   to an object returned by the `querystring` module's `parse()` method.
+   *   If `false`, the `query` property on the returned URL object will be an
+   *   unparsed, undecoded string. Defaults to `false`.
+   *
+   * @param slashedDenoteHost - If `true`, the first token after the literal
+   *   string `//` and preceeding the next `/` will be interpreted as the
+   *   `host`.
+   *   For instance, given `//foo/bar`, the result would be
+   *   `{host: 'foo', pathname: '/bar'}` rather than `{pathname: '//foo/bar'}`.
+   *   Defaults to `false`.
+   *
+   * @returns A URL object.
+   */
+  export
+  function parse(urlStr: string, parseQueryString?: boolean, slashesDenoteHost?: boolean): IUrl {
+    return url.parse(urlStr, parseQueryString, slashesDenoteHost);
+  }
+
+  /**
+   * Resolve a target URL relative to a base URL in a manner similar to that
+   * of a Web browser resolving an anchor tag HRE
+   *
+   * @param from - The Base URL being resolved against.
+   *
+   * @param to - The HREF URL being resolved
+   *
+   * @returns the resolved url.
+   */
+  export
+  function resolve(from: string, to: string): string {
+    return url.resolve(from, to);
+  }
+
+  /**
+   * Join a sequence of url components and normalizes as in node `path.join`.
+   *
+   * @param parts - The url components.
+   *
+   * @returns the joined url.
+   */
+  export
+  function join(...parts: string[]): string {
+    return posix.join(...parts);
+  }
+
+  /**
+   * Encode the components of a multi-segment url.
+   *
+   * @param url - The url to encode.
+   *
+   * @returns the encoded url.
+   *
+   * #### Notes
+   * Preserves the `'/'` separators.
+   * Should not include the base url, since all parts are escaped.
+   */
+  export
+  function encodeParts(url: string): string {
+    // Normalize and join, split, encode, then join.
+    url = join(url);
+    let parts = url.split('/').map(encodeURIComponent);
+    return join(...parts);
+  }
+
+  /**
+   * Return a serialized object string suitable for a query.
+   *
+   * @param object - The source object.
+   *
+   * @returns an encoded url query.
+   *
+   * #### Notes
+   * From [stackoverflow](http://stackoverflow.com/a/30707423).
+   */
+  export
+  function objectToQueryString(value: JSONObject): string {
+    return '?' + Object.keys(value).map(key =>
+      encodeURIComponent(key) + '=' + encodeURIComponent(String(value[key]))
+    ).join('&');
+  }
+
+  /**
+   * Test whether the url is a local url.
+   */
+  export
+  function isLocal(url: string): boolean {
+    return !parse(url).protocol && url.indexOf('//') !== 0;
+  }
+
+  /**
+   * The interface for a URL object
+   */
+  export interface IUrl {
+    /**
+     * The full URL string that was parsed with both the protocol and host
+     * components converted to lower-case.
+     */
+    href?: string;
+
+    /**
+     * Identifies the URL's lower-cased protocol scheme.
+     */
+    protocol?: string;
+
+    /**
+     * Whether two ASCII forward-slash characters (/) are required
+     * following the colon in the protocol.
+     */
+    slashes?: boolean;
+
+    /**
+     * The full lower-cased host portion of the URL, including the port if
+     * specified.
+     */
+    host?: string;
+
+    /**
+     * The username and password portion of the URL.
+     */
+    auth?: string;
+
+    /**
+     * The lower-cased host name portion of the host component without the
+     * port included.
+     */
+    hostname?: string;
+
+    /**
+     * The numeric port portion of the host component.
+     */
+    port?: string;
+
+    /**
+     * The entire path section of the URL.
+     */
+    pathname?: string;
+
+    /**
+     * The the entire "query string" portion of the URL, including the
+     * leading ASCII question mark `(?)` character.
+     */
+    search?: string;
+
+    /**
+     * Aaconcatenation of the pathname and search components.
+     */
+    path?: string;
+
+    /**
+     * Either the query string without the leading ASCII question mark
+     * `(?)`, or an object returned by the `parse()` method when
+     * `parseQuestyString` is `true`.
+     */
+    query?: string | any;
+
+    /**
+     * The "fragment" portion of the URL including the leading ASCII hash
+     * `(#)` character
+     */
+    hash?: string;
+  }
+}

+ 18 - 0
src/coreutils/uuid.ts

@@ -0,0 +1,18 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+
+/**
+ * Get a random hex string (not a formal UUID).
+ *
+ * @param length - The length of the hex string.
+ */
+export
+function uuid(length: number=32): string {
+  let s = new Array<string>(length);
+  let hexDigits = '0123456789abcdef';
+  let nChars = hexDigits.length;
+  for (let i = 0; i < length; i++) {
+    s[i] = hexDigits.charAt(Math.floor(Math.random() * nChars));
+  }
+  return s.join('');
+}

+ 7 - 3
src/docregistry/context.ts

@@ -2,7 +2,7 @@
 // Distributed under the terms of the Modified BSD License.
 
 import {
-  ContentsManager, Contents, Kernel, ServiceManager, Session, utils
+  ContentsManager, Contents, Kernel, ServiceManager, Session
 } from '@jupyterlab/services';
 
 import {
@@ -29,6 +29,10 @@ import {
   showDialog, Dialog
 } from '../apputils';
 
+import {
+  URLExt
+} from '../coreutils';
+
 import {
   findKernel
 } from '../docregistry';
@@ -383,7 +387,7 @@ class Context<T extends DocumentRegistry.IModel> implements DocumentRegistry.ICo
    */
   resolveUrl(url: string): Promise<string> {
     // Ignore urls that have a protocol.
-    if (utils.urlParse(url).protocol || url.indexOf('//') === 0) {
+    if (URLExt.parse(url).protocol || url.indexOf('//') === 0) {
       return Promise.resolve(url);
     }
     let cwd = ContentsManager.dirname(this._path);
@@ -396,7 +400,7 @@ class Context<T extends DocumentRegistry.IModel> implements DocumentRegistry.ICo
    */
   getDownloadUrl(path: string): Promise<string> {
     // Ignore urls that have a protocol.
-    if (utils.urlParse(path).protocol || path.indexOf('//') === 0) {
+    if (URLExt.parse(path).protocol || path.indexOf('//') === 0) {
       return Promise.resolve(path);
     }
     return this._manager.contents.getDownloadUrl(path);

+ 5 - 5
src/rendermime/plugin.ts

@@ -1,10 +1,6 @@
 // Copyright (c) Jupyter Development Team.
 // Distributed under the terms of the Modified BSD License.
 
-import {
-  utils
-} from '@jupyterlab/services';
-
 import {
   JupyterLabPlugin, JupyterLab
 } from '../application';
@@ -13,6 +9,10 @@ import {
   ICommandLinker
 } from '../commandlinker';
 
+import {
+  URLExt
+} from '../coreutils';
+
 import {
   CommandIDs
 } from '../filebrowser';
@@ -45,7 +45,7 @@ export default plugin;
 function activate(app: JupyterLab, linker: ICommandLinker): IRenderMime {
   let linkHandler = {
     handleLink: (node: HTMLElement, path: string) => {
-      if (!utils.urlParse(path).protocol && path.indexOf('//') !== 0) {
+      if (!URLExt.parse(path).protocol && path.indexOf('//') !== 0) {
         linker.connectNode(node, CommandIDs.open, { path });
       }
     }

+ 4 - 4
src/rendermime/rendermime.ts

@@ -2,7 +2,7 @@
 // Distributed under the terms of the Modified BSD License.
 
 import {
-  Contents, ContentsManager, Session, utils
+  Contents, ContentsManager, Session
 } from '@jupyterlab/services';
 
 import {
@@ -22,7 +22,7 @@ import {
 } from '@phosphor/widgets';
 
 import {
-  IObservableJSON
+  IObservableJSON, URLExt
 } from '../coreutils';
 
 import {
@@ -446,7 +446,7 @@ namespace RenderMime {
      */
     resolveUrl(url: string): Promise<string> {
       // Ignore urls that have a protocol.
-      if (utils.urlParse(url).protocol || url.indexOf('//') === 0) {
+      if (URLExt.parse(url).protocol || url.indexOf('//') === 0) {
         return Promise.resolve(url);
       }
       let path = this._session.path;
@@ -460,7 +460,7 @@ namespace RenderMime {
      */
     getDownloadUrl(path: string): Promise<string> {
       // Ignore urls that have a protocol.
-      if (utils.urlParse(path).protocol || path.indexOf('//') === 0) {
+      if (URLExt.parse(path).protocol || path.indexOf('//') === 0) {
         return Promise.resolve(path);
       }
       return this._contents.getDownloadUrl(path);

+ 2 - 0
src/typings.d.ts

@@ -3,4 +3,6 @@
 
 /// <reference path="../typings/ansi_up/ansi_up.d.ts"/>
 /// <reference path="../typings/codemirror/codemirror.d.ts"/>
+/// <reference path="../typings/path-posix/path-posix.d.ts"/>
+/// <reference path="../typings/url/url.d.ts"/>
 /// <reference path="../typings/xterm/xterm.d.ts"/>

+ 50 - 0
test/src/coreutils/nbformat.spec.ts

@@ -0,0 +1,50 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+
+import {
+  expect
+} from 'chai';
+
+import {
+  nbformat
+} from '..';
+
+
+const VALIDATE = nbformat.validateMimeValue;
+
+
+describe('@jupyterlab/coreutils', () => {
+
+  describe('nbformat', () => {
+
+    describe('validateMimeValue', () => {
+
+      it('should return true for a valid json object', () => {
+        expect(VALIDATE('application/json', { 'foo': 1 })).to.equal(true);
+      });
+
+      it('should return true for a valid json-like object', () => {
+        expect(VALIDATE('application/foo+json', { 'foo': 1 })).to.equal(true);
+      });
+
+      it('should return true for a valid string object', () => {
+        expect(VALIDATE('text/plain', 'foo')).to.equal(true);
+      });
+
+      it('should return true for a valid array of strings object', () => {
+        expect(VALIDATE('text/plain', ['foo', 'bar'])).to.equal(true);
+      });
+
+      it('should return false for a json type with string data', () => {
+        expect(VALIDATE('application/foo+json', 'bar')).to.equal(false);
+      });
+
+      it('should return false for a string type with json data', () => {
+        expect(VALIDATE('foo/bar', { 'foo': 1 })).to.equal(false);
+      });
+
+    });
+
+  });
+
+});

+ 104 - 0
test/src/coreutils/path.spec.ts

@@ -0,0 +1,104 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+
+import {
+  expect
+} from 'chai';
+
+import {
+  PathExt
+} from '..';
+
+
+const TESTPATH = '/foo/test/simple/test-path.js';
+
+
+describe('@jupyterlab/coreutils', () => {
+
+  describe('PathExt', () => {
+
+    describe('.join()', () => {
+
+      it('should join the arguments and normalize the path', () => {
+        let path = PathExt.join('/foo', '../../../bar');
+        expect(path).to.equal('/bar');
+      });
+
+    });
+
+    describe('.basename()', () => {
+
+      it('should return the last portion of a path', () => {
+        expect(PathExt.basename(TESTPATH)).to.equal('test-path.js');
+      });
+
+    });
+
+    describe('.dirname()', () => {
+
+      it('should get the directory name of a path', () => {
+        expect(PathExt.dirname(TESTPATH)).to.equal('/foo/test/simple');
+      });
+
+    });
+
+    describe('.extname()', () => {
+
+      it('should get the file extension of the path', () => {
+        expect(PathExt.extname(TESTPATH)).to.equal('.js');
+      });
+
+      it('should only take the last occurance of a dot', () => {
+        expect(PathExt.extname('foo.tar.gz')).to.equal('.gz');
+      });
+
+    });
+
+    describe('.normalize()', () => {
+
+      it('should normalize a string path', () => {
+        let path = PathExt.normalize('./fixtures///b/../b/c.js');
+        expect(path).to.equal('fixtures/b/c.js');
+      });
+
+    });
+
+    describe('.resolve()', () => {
+
+      it('should resolve a sequence of paths to an absolute path', () => {
+        let path = PathExt.resolve('/var/lib', '../', 'file/');
+        expect(path).to.equal('/var/file');
+      });
+
+    });
+
+    describe('.relative()', () => {
+
+      it('should solve the relative path', () => {
+        let path = PathExt.relative('/var/lib', '/var/apache');
+        expect(path).to.equal('../apache');
+      });
+
+    });
+
+    describe('.isAbsolute()', () => {
+
+      it('should determine whether a path is absolute', () => {
+        expect(PathExt.isAbsolute('/home/foo')).to.equal(true);
+        expect(PathExt.isAbsolute('./baz')).to.equal(false);
+      });
+
+    });
+
+    describe('.normalizeExtension()', () => {
+
+      it('should normalize a file extension to be of type `.foo`', () => {
+        expect(PathExt.normalizeExtension('foo')).to.equal('.foo');
+        expect(PathExt.normalizeExtension('.bar')).to.equal('.bar');
+      });
+
+    });
+
+  });
+
+});

+ 103 - 0
test/src/coreutils/url.spec.ts

@@ -0,0 +1,103 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+
+import {
+  expect
+} from 'chai';
+
+import {
+  URLExt
+} from '..';
+
+
+describe('@jupyterlab/coreutils', () => {
+
+  describe('URLExt', () => {
+
+    describe('.parse()', () => {
+
+      it('should parse a url into a URLExt object', () => {
+        let obj = URLExt.parse('http://www.example.com');
+        expect(obj.href).to.equal('http://www.example.com/');
+        expect(obj.protocol).to.equal('http:');
+        expect(obj.slashes).to.equal(true);
+        expect(obj.host).to.equal('www.example.com');
+        expect(obj.hostname).to.equal('www.example.com');
+        expect(obj.pathname).to.equal('/');
+        expect(obj.path).to.equal('/');
+      });
+
+      it('should handle query and hash', () => {
+        let obj = URLExt.parse('http://x.com/path?that\'s#all, folks');
+        expect(obj.href).to.equal('http://x.com/path?that%27s#all,%20folks');
+        expect(obj.protocol).to.equal('http:');
+        expect(obj.slashes).to.equal(true);
+        expect(obj.host).to.equal('x.com');
+        expect(obj.hostname).to.equal('x.com');
+        expect(obj.search).to.equal('?that%27s');
+        expect(obj.query).to.equal('that%27s');
+        expect(obj.pathname).to.equal('/path');
+        expect(obj.hash).to.equal('#all,%20folks');
+        expect(obj.path).to.equal('/path?that%27s');
+      });
+
+    });
+
+    describe('.resolve()', () => {
+
+      it('should resolve a target URLExt relative to a base url', () => {
+        let path = URLExt.resolve('/foo/bar/baz', '/bar');
+        expect(path).to.equal('/bar');
+        expect(URLExt.resolve('/foo/bar', '.')).to.equal('/foo/');
+        path = URLExt.resolve(
+          'http://example.com/b//c//d;p?q#blarg',
+          'https://u:p@h.com/p/a/t/h?s#hash2'
+        );
+        expect(path).to.equal('https://u:p@h.com/p/a/t/h?s#hash2');
+      });
+
+    });
+
+    describe('.join()', () => {
+
+      it('should join a sequence of url components', () => {
+        expect(URLExt.join('/foo/', 'bar/')).to.equal('/foo/bar/');
+      });
+
+    });
+
+    describe('.encodeParts()', () => {
+
+      it('should encode and join a sequence of url components', () => {
+        expect(URLExt.encodeParts('>/>')).to.equal('%3E/%3E');
+      });
+
+    });
+
+    describe('objectToQueryString()', () => {
+
+      it('should return a serialized object string suitable for a query', () => {
+        let obj = {
+          name: 'foo',
+          id: 'baz'
+        };
+        expect(URLExt.objectToQueryString(obj)).to.equal('?name=foo&id=baz');
+      });
+
+    });
+
+    describe('.isLocal()', () => {
+
+      it('should test whether the url is a local url', () => {
+        expect(URLExt.isLocal('//foo')).to.equal(false);
+        expect(URLExt.isLocal('http://foo')).to.equal(false);
+        expect(URLExt.isLocal('/foo/bar')).to.equal(true);
+        expect(URLExt.isLocal('foo.txt')).to.equal(true);
+      });
+
+    });
+
+  });
+
+
+});

+ 35 - 0
test/src/coreutils/uuid.spec.ts

@@ -0,0 +1,35 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+
+import {
+  expect
+} from 'chai';
+
+import {
+  uuid
+} from '..';
+
+
+describe('@jupyterlab/coreutils', () => {
+
+  describe('uuid()', () => {
+
+    it('should generate a random 32 character hex string', () => {
+      let id0 = uuid();
+      let id1 = uuid();
+      expect(id0.length).to.equal(32);
+      expect(id1.length).to.equal(32);
+      expect(id0).to.not.equal(id1);
+    });
+
+    it('should accept a length', () => {
+      let id0 = uuid(10);
+      let id1 = uuid(10);
+      expect(id0.length).to.equal(10);
+      expect(id1.length).to.equal(10);
+      expect(id0).to.not.equal(id1);
+    });
+
+  });
+
+});

+ 121 - 0
typings/path-posix/path-posix.d.ts

@@ -0,0 +1,121 @@
+// Extracted from https://github.com/DefinitelyTyped/DefinitelyTyped/blob/1ad10bb0dd35f98e27d4d848bfe3d5dfc919e9f0/node/node.d.ts
+// Type definitions for Node.js v6.x
+// Project: http://nodejs.org/
+// Definitions by: Microsoft TypeScript <http://typescriptlang.org>, DefinitelyTyped <https://github.com/DefinitelyTyped/DefinitelyTyped>
+// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
+
+declare module 'path-posix' {
+    /**
+     * A parsed path object generated by path.parse() or consumed by path.format().
+     */
+    export interface ParsedPath {
+        /**
+         * The root of the path such as '/' or 'c:\'
+         */
+        root: string;
+        /**
+         * The full directory path such as '/home/user/dir' or 'c:\path\dir'
+         */
+        dir: string;
+        /**
+         * The file name including extension (if any) such as 'index.html'
+         */
+        base: string;
+        /**
+         * The file extension (if any) such as '.html'
+         */
+        ext: string;
+        /**
+         * The file name without extension (if any) such as 'index'
+         */
+        name: string;
+    }
+
+    /**
+     * Normalize a string path, reducing '..' and '.' parts.
+     * When multiple slashes are found, they're replaced by a single one; when the path contains a trailing slash, it is preserved. On Windows backslashes are used.
+     *
+     * @param p string path to normalize.
+     */
+    export function normalize(p: string): string;
+    /**
+     * Join all arguments together and normalize the resulting path.
+     * Arguments must be strings. In v0.8, non-string arguments were silently ignored. In v0.10 and up, an exception is thrown.
+     *
+     * @param paths string paths to join.
+     */
+    export function join(...paths: any[]): string;
+    /**
+     * Join all arguments together and normalize the resulting path.
+     * Arguments must be strings. In v0.8, non-string arguments were silently ignored. In v0.10 and up, an exception is thrown.
+     *
+     * @param paths string paths to join.
+     */
+    export function join(...paths: string[]): string;
+    /**
+     * The right-most parameter is considered {to}.  Other parameters are considered an array of {from}.
+     *
+     * Starting from leftmost {from} paramter, resolves {to} to an absolute path.
+     *
+     * If {to} isn't already absolute, {from} arguments are prepended in right to left order, until an absolute path is found. If after using all {from} paths still no absolute path is found, the current working directory is used as well. The resulting path is normalized, and trailing slashes are removed unless the path gets resolved to the root directory.
+     *
+     * @param pathSegments string paths to join.  Non-string arguments are ignored.
+     */
+    export function resolve(...pathSegments: any[]): string;
+    /**
+     * Determines whether {path} is an absolute path. An absolute path will always resolve to the same location, regardless of the working directory.
+     *
+     * @param path path to test.
+     */
+    export function isAbsolute(path: string): boolean;
+    /**
+     * Solve the relative path from {from} to {to}.
+     * At times we have two absolute paths, and we need to derive the relative path from one to the other. This is actually the reverse transform of path.resolve.
+     *
+     * @param from
+     * @param to
+     */
+    export function relative(from: string, to: string): string;
+    /**
+     * Return the directory name of a path. Similar to the Unix dirname command.
+     *
+     * @param p the path to evaluate.
+     */
+    export function dirname(p: string): string;
+    /**
+     * Return the last portion of a path. Similar to the Unix basename command.
+     * Often used to extract the file name from a fully qualified path.
+     *
+     * @param p the path to evaluate.
+     * @param ext optionally, an extension to remove from the result.
+     */
+    export function basename(p: string, ext?: string): string;
+    /**
+     * Return the extension of the path, from the last '.' to end of string in the last portion of the path.
+     * If there is no '.' in the last portion of the path or the first character of it is '.', then it returns an empty string
+     *
+     * @param p the path to evaluate.
+     */
+    export function extname(p: string): string;
+    /**
+     * The platform-specific file separator. '\\' or '/'.
+     */
+    export var sep: string;
+    /**
+     * The platform-specific file delimiter. ';' or ':'.
+     */
+    export var delimiter: string;
+    /**
+     * Returns an object from a path string - the opposite of format().
+     *
+     * @param pathString path to evaluate.
+     */
+    export function parse(pathString: string): ParsedPath;
+    /**
+     * Returns a path string from an object - the opposite of parse().
+     *
+     * @param pathString path to evaluate.
+     */
+    export function format(pathObject: ParsedPath): string;
+}
+

+ 26 - 0
typings/url/url.d.ts

@@ -0,0 +1,26 @@
+// Extracted from https://github.com/DefinitelyTyped/DefinitelyTyped/blob/1ad10bb0dd35f98e27d4d848bfe3d5dfc919e9f0/node/node.d.ts
+// Type definitions for Node.js v6.x
+// Project: http://nodejs.org/
+// Definitions by: Microsoft TypeScript <http://typescriptlang.org>, DefinitelyTyped <https://github.com/DefinitelyTyped/DefinitelyTyped>
+// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
+
+declare module "url" {
+    export interface Url {
+        href?: string;
+        protocol?: string;
+        auth?: string;
+        hostname?: string;
+        port?: string;
+        host?: string;
+        pathname?: string;
+        search?: string;
+        query?: string | any;
+        slashes?: boolean;
+        hash?: string;
+        path?: string;
+    }
+
+    export function parse(urlStr: string, parseQueryString?: boolean, slashesDenoteHost?: boolean): Url;
+    export function format(url: Url): string;
+    export function resolve(from: string, to: string): string;
+}