Skip to content

Commit 9d382de

Browse files
authored
Added exporting notebook to HTML (#12375)
* export working * added export base * file opens when click yes button * small changes to exportManagerFileOpener * small changes * added tests for html (new file) * localized string * added news file * added missing keys to package.nls.json * addressed comments * removes strange space * checked exported file is HTML file * modified test * fixed tests * changed launch.json back to original
1 parent 5b55c59 commit 9d382de

File tree

18 files changed

+246
-37
lines changed

18 files changed

+246
-37
lines changed

.vscode/launch.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@
176176
"--extensionTestsPath=${workspaceFolder}/out/test"
177177
],
178178
"env": {
179-
"VSC_PYTHON_CI_TEST_GREP": "" // Modify this to run a subset of the single workspace tests
179+
"VSC_PYTHON_CI_TEST_GREP": "DataScience - Export HTML" // Modify this to run a subset of the single workspace tests
180180
},
181181
"stopOnEntry": false,
182182
"sourceMaps": true,

news/1 Enhancements/12375.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added exporting notebooks to HTML

package.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,11 @@
360360
"title": "%python.command.python.datascience.exportAsPythonScript.title%",
361361
"category": "Python"
362362
},
363+
{
364+
"command": "python.datascience.exportToHTML",
365+
"title": "%python.command.python.datascience.exportToHTML.title%",
366+
"category": "Python"
367+
},
363368
{
364369
"command": "python.datascience.selectJupyterInterpreter",
365370
"title": "%python.command.python.datascience.selectJupyterInterpreter.title%",
@@ -858,6 +863,12 @@
858863
"category": "Python",
859864
"when": "python.datascience.isnativeactive && python.datascience.featureenabled"
860865
},
866+
{
867+
"command": "python.datascience.exportToHTML",
868+
"title": "%python.command.python.datascience.exportToHTML.title%",
869+
"category": "Python",
870+
"when": "python.datascience.isnativeactive && python.datascience.featureenabled"
871+
},
861872
{
862873
"command": "python.switchOffInsidersChannel",
863874
"title": "%python.command.python.switchOffInsidersChannel.title%",

package.nls.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,15 @@
2020
"python.command.python.datascience.exportAsPythonScript.title": "Export as Python Script",
2121
"python.command.python.datascience.exportToHTML.title": "Export to HTML",
2222
"python.command.python.datascience.exportToPDF.title": "Export to PDF",
23+
"DataScience.checkingIfImportIsSupported" : "Checking if import is supported",
24+
"DataScience.installingMissingDependencies" : "Installing missing dependencies",
25+
"DataScience.exportNotebookToPython" : "Exporting Notebook to Python",
26+
"DataScience.performingExport" : "Performing export",
27+
"DataScience.exportHTMLQuickPickLabel" : " HTML",
28+
"DataScience.openExportedFileMessage" : "Would you like to open the exported file?",
29+
"DataScience.openExportFileYes" : "Yes",
30+
"DataScience.openExportFileNo" : "No",
31+
"DataScience.failedExportMessage": "Export failed.",
2332
"python.command.python.viewLanguageServerOutput.title": "Show Language Server Output",
2433
"python.command.python.selectAndRunTestMethod.title": "Run Test Method ...",
2534
"python.command.python.selectAndDebugTestMethod.title": "Debug Test Method ...",

src/client/common/utils/localize.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,19 @@ export namespace DataScience {
296296
'DataScience.badWebPanelFormatString',
297297
'<html><body><h1>{0} is not a valid file name</h1></body></html>'
298298
);
299+
export const checkingIfImportIsSupported = localize(
300+
'DataScience.checkingIfImportIsSupported',
301+
'Checking if import is supported'
302+
);
303+
export const installingMissingDependencies = localize(
304+
'DataScience.installingMissingDependencies',
305+
'Installing missing dependencies'
306+
);
307+
export const performingExport = localize('DataScience.performingExport', 'Performing export');
308+
export const exportNotebookToPython = localize(
309+
'DataScience.exportNotebookToPython',
310+
'Exporting Notebook to Python'
311+
);
299312
export const sessionDisposed = localize(
300313
'DataScience.sessionDisposed',
301314
'Cannot execute code, session has been disposed.'
@@ -561,6 +574,7 @@ export namespace DataScience {
561574
export const interruptKernelStatus = localize('DataScience.interruptKernelStatus', 'Interrupting IPython Kernel');
562575
export const exportCancel = localize('DataScience.exportCancel', 'Cancel');
563576
export const exportPythonQuickPickLabel = localize('DataScience.exportPythonQuickPickLabel', 'Python Script');
577+
export const exportHTMLQuickPickLabel = localize('DataScience.exportHTMLQuickPickLabel', ' HTML');
564578
export const restartKernelAfterInterruptMessage = localize(
565579
'DataScience.restartKernelAfterInterruptMessage',
566580
'Interrupting the kernel timed out. Do you want to restart the kernel instead? All variables will be lost.'
@@ -768,6 +782,13 @@ export namespace DataScience {
768782
export const notebookExportAs = localize('DataScience.notebookExportAs', 'Convert and save to a python script');
769783
export const exportAsPythonFileTitle = localize('DataScience.exportAsPythonFileTitle', 'Save As Python File');
770784
export const exportAsQuickPickPlaceholder = localize('DataScience.exportAsQuickPickPlaceholder', 'Export As...');
785+
export const openExportedFileMessage = localize(
786+
'DataScience.openExportedFileMessage',
787+
'Would you like to open the exported file?'
788+
);
789+
export const openExportFileYes = localize('DataScience.openExportFileYes', 'Yes');
790+
export const openExportFileNo = localize('DataScience.openExportFileNo', 'No');
791+
export const failedExportMessage = localize('DataScience.failedExportMessage', 'Export failed.');
771792
export const runCell = localize('DataScience.runCell', 'Run cell');
772793
export const deleteCell = localize('DataScience.deleteCell', 'Delete cell');
773794
export const moveCellUp = localize('DataScience.moveCellUp', 'Move cell up');

src/client/datascience/commands/exportCommands.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,12 @@ export class ExportCommands implements IDisposable {
7777
label: DataScience.exportPythonQuickPickLabel(),
7878
picked: true,
7979
handler: () => this.commandManager.executeCommand(Commands.ExportAsPythonScript, model)
80+
},
81+
{
82+
label: DataScience.exportHTMLQuickPickLabel(),
83+
picked: false,
84+
handler: () => this.commandManager.executeCommand(Commands.ExportToHTML, model)
8085
}
81-
//{ label: 'HTML', picked: false, handler: () => this.commandManager.executeCommand(Commands.ExportToHTML) },
8286
//{ label: 'PDF', picked: false, handler: () => this.commandManager.executeCommand(Commands.ExportToPDF) }
8387
];
8488
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { inject, injectable } from 'inversify';
2+
import { Uri } from 'vscode';
3+
import { IFileSystem } from '../../common/platform/types';
4+
import { IPythonExecutionFactory, IPythonExecutionService } from '../../common/process/types';
5+
import { reportAction } from '../progress/decorator';
6+
import { ReportableAction } from '../progress/types';
7+
import { IJupyterSubCommandExecutionService, INotebookImporter } from '../types';
8+
import { IExport } from './types';
9+
10+
@injectable()
11+
export class ExportBase implements IExport {
12+
constructor(
13+
@inject(IPythonExecutionFactory) protected readonly pythonExecutionFactory: IPythonExecutionFactory,
14+
@inject(IJupyterSubCommandExecutionService)
15+
protected jupyterService: IJupyterSubCommandExecutionService,
16+
@inject(IFileSystem) protected readonly fileSystem: IFileSystem,
17+
@inject(INotebookImporter) protected readonly importer: INotebookImporter
18+
) {}
19+
20+
// tslint:disable-next-line: no-empty
21+
public async export(_source: Uri, _target: Uri): Promise<void> {}
22+
23+
@reportAction(ReportableAction.PerformingExport)
24+
public async executeCommand(source: Uri, args: string[]): Promise<void> {
25+
const service = await this.getExecutionService(source);
26+
if (!service) {
27+
return;
28+
}
29+
await service.execModule('jupyter', ['nbconvert'].concat(args), { throwOnStdErr: false, encoding: 'utf8' });
30+
}
31+
32+
protected async getExecutionService(source: Uri): Promise<IPythonExecutionService | undefined> {
33+
return this.pythonExecutionFactory.createActivatedEnvironment({
34+
resource: source,
35+
allowEnvironmentFetchExceptions: false,
36+
bypassCondaExecution: true
37+
});
38+
}
39+
}
Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { inject, injectable } from 'inversify';
22
import { Uri } from 'vscode';
3-
import { IDocumentManager } from '../../common/application/types';
3+
import { getLocString } from '../../../datascience-ui/react-common/locReactSide';
4+
import { IApplicationShell, IDocumentManager } from '../../common/application/types';
45
import { PYTHON_LANGUAGE } from '../../common/constants';
56
import { IFileSystem } from '../../common/platform/types';
7+
import { IBrowserService } from '../../common/types';
68
import { ProgressReporter } from '../progress/progressReporter';
79
import { INotebookModel } from '../types';
810
import { ExportManagerDependencyChecker } from './exportManagerDependencyChecker';
@@ -14,26 +16,30 @@ export class ExportManagerFileOpener implements IExportManager {
1416
@inject(ExportManagerDependencyChecker) private readonly manager: IExportManager,
1517
@inject(IDocumentManager) protected readonly documentManager: IDocumentManager,
1618
@inject(ProgressReporter) private readonly progressReporter: ProgressReporter,
17-
@inject(IFileSystem) private readonly fileSystem: IFileSystem
19+
@inject(IFileSystem) private readonly fileSystem: IFileSystem,
20+
@inject(IApplicationShell) private readonly applicationShell: IApplicationShell,
21+
@inject(IBrowserService) private readonly browserService: IBrowserService
1822
) {}
1923

2024
public async export(format: ExportFormat, model: INotebookModel): Promise<Uri | undefined> {
2125
const reporter = this.progressReporter.createProgressIndicator(`Exporting to ${format}`); // need to localize
2226
let uri: Uri | undefined;
2327
try {
2428
uri = await this.manager.export(format, model);
25-
if (!uri) {
26-
return;
27-
}
29+
} catch (e) {
30+
await this.showExportFailed(e);
2831
} finally {
2932
reporter.dispose();
3033
}
3134

32-
if (format === ExportFormat.python) {
33-
await this.openPythonFile(uri);
34-
} else {
35-
throw new Error('Not supported');
35+
if (uri) {
36+
if (format === ExportFormat.python) {
37+
await this.openPythonFile(uri);
38+
} else {
39+
await this.askOpenFile(uri);
40+
}
3641
}
42+
return;
3743
}
3844

3945
private async openPythonFile(uri: Uri): Promise<void> {
@@ -42,4 +48,28 @@ export class ExportManagerFileOpener implements IExportManager {
4248
const doc = await this.documentManager.openTextDocument({ language: PYTHON_LANGUAGE, content: contents });
4349
await this.documentManager.showTextDocument(doc);
4450
}
51+
52+
private async showExportFailed(msg: string) {
53+
await this.applicationShell.showErrorMessage(
54+
// tslint:disable-next-line: messages-must-be-localized
55+
`${getLocString('DataScience.failedExportMessage', 'Export failed')} ${msg}`
56+
);
57+
}
58+
59+
private async askOpenFile(uri: Uri): Promise<void> {
60+
const yes = getLocString('DataScience.openExportFileYes', 'Yes');
61+
const no = getLocString('DataScience.openExportFileNo', 'No');
62+
const items = [yes, no];
63+
64+
const selected = await this.applicationShell
65+
.showInformationMessage(
66+
getLocString('DataScience.openExportedFileMessage', 'Would you like to open the exported file?'),
67+
...items
68+
)
69+
.then((item) => item);
70+
71+
if (selected === yes) {
72+
this.browserService.launch(uri.toString());
73+
}
74+
}
4575
}
Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,20 @@
11
import { injectable } from 'inversify';
2+
import * as path from 'path';
23
import { Uri } from 'vscode';
3-
import { IExport } from './types';
4+
import { ExportBase } from './exportBase';
45

56
@injectable()
6-
export class ExportToHTML implements IExport {
7-
// tslint:disable-next-line: no-empty
8-
public async export(_source: Uri, _target: Uri): Promise<void> {}
7+
export class ExportToHTML extends ExportBase {
8+
public async export(source: Uri, target: Uri): Promise<void> {
9+
const args = [
10+
source.fsPath,
11+
'--to',
12+
'html',
13+
'--output',
14+
path.basename(target.fsPath),
15+
'--output-dir',
16+
path.dirname(target.fsPath)
17+
];
18+
await this.executeCommand(source, args);
19+
}
920
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { injectable } from 'inversify';
22
import { Uri } from 'vscode';
3-
import { IExport } from './types';
3+
import { ExportBase } from './exportBase';
44

55
@injectable()
6-
export class ExportToPDF implements IExport {
6+
export class ExportToPDF extends ExportBase {
77
// tslint:disable-next-line: no-empty
88
public async export(_source: Uri, _target: Uri): Promise<void> {}
99
}

src/client/datascience/export/exportToPython.ts

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,9 @@
1-
import { inject, injectable } from 'inversify';
1+
import { injectable } from 'inversify';
22
import { Uri } from 'vscode';
3-
import { IFileSystem } from '../../common/platform/types';
4-
import { INotebookImporter } from '../types';
5-
import { IExport } from './types';
3+
import { ExportBase } from './exportBase';
64

75
@injectable()
8-
export class ExportToPython implements IExport {
9-
constructor(
10-
@inject(IFileSystem) protected readonly fileSystem: IFileSystem,
11-
@inject(INotebookImporter) protected readonly importer: INotebookImporter
12-
) {}
13-
6+
export class ExportToPython extends ExportBase {
147
public async export(source: Uri, target: Uri): Promise<void> {
158
const contents = await this.importer.importFromFile(source.fsPath);
169
await this.fileSystem.writeFile(target.fsPath, contents);

src/client/datascience/progress/messages.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@ const progressMessages = {
1515
[ReportableAction.NotebookConnect]: DataScience.connectingToJupyter(),
1616
[ReportableAction.NotebookStart]: DataScience.startingJupyterNotebook(),
1717
[ReportableAction.RawKernelConnecting]: DataScience.rawKernelConnectingSession(),
18-
[ReportableAction.CheckingIfImportIsSupported]: 'Checking if import is supported', // Localize these later
19-
[ReportableAction.InstallingMissingDependencies]: 'Installing missing dependencies',
20-
[ReportableAction.ExportNotebookToPython]: 'Exporting Notebook to Python'
18+
[ReportableAction.CheckingIfImportIsSupported]: DataScience.checkingIfImportIsSupported(), // Localize these later
19+
[ReportableAction.InstallingMissingDependencies]: DataScience.installingMissingDependencies(),
20+
[ReportableAction.ExportNotebookToPython]: DataScience.exportNotebookToPython(),
21+
[ReportableAction.PerformingExport]: DataScience.performingExport()
2122
};
2223

2324
/**

src/client/datascience/progress/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,5 +51,6 @@ export enum ReportableAction {
5151
RawKernelConnecting = 'RawKernelConnecting',
5252
CheckingIfImportIsSupported = 'CheckingIfImportIsSupported',
5353
InstallingMissingDependencies = 'InstallingMissingDependencies',
54-
ExportNotebookToPython = 'ExportNotebookToPython'
54+
ExportNotebookToPython = 'ExportNotebookToPython',
55+
PerformingExport = 'PerformingExport'
5556
}

src/client/datascience/serviceRegistry.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import { CodeWatcher } from './editor-integration/codewatcher';
3535
import { Decorator } from './editor-integration/decorator';
3636
import { HoverProvider } from './editor-integration/hoverProvider';
3737
import { DataScienceErrorHandler } from './errorHandler/errorHandler';
38+
import { ExportBase } from './export/exportBase';
3839
import { ExportManager } from './export/exportManager';
3940
import { ExportManagerDependencyChecker } from './export/exportManagerDependencyChecker';
4041
import { ExportManagerFileOpener } from './export/exportManagerFileOpener';
@@ -282,6 +283,7 @@ export function registerTypes(serviceManager: IServiceManager) {
282283
serviceManager.addSingleton<IExport>(IExport, ExportToPDF, ExportFormat.pdf);
283284
serviceManager.addSingleton<IExport>(IExport, ExportToHTML, ExportFormat.html);
284285
serviceManager.addSingleton<IExport>(IExport, ExportToPython, ExportFormat.python);
286+
serviceManager.addSingleton<IExport>(IExport, ExportBase, 'Export Base');
285287
serviceManager.addSingleton<ExportCommands>(ExportCommands, ExportCommands);
286288
serviceManager.addSingleton<IExportManagerFilePicker>(IExportManagerFilePicker, ExportManagerFilePicker);
287289

0 commit comments

Comments
 (0)