Skip to content

Commit

Permalink
Added export notebook to PDF (#12517)
Browse files Browse the repository at this point in the history
* temp work

* started working on dependency checking

* good start on pdf

* removed space from label

* added to command pallete

* changed pdf method to TeX

* fixed pdf title

* removed unecessary changes

* changed back package-lock.json

* cleaned up dependency checker

* made changes and add telemetry

* changed from 2 to 1 error message

* changed name for temp directory

* removed duplicate import

* removed mardown prefix

* deleted temp file
  • Loading branch information
techwithtim committed Jun 24, 2020
1 parent 3bb92e1 commit 036621a
Show file tree
Hide file tree
Showing 11 changed files with 138 additions and 33 deletions.
11 changes: 11 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,11 @@
"title": "%python.command.python.datascience.exportToHTML.title%",
"category": "Python"
},
{
"command": "python.datascience.exportToPDF",
"title": "%python.command.python.datascience.exportToPDF.title%",
"category": "Python"
},
{
"command": "python.datascience.selectJupyterInterpreter",
"title": "%python.command.python.datascience.selectJupyterInterpreter.title%",
Expand Down Expand Up @@ -881,6 +886,12 @@
"category": "Python",
"when": "python.datascience.isnativeactive && python.datascience.featureenabled"
},
{
"command": "python.datascience.exportToPDF",
"title": "%python.command.python.datascience.exportToPDF.title%",
"category": "Python",
"when": "python.datascience.isnativeactive && python.datascience.featureenabled"
},
{
"command": "python.switchOffInsidersChannel",
"title": "%python.command.python.switchOffInsidersChannel.title%",
Expand Down
21 changes: 12 additions & 9 deletions package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,18 @@
"python.command.python.datascience.exportAsPythonScript.title": "Export as Python Script",
"python.command.python.datascience.exportToHTML.title": "Export to HTML",
"python.command.python.datascience.exportToPDF.title": "Export to PDF",
"DataScience.checkingIfImportIsSupported" : "Checking if import is supported",
"DataScience.installingMissingDependencies" : "Installing missing dependencies",
"DataScience.exportNotebookToPython" : "Exporting Notebook to Python",
"DataScience.performingExport" : "Performing export",
"DataScience.exportHTMLQuickPickLabel" : " HTML",
"DataScience.openExportedFileMessage" : "Would you like to open the exported file?",
"DataScience.openExportFileYes" : "Yes",
"DataScience.openExportFileNo" : "No",
"DataScience.failedExportMessage": "Export failed.",
"DataScience.checkingIfImportIsSupported": "Checking if import is supported",
"DataScience.installingMissingDependencies": "Installing missing dependencies",
"DataScience.exportNotebookToPython": "Exporting Notebook to Python",
"DataScience.performingExport": "Performing export",
"DataScience.convertingToPDF": "Converting to PDF",
"DataScience.exportHTMLQuickPickLabel": "HTML",
"DataScience.exportPDFQuickPickLabel": "PDF",
"DataScience.openExportedFileMessage": "Would you like to open the exported file?",
"DataScience.openExportFileYes": "Yes",
"DataScience.openExportFileNo": "No",
"DataScience.failedExportMessage": "Export failed.",
"DataScience.exportToPDFDependencyMessage": "If you have not installed xelatex (TeX) you will need to do so before you can export to PDF, for further instructions please look [here](https://nbconvert.readthedocs.io/en/latest/install.html#installing-tex). \r\nTo avoid installing xelatex (TeX) you might want to try exporting to HTML and using your browsers \"Print to PDF\" feature.",
"python.command.python.viewLanguageServerOutput.title": "Show Language Server Output",
"python.command.python.selectAndRunTestMethod.title": "Run Test Method ...",
"python.command.python.selectAndDebugTestMethod.title": "Debug Test Method ...",
Expand Down
6 changes: 6 additions & 0 deletions src/client/common/utils/localize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@ export namespace DataScience {
'Installing missing dependencies'
);
export const performingExport = localize('DataScience.performingExport', 'Performing export');
export const convertingToPDF = localize('DataScience.convertingToPDF', 'Converting to PDF');
export const exportNotebookToPython = localize(
'DataScience.exportNotebookToPython',
'Exporting Notebook to Python'
Expand Down Expand Up @@ -571,6 +572,7 @@ export namespace DataScience {
export const exportCancel = localize('DataScience.exportCancel', 'Cancel');
export const exportPythonQuickPickLabel = localize('DataScience.exportPythonQuickPickLabel', 'Python Script');
export const exportHTMLQuickPickLabel = localize('DataScience.exportHTMLQuickPickLabel', 'HTML');
export const exportPDFQuickPickLabel = localize('DataScience.exportPDFQuickPickLabel', 'PDF');
export const restartKernelAfterInterruptMessage = localize(
'DataScience.restartKernelAfterInterruptMessage',
'Interrupting the kernel timed out. Do you want to restart the kernel instead? All variables will be lost.'
Expand Down Expand Up @@ -784,6 +786,10 @@ export namespace DataScience {
);
export const openExportFileYes = localize('DataScience.openExportFileYes', 'Yes');
export const openExportFileNo = localize('DataScience.openExportFileNo', 'No');
export const exportToPDFDependencyMessage = localize(
'DataScience.exportToPDFDependencyMessage',
'If you have not installed xelatex (TeX) you will need to do so before you can export to PDF, for further instructions please look [here](https://nbconvert.readthedocs.io/en/latest/install.html#installing-tex). \r\nTo avoid installing xelatex (TeX) you might want to try exporting to HTML and using your browsers "Print to PDF" feature.'
);
export const failedExportMessage = localize('DataScience.failedExportMessage', 'Export failed.');
export const runCell = localize('DataScience.runCell', 'Run cell');
export const deleteCell = localize('DataScience.deleteCell', 'Delete cell');
Expand Down
11 changes: 10 additions & 1 deletion src/client/datascience/commands/exportCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,17 @@ export class ExportCommands implements IDisposable {
});
this.commandManager.executeCommand(Commands.ExportToHTML, model);
}
},
{
label: DataScience.exportPDFQuickPickLabel(),
picked: false,
handler: () => {
sendTelemetryEvent(Telemetry.ClickedExportNotebookAsQuickPick, undefined, {
format: ExportFormat.pdf
});
this.commandManager.executeCommand(Commands.ExportToPDF, model);
}
}
//{ label: 'PDF', picked: false, handler: () => this.commandManager.executeCommand(Commands.ExportToPDF) }
];
}

Expand Down
6 changes: 5 additions & 1 deletion src/client/datascience/export/exportManager.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { inject, injectable, named } from 'inversify';
import { Uri } from 'vscode';
import { IFileSystem, TemporaryFile } from '../../common/platform/types';
import { ProgressReporter } from '../progress/progressReporter';
import { IDataScienceErrorHandler, INotebookModel } from '../types';
import { IExportManagerFilePicker } from './exportManagerFilePicker';
import { ExportFormat, IExport, IExportManager } from './types';
Expand All @@ -13,7 +14,8 @@ export class ExportManager implements IExportManager {
@inject(IExport) @named(ExportFormat.python) private readonly exportToPython: IExport,
@inject(IFileSystem) private readonly fileSystem: IFileSystem,
@inject(IDataScienceErrorHandler) private readonly errorHandler: IDataScienceErrorHandler,
@inject(IExportManagerFilePicker) private readonly filePicker: IExportManagerFilePicker
@inject(IExportManagerFilePicker) private readonly filePicker: IExportManagerFilePicker,
@inject(ProgressReporter) private readonly progressReporter: ProgressReporter
) {}

public async export(format: ExportFormat, model: INotebookModel): Promise<Uri | undefined> {
Expand All @@ -32,6 +34,7 @@ export class ExportManager implements IExportManager {
return; // error making temp file
}

const reporter = this.progressReporter.createProgressIndicator(`Exporting to ${format}`);
const source = Uri.file(tempFile.filePath);
try {
switch (format) {
Expand All @@ -52,6 +55,7 @@ export class ExportManager implements IExportManager {
}
} finally {
tempFile.dispose();
reporter.dispose();
}

return target;
Expand Down
23 changes: 14 additions & 9 deletions src/client/datascience/export/exportManagerDependencyChecker.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { inject, injectable } from 'inversify';
import { Uri } from 'vscode';
import * as localize from '../../common/utils/localize';
import { ProgressReporter } from '../progress/progressReporter';
import { IJupyterExecution, IJupyterInterpreterDependencyManager, INotebookModel } from '../types';
import { ExportManager } from './exportManager';
import { ExportFormat, IExportManager } from './types';
Expand All @@ -11,19 +12,23 @@ export class ExportManagerDependencyChecker implements IExportManager {
@inject(ExportManager) private readonly manager: IExportManager,
@inject(IJupyterExecution) private jupyterExecution: IJupyterExecution,
@inject(IJupyterInterpreterDependencyManager)
private readonly dependencyManager: IJupyterInterpreterDependencyManager
private readonly dependencyManager: IJupyterInterpreterDependencyManager,
@inject(ProgressReporter) private readonly progressReporter: ProgressReporter
) {}

public async export(format: ExportFormat, model: INotebookModel): Promise<Uri | undefined> {
// Before we try the import, see if we don't support it, if we don't give a chance to install dependencies
if (!(await this.jupyterExecution.isImportSupported())) {
await this.dependencyManager.installMissingDependencies();
}

if (await this.jupyterExecution.isImportSupported()) {
return this.manager.export(format, model);
} else {
throw new Error(localize.DataScience.jupyterNbConvertNotSupported());
const reporter = this.progressReporter.createProgressIndicator(`Exporting to ${format}`);
try {
if (!(await this.jupyterExecution.isImportSupported())) {
await this.dependencyManager.installMissingDependencies();
if (!(await this.jupyterExecution.isImportSupported())) {
throw new Error(localize.DataScience.jupyterNbConvertNotSupported());
}
}
} finally {
reporter.dispose();
}
return this.manager.export(format, model);
}
}
16 changes: 9 additions & 7 deletions src/client/datascience/export/exportManagerFileOpener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import { PYTHON_LANGUAGE } from '../../common/constants';
import { traceError } from '../../common/logger';
import { IFileSystem } from '../../common/platform/types';
import { IBrowserService } from '../../common/types';
import { DataScience } from '../../common/utils/localize';
import { sendTelemetryEvent } from '../../telemetry';
import { Telemetry } from '../constants';
import { ProgressReporter } from '../progress/progressReporter';
import { INotebookModel } from '../types';
import { ExportManagerDependencyChecker } from './exportManagerDependencyChecker';
import { ExportFormat, IExportManager } from './types';
Expand All @@ -18,24 +18,26 @@ export class ExportManagerFileOpener implements IExportManager {
constructor(
@inject(ExportManagerDependencyChecker) private readonly manager: IExportManager,
@inject(IDocumentManager) protected readonly documentManager: IDocumentManager,
@inject(ProgressReporter) private readonly progressReporter: ProgressReporter,
@inject(IFileSystem) private readonly fileSystem: IFileSystem,
@inject(IApplicationShell) private readonly applicationShell: IApplicationShell,
@inject(IBrowserService) private readonly browserService: IBrowserService
) {}

public async export(format: ExportFormat, model: INotebookModel): Promise<Uri | undefined> {
const reporter = this.progressReporter.createProgressIndicator(`Exporting to ${format}`);
let uri: Uri | undefined;
try {
uri = await this.manager.export(format, model);
} catch (e) {
let msg = e;
traceError('Export failed', e);
this.showExportFailed(e);
sendTelemetryEvent(Telemetry.ExportNotebookAsFailed, undefined, { format: format });

if (format === ExportFormat.pdf) {
msg = DataScience.exportToPDFDependencyMessage();
}

this.showExportFailed(msg);
return;
} finally {
reporter.dispose();
}

if (!uri) {
Expand Down Expand Up @@ -72,7 +74,7 @@ export class ExportManagerFileOpener implements IExportManager {
this.applicationShell
.showErrorMessage(
// tslint:disable-next-line: messages-must-be-localized
`${getLocString('DataScience.failedExportMessage', 'Export failed')} ${msg}`
`${getLocString('DataScience.failedExportMessage', 'Export failed.')} ${msg}`
)
.then();
}
Expand Down
70 changes: 67 additions & 3 deletions src/client/datascience/export/exportToPDF.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,73 @@
import { injectable } from 'inversify';
import { inject, injectable } from 'inversify';
import * as path from 'path';
import { Uri } from 'vscode';
import { IFileSystem } from '../../common/platform/types';
import { IPythonExecutionFactory } from '../../common/process/types';
import { IJupyterSubCommandExecutionService, INotebookImporter } from '../types';
import { ExportBase } from './exportBase';

@injectable()
export class ExportToPDF extends ExportBase {
// tslint:disable-next-line: no-empty
public async export(_source: Uri, _target: Uri): Promise<void> {}
constructor(
@inject(IPythonExecutionFactory) protected readonly pythonExecutionFactory: IPythonExecutionFactory,
@inject(IJupyterSubCommandExecutionService)
protected jupyterService: IJupyterSubCommandExecutionService,
@inject(IFileSystem) protected readonly fileSystem: IFileSystem,
@inject(INotebookImporter) protected readonly importer: INotebookImporter
) {
super(pythonExecutionFactory, jupyterService, fileSystem, importer);
}

public async export(source: Uri, target: Uri): Promise<void> {
const tempFile = await this.fileSystem.createTemporaryFile('.ipynb');
const directoryPath = path.join(
path.dirname(tempFile.filePath),
path.basename(tempFile.filePath, path.extname(tempFile.filePath))
);
tempFile.dispose();
const newFileName = path.basename(target.fsPath, path.extname(target.fsPath));
const newSource = Uri.file(await this.createNewFile(directoryPath, newFileName, source));

const args = [
newSource.fsPath,
'--to',
'pdf',
'--output',
path.basename(target.fsPath),
'--output-dir',
path.dirname(target.fsPath)
];
try {
await this.executeCommand(newSource, target, args);
} finally {
await this.deleteNewDirectory(directoryPath);
}
}

private async createNewFile(dirPath: string, newName: string, source: Uri): Promise<string> {
// When exporting to PDF we need to change the source files name to match
// what the title of the pdf should be.
// To ensure the new file path is unique we will create a directory and
// save the new file there
try {
await this.fileSystem.createDirectory(dirPath);
const newFilePath = path.join(dirPath, newName);
await this.fileSystem.copyFile(source.fsPath, newFilePath);
return newFilePath;
} catch (e) {
await this.deleteNewDirectory(dirPath);
throw e;
}
}

private async deleteNewDirectory(dirPath: string) {
if (!(await this.fileSystem.directoryExists(dirPath))) {
return;
}
const files = await this.fileSystem.getFiles(dirPath);
for (const file of files) {
await this.fileSystem.deleteFile(file);
}
await this.fileSystem.deleteDirectory(dirPath);
}
}
3 changes: 2 additions & 1 deletion src/client/datascience/progress/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ const progressMessages = {
[ReportableAction.CheckingIfImportIsSupported]: DataScience.checkingIfImportIsSupported(), // Localize these later
[ReportableAction.InstallingMissingDependencies]: DataScience.installingMissingDependencies(),
[ReportableAction.ExportNotebookToPython]: DataScience.exportNotebookToPython(),
[ReportableAction.PerformingExport]: DataScience.performingExport()
[ReportableAction.PerformingExport]: DataScience.performingExport(),
[ReportableAction.ConvertingToPDF]: DataScience.convertingToPDF()
};

/**
Expand Down
3 changes: 2 additions & 1 deletion src/client/datascience/progress/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,6 @@ export enum ReportableAction {
CheckingIfImportIsSupported = 'CheckingIfImportIsSupported',
InstallingMissingDependencies = 'InstallingMissingDependencies',
ExportNotebookToPython = 'ExportNotebookToPython',
PerformingExport = 'PerformingExport'
PerformingExport = 'PerformingExport',
ConvertingToPDF = 'ConvertingToPDF'
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ suite('Data Science - Export File Opener', () => {
fileOpener = new ExportManagerFileOpener(
instance(exporter),
instance(documentManager),
instance(reporter),
instance(fileSystem),
instance(applicationShell),
instance(browserService)
Expand Down

0 comments on commit 036621a

Please sign in to comment.