浏览代码

Merge pull request #5217 from dharmaquark/0818-nbconvert

adding nbconvert to services and using to load exporter list from nbconvert
Ian Rose 6 年之前
父节点
当前提交
d18a539fef

+ 82 - 37
packages/notebook-extension/src/index.ts

@@ -57,7 +57,7 @@ import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
 
 import { ServiceManager } from '@jupyterlab/services';
 
-import { ReadonlyJSONObject } from '@phosphor/coreutils';
+import { ReadonlyJSONObject, JSONValue } from '@phosphor/coreutils';
 
 import { Message, MessageLoop } from '@phosphor/messaging';
 
@@ -212,17 +212,29 @@ const FACTORY = 'Notebook';
 const CELL_TOOLS_RANK = 400;
 
 /**
- * The allowed Export To ... formats and their human readable labels.
+ * The exluded Export To ...
+ * (returned from nbconvert's export list)
  */
-const EXPORT_TO_FORMATS = [
-  { format: 'html', label: 'HTML' },
-  { format: 'latex', label: 'LaTeX' },
-  { format: 'markdown', label: 'Markdown' },
-  { format: 'pdf', label: 'PDF' },
-  { format: 'rst', label: 'ReStructured Text' },
-  { format: 'script', label: 'Executable Script' },
-  { format: 'slides', label: 'Reveal.js Slides' }
-];
+const FORMAT_EXCLUDE = ['notebook', 'python', 'custom'];
+
+/**
+ * The exluded Cell Inspector Raw NbConvert Formats
+ * (returned from nbconvert's export list)
+ */
+const RAW_FORMAT_EXCLUDE = ['pdf', 'slides', 'script', 'notebook', 'custom'];
+
+/**
+ * The default Export To ... formats and their human readable labels.
+ */
+const FORMAT_LABEL: { [k: string]: string } = {
+  html: 'HTML',
+  latex: 'LaTeX',
+  markdown: 'Markdown',
+  pdf: 'PDF',
+  rst: 'ReStructured Text',
+  script: 'Executable Script',
+  slides: 'Reveal.js Slides'
+};
 
 /**
  * The notebook widget tracker provider.
@@ -288,9 +300,9 @@ function activateCellTools(
   const celltools = new CellTools({ tracker });
   const activeCellTool = new CellTools.ActiveCellTool();
   const slideShow = CellTools.createSlideShowSelector();
-  const nbConvert = CellTools.createNBConvertSelector();
   const editorFactory = editorServices.factoryService.newInlineEditor;
   const metadataEditor = new CellTools.MetadataEditorTool({ editorFactory });
+  const services = app.serviceManager;
 
   // Create message hook for triggers to save to the database.
   const hook = (sender: any, message: Message): boolean => {
@@ -307,13 +319,29 @@ function activateCellTools(
     }
     return true;
   };
-
+  let optionsMap: { [key: string]: JSONValue } = {};
+  optionsMap.None = '-';
+  services.nbconvert.getExportFormats().then(response => {
+    if (response) {
+      // convert exportList to palette and menu items
+      const formatList = Object.keys(response);
+      formatList.forEach(function(key) {
+        if (RAW_FORMAT_EXCLUDE.indexOf(key) === -1) {
+          let capCaseKey = key[0].toUpperCase() + key.substr(1);
+          let labelStr = FORMAT_LABEL[key] ? FORMAT_LABEL[key] : capCaseKey;
+          let mimeType = response[key].output_mimetype;
+          optionsMap[labelStr] = mimeType;
+        }
+      });
+      const nbConvert = CellTools.createNBConvertSelector(optionsMap);
+      celltools.addItem({ tool: nbConvert, rank: 3 });
+    }
+  });
   celltools.title.iconClass = 'jp-BuildIcon jp-SideBar-tabIcon';
   celltools.title.caption = 'Cell Inspector';
   celltools.id = id;
   celltools.addItem({ tool: activeCellTool, rank: 1 });
   celltools.addItem({ tool: slideShow, rank: 2 });
-  celltools.addItem({ tool: nbConvert, rank: 3 });
   celltools.addItem({ tool: metadataEditor, rank: 4 });
   MessageLoop.installMessageHook(celltools, hook);
 
@@ -393,7 +421,7 @@ function activateNotebookHandler(
   registry.addWidgetFactory(factory);
 
   addCommands(app, services, tracker);
-  populatePalette(palette);
+  populatePalette(palette, services);
 
   let id = 0; // The ID counter for notebook panels.
 
@@ -471,7 +499,7 @@ function activateNotebookHandler(
     });
 
   // Add main menu notebook menu.
-  populateMenus(app, mainMenu, tracker);
+  populateMenus(app, mainMenu, tracker, services, palette);
 
   // Utility function to create a new notebook.
   const createNew = (cwd: string, kernelName?: string) => {
@@ -1569,7 +1597,10 @@ function addCommands(
 /**
  * Populate the application's command palette with notebook commands.
  */
-function populatePalette(palette: ICommandPalette): void {
+function populatePalette(
+  palette: ICommandPalette,
+  services: ServiceManager
+): void {
   let category = 'Notebook Operations';
   [
     CommandIDs.interrupt,
@@ -1601,15 +1632,6 @@ function populatePalette(palette: ICommandPalette): void {
     args: { isPalette: true }
   });
 
-  EXPORT_TO_FORMATS.forEach(exportToFormat => {
-    let args = {
-      format: exportToFormat['format'],
-      label: exportToFormat['label'],
-      isPalette: true
-    };
-    palette.addItem({ command: CommandIDs.exportToFormat, category, args });
-  });
-
   category = 'Notebook Cell Operations';
   [
     CommandIDs.run,
@@ -1665,7 +1687,9 @@ function populatePalette(palette: ICommandPalette): void {
 function populateMenus(
   app: JupyterLab,
   mainMenu: IMainMenu,
-  tracker: INotebookTracker
+  tracker: INotebookTracker,
+  services: ServiceManager,
+  palette: ICommandPalette
 ): void {
   let { commands } = app;
 
@@ -1731,17 +1755,38 @@ function populateMenus(
   // Add a notebook group to the File menu.
   let exportTo = new Menu({ commands });
   exportTo.title.label = 'Export Notebook As…';
-  EXPORT_TO_FORMATS.forEach(exportToFormat => {
-    exportTo.addItem({
-      command: CommandIDs.exportToFormat,
-      args: exportToFormat
-    });
+  services.nbconvert.getExportFormats().then(response => {
+    if (response) {
+      // convert exportList to palette and menu items
+      const formatList = Object.keys(response);
+      const category = 'Notebook Operations';
+      formatList.forEach(function(key) {
+        let capCaseKey = key[0].toUpperCase() + key.substr(1);
+        let labelStr = FORMAT_LABEL[key] ? FORMAT_LABEL[key] : capCaseKey;
+        let args = {
+          format: key,
+          label: labelStr,
+          isPalette: true
+        };
+        if (FORMAT_EXCLUDE.indexOf(key) === -1) {
+          exportTo.addItem({
+            command: CommandIDs.exportToFormat,
+            args: args
+          });
+          palette.addItem({
+            command: CommandIDs.exportToFormat,
+            category,
+            args
+          });
+        }
+      });
+      const fileGroup = [
+        { command: CommandIDs.trust },
+        { type: 'submenu', submenu: exportTo } as Menu.IItemOptions
+      ];
+      mainMenu.fileMenu.addGroup(fileGroup, 10);
+    }
   });
-  const fileGroup = [
-    { command: CommandIDs.trust },
-    { type: 'submenu', submenu: exportTo } as Menu.IItemOptions
-  ];
-  mainMenu.fileMenu.addGroup(fileGroup, 10);
 
   // Add a kernel user to the Kernel menu
   mainMenu.kernelMenu.kernelUsers.add({

+ 4 - 9
packages/notebook/src/celltools.ts

@@ -655,18 +655,13 @@ export namespace CellTools {
   /**
    * Create an nbcovert selector.
    */
-  export function createNBConvertSelector(): KeySelector {
+  export function createNBConvertSelector(optionsMap: {
+    [key: string]: JSONValue;
+  }): KeySelector {
     return new KeySelector({
       key: 'raw_mimetype',
       title: 'Raw NBConvert Format',
-      optionsMap: {
-        None: '-',
-        LaTeX: 'text/latex',
-        reST: 'text/restructuredtext',
-        HTML: 'text/html',
-        Markdown: 'text/markdown',
-        Python: 'text/x-python'
-      },
+      optionsMap: optionsMap,
       validCellTypes: ['raw']
     });
   }

+ 1 - 1
packages/services/src/builder/index.ts

@@ -11,7 +11,7 @@ import { ServerConnection } from '../serverconnection';
 const BUILD_SETTINGS_URL = 'lab/api/build';
 
 /**
- * The static namespace for `BuildManager`.
+ * The build API service manager.
  */
 export class BuildManager {
   /**

+ 1 - 0
packages/services/src/index.ts

@@ -10,5 +10,6 @@ export * from './session';
 export * from './setting';
 export * from './terminal';
 export * from './workspace';
+export * from './nbconvert';
 
 export { Builder } from './builder';

+ 13 - 0
packages/services/src/manager.ts

@@ -7,6 +7,8 @@ import { ISignal, Signal } from '@phosphor/signaling';
 
 import { Builder, BuildManager } from './builder';
 
+import { NbConvert, NbConvertManager } from './nbconvert';
+
 import { Contents, ContentsManager } from './contents';
 
 import { Kernel } from './kernel';
@@ -38,6 +40,7 @@ export class ServiceManager implements ServiceManager.IManager {
     this.terminals = new TerminalManager(options);
     this.builder = new BuildManager(options);
     this.workspaces = new WorkspaceManager(options);
+    this.nbconvert = new NbConvertManager(options);
 
     this.sessions.specsChanged.connect((sender, specs) => {
       this._specsChanged.emit(specs);
@@ -124,6 +127,11 @@ export class ServiceManager implements ServiceManager.IManager {
    */
   readonly workspaces: WorkspaceManager;
 
+  /**
+   * Get the nbconvert manager instance.
+   */
+  readonly nbconvert: NbConvertManager;
+
   /**
    * Test whether the manager is ready.
    */
@@ -206,6 +214,11 @@ export namespace ServiceManager {
      * The workspace manager for the manager.
      */
     readonly workspaces: Workspace.IManager;
+
+    /**
+     * The nbconvert manager for the manager.
+     */
+    readonly nbconvert: NbConvert.IManager;
   }
 
   /**

+ 92 - 0
packages/services/src/nbconvert/index.ts

@@ -0,0 +1,92 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+
+import { URLExt } from '@jupyterlab/coreutils';
+
+import { ServerConnection } from '../serverconnection';
+
+/**
+ * The url for the lab nbconvert service.
+ */
+const NBCONVERT_SETTINGS_URL = 'api/nbconvert';
+
+/**
+ * The nbconvert API service manager.
+ */
+export class NbConvertManager {
+  /**
+   * Create a new nbconvert manager.
+   */
+  constructor(options: NbConvertManager.IOptions = {}) {
+    this.serverSettings =
+      options.serverSettings || ServerConnection.makeSettings();
+  }
+
+  /**
+   * The server settings used to make API requests.
+   */
+  readonly serverSettings: ServerConnection.ISettings;
+
+  /**
+   * Get whether the application should be built.
+   */
+  getExportFormats(): Promise<NbConvertManager.IExportFormats> {
+    const base = this.serverSettings.baseUrl;
+    const url = URLExt.join(base, NBCONVERT_SETTINGS_URL);
+    const { serverSettings } = this;
+    const promise = ServerConnection.makeRequest(url, {}, serverSettings);
+
+    return promise
+      .then(response => {
+        if (response.status !== 200) {
+          throw new ServerConnection.ResponseError(response);
+        }
+
+        return response.json();
+      })
+      .then(data => {
+        let exportList: NbConvertManager.IExportFormats = {};
+        let keys = Object.keys(data);
+        keys.forEach(function(key) {
+          let mimeType: string = data[key].output_mimetype;
+          exportList[key] = { output_mimetype: mimeType };
+        });
+        return exportList;
+      });
+  }
+}
+
+/**
+ * A namespace for `BuildManager` statics.
+ */
+export namespace NbConvertManager {
+  /**
+   * The instantiation options for a setting manager.
+   */
+  export interface IOptions {
+    /**
+     * The server settings used to make API requests.
+     */
+    serverSettings?: ServerConnection.ISettings;
+  }
+
+  /**
+   * A namespace for nbconvert API interfaces.
+   */
+  export interface IExportFormats {
+    /**
+     * The list of supported export formats.
+     */
+    [key: string]: { output_mimetype: string };
+  }
+}
+
+/**
+ * A namespace for builder API interfaces.
+ */
+export namespace NbConvert {
+  /**
+   * The interface for the build manager.
+   */
+  export interface IManager extends NbConvertManager {}
+}

+ 21 - 2
tests/test-notebook/src/celltools.spec.ts

@@ -7,6 +7,8 @@ import { Message } from '@phosphor/messaging';
 
 import { TabPanel, Widget } from '@phosphor/widgets';
 
+import { JSONValue } from '@phosphor/coreutils';
+
 import { simulate } from 'simulate-event';
 
 import { CodeMirrorEditorFactory } from '@jupyterlab/codemirror';
@@ -413,7 +415,16 @@ describe('@jupyterlab/notebook', () => {
 
     describe('CellTools.createNBConvertSelector()', () => {
       it('should create a raw mimetype selector', () => {
-        const tool = CellTools.createNBConvertSelector();
+        let optionsMap: { [key: string]: JSONValue } = {
+          None: '-',
+          LaTeX: 'text/latex',
+          reST: 'text/restructuredtext',
+          HTML: 'text/html',
+          Markdown: 'text/markdown',
+          Python: 'text/x-python'
+        };
+        optionsMap.None = '-';
+        const tool = CellTools.createNBConvertSelector(optionsMap);
         tool.selectNode.selectedIndex = -1;
         celltools.addItem({ tool });
         simulate(panel0.node, 'focus');
@@ -433,7 +444,15 @@ describe('@jupyterlab/notebook', () => {
       });
 
       it('should have no effect on a code cell', () => {
-        const tool = CellTools.createNBConvertSelector();
+        let optionsMap: { [key: string]: JSONValue } = {
+          None: '-',
+          LaTeX: 'text/latex',
+          reST: 'text/restructuredtext',
+          HTML: 'text/html',
+          Markdown: 'text/markdown',
+          Python: 'text/x-python'
+        };
+        const tool = CellTools.createNBConvertSelector(optionsMap);
         tool.selectNode.selectedIndex = -1;
         celltools.addItem({ tool });
         simulate(panel0.node, 'focus');