Skip to content

Commit ea46cfc

Browse files
authored
Store file extension configuration next to your DataPlugin (#22)
* Better file extension handling * Store file extensions on first save * Readme adjusted * Initial unit test for file-utils * Work on e2e tests
1 parent fc7f8b8 commit ea46cfc

14 files changed

+196
-72
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ A [Visual Studio Code](https://code.visualstudio.com/) extension that provides d
1616
```
1717
NI DataPlugins: Create new Python-DataPlugin
1818
```
19-
**Step 3.** Export the DataPlugin. Right click on the \*.py file you want to export -> Choose "NI DataPlugins: Export DataPlugin".
19+
**Step 3.** Export the DataPlugin. Right click on the \*.py file you want to export -> Choose "NI DataPlugins: Export DataPlugin". Create a file `.file-extensions` in the root directory of your project and list all file extensions that your DataPlugin should support. If no list is defined, you will be prompted to provide a list of file extensions on first export of your DataPlugin.
2020

2121
# Settings
2222

assets/.file-extensions

Whitespace-only changes.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@
8686
"lint": "tslint -p ./",
8787
"pretest": "npm run compile && npm run lint",
8888
"test": "node ./out/test/runTest.js",
89-
"test:e2e": "extest setup-and-run out/test/e2e/*.js -u -o ./src/test/e2e/settings.json",
89+
"test:e2e": "extest setup-and-run out/test/e2e/*.js -e ./src/test/e2e -u -o ./src/test/e2e/settings.json",
9090
"vscode:prepublish": "npm run compile",
9191
"watch": "tsc -watch -p ./"
9292
},

src/commands.ts

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,7 @@ import { Languages } from './plugin-languages.enum';
99

1010
export async function createDataPlugin(): Promise<DataPlugin | null> {
1111
const examples: string[] = vscu.loadExamples();
12-
const examplesNames: string[] = new Array();
13-
for (let i = 0; i < examples.length; i++) {
14-
examplesNames[i] = path.basename(examples[i]);
15-
}
12+
const examplesNames: string[] = examples.map((example) => { return path.basename(example); });
1613

1714
const scriptName: string | undefined = await vscu.showInputBox('DataPlugin name: ', 'Please enter your DataPlugin name');
1815
if (!scriptName) {
@@ -55,22 +52,39 @@ export async function createDataPlugin(): Promise<DataPlugin | null> {
5552
}
5653

5754
export async function exportPluginFromContextMenu(uri: vscode.Uri) {
58-
const extensions = await vscu.showInputBox('Please enter the file extensions your DataPlugin can handle in the right syntax: ', '*.tdm; *.xls ...');
5955
const scriptPath: string = uri.fsPath;
6056
const pluginName: string = path.basename(path.dirname(scriptPath));
6157

62-
let exportPath: string = config.exportPath || '';
63-
if (extensions) {
64-
if (!exportPath) {
65-
const options: vscode.SaveDialogOptions = {
66-
defaultUri: vscode.Uri.parse(`${config.dataPluginFolder}\\${pluginName}`),
67-
filters: { 'Uri': ['uri'] },
68-
};
69-
70-
const fileInfos = await vscode.window.showSaveDialog({ ...options });
71-
exportPath = fileInfos?.fsPath || '';
58+
let extensions: string | undefined;
59+
60+
try {
61+
extensions = await fileutils.readFileExtensionConfig(path.dirname(scriptPath));
62+
} catch (e) {
63+
extensions = undefined;
64+
}
65+
66+
if (!extensions) {
67+
extensions = await vscu.showInputBox('Please enter the file extensions your DataPlugin can handle in the right syntax: ', '*.tdm; *.xls ...');
68+
69+
if (!extensions) {
70+
return;
7271
}
7372

74-
await vscu.exportDataPlugin(scriptPath, extensions.toString(), `${exportPath}\\${pluginName}.uri`);
73+
// Store selected extensions so we don't have to ask again
74+
fileutils.storeFileExtensionConfig(path.dirname(scriptPath), extensions);
7575
}
76+
77+
let exportPath: string = config.exportPath || '';
78+
if (!exportPath) {
79+
const options: vscode.SaveDialogOptions = {
80+
defaultUri: vscode.Uri.file(`${config.dataPluginFolder}\\${pluginName}\\${pluginName}.uri`),
81+
filters: { 'Uri': ['uri'] },
82+
};
83+
84+
const fileInfos = await vscode.window.showSaveDialog({ ...options });
85+
exportPath = fileInfos?.fsPath || '';
86+
}
87+
88+
await vscu.exportDataPlugin(scriptPath, extensions.toString(), `${exportPath}`);
89+
7690
}

src/dataplugin.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import * as fs from 'fs-extra';
33
import * as path from 'path';
44
import * as config from './config';
55
import { FileExistsError } from './dataplugin-error';
6-
import { FileExtensions } from './file-extensions.enum';
76
import { Languages } from './plugin-languages.enum';
87

98
const dataPluginFolder = config.dataPluginFolder;
@@ -15,7 +14,6 @@ export class DataPlugin {
1514
private _scriptPath: string;
1615
private _language: Languages;
1716
private _baseTemplate: string;
18-
private _fileExtensions: FileExtensions[] = new Array();
1917

2018
get name(): string {
2119
return this._name;
@@ -53,14 +51,6 @@ export class DataPlugin {
5351
return this._baseTemplate;
5452
}
5553

56-
get fileExtensions(): FileExtensions[] {
57-
return this._fileExtensions;
58-
}
59-
60-
set fileExtensions(fileExtensionss: FileExtensions[]) {
61-
this._fileExtensions = fileExtensionss;
62-
}
63-
6454
constructor(name: string, baseTemplate: string, language: Languages) {
6555
this._name = name;
6656
this._baseTemplate = baseTemplate;
@@ -77,12 +67,14 @@ export class DataPlugin {
7767

7868
public async createMainPy(): Promise<void> {
7969
try {
70+
const extensionsFile = '.file-extensions';
8071
const launchFile = 'launch.json';
8172
const loadFile = 'diadem_load.py';
8273
const assetFolder: string = path.join(dirNamePath, 'assets');
8374
const exampleFolder: string = path.join(dirNamePath, 'examples', this.baseTemplate);
8475

8576
await fs.copy(exampleFolder, this.folderPath);
77+
await fs.copy(path.join(assetFolder, extensionsFile), path.join(this.folderPath, extensionsFile));
8678
await fs.copy(path.join(assetFolder, loadFile), path.join(this.folderPath, '.ni', loadFile));
8779
await fs.copy(path.join(assetFolder, launchFile), path.join(this.folderPath, '.vscode', launchFile));
8880

src/file-extensions.enum.ts

Lines changed: 0 additions & 27 deletions
This file was deleted.

src/file-utils.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,47 @@
1-
import * as path from 'path';
21
import * as fs from 'fs-extra';
2+
import * as path from 'path';
3+
import * as readline from 'readline';
34
import { UriTemplate } from './uri-template';
45

6+
async function readFirstLineOfFile(filePath: string): Promise<string> {
7+
const rl = readline.createInterface({
8+
input: fs.createReadStream(filePath)
9+
});
10+
11+
return new Promise((resolve) => {
12+
rl.on('line', (line) => {
13+
resolve(line);
14+
});
15+
16+
rl.on('close', () => {
17+
resolve('');
18+
});
19+
});
20+
}
21+
522
export function createFolderSync(folder: string) {
623
if (!fs.existsSync(folder)) {
724
fs.mkdirSync(folder);
825
}
926
}
1027

28+
export async function readFileExtensionConfig(workspaceDir: string): Promise<string> {
29+
const filePath: string = path.join(workspaceDir, '.file-extensions');
30+
if (fs.existsSync(filePath)) {
31+
const head = await readFirstLineOfFile(filePath);
32+
return Promise.resolve(head);
33+
}
34+
35+
return Promise.reject('file not found');
36+
}
37+
38+
export function storeFileExtensionConfig(workspaceDir: string, fileExtensions: string): void {
39+
const filePath: string = path.join(workspaceDir, '.file-extensions');
40+
if (fs.existsSync(filePath)) {
41+
fs.writeFile(filePath, fileExtensions);
42+
}
43+
}
44+
1145
export async function writeUriFile(scriptPath: string, fileExtensions: string, exportPath: string): Promise<void> {
1246
const dirName = path.basename(path.dirname(scriptPath));
1347
const uriTemplate = new UriTemplate(`${dirName}`, scriptPath, fileExtensions);

src/test/e2e/create-plugin.test.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as assert from 'assert';
22
import { Guid } from 'guid-typescript';
3-
import { InputBox, Workbench, TextEditor, WebDriver, VSBrowser, EditorView, SideBarView } from 'vscode-extension-tester';
3+
import { InputBox, Workbench, WebDriver, VSBrowser, SideBarView } from 'vscode-extension-tester';
44

55
/*
66
API Reference: https://github.com/redhat-developer/vscode-extension-tester/wiki/Page-Object-APIs
@@ -15,22 +15,23 @@ describe('Basic UI Tests', () => {
1515

1616
it('Should create a new DataPlugin project', async () => {
1717
const randomName: string = Guid.create().toString();
18+
await new Promise(res => setTimeout(res, 5000)); // wait for loading vscode completely
1819
const workbench = new Workbench();
1920
await workbench.executeCommand('NI DataPlugins: Create new Python-DataPlugin');
2021
await new Promise(res => setTimeout(res, 500));
2122

2223
// Input box requesting a DataPlugin name?
2324
const enterDataPluginNameInputBox = await driver.wait(() => { return new InputBox(); }, 1000);
2425
const placeholderText1 = await enterDataPluginNameInputBox.getPlaceHolder();
25-
assert.equal('Please enter your DataPlugin name', placeholderText1);
26+
assert.strictEqual('Please enter your DataPlugin name', placeholderText1);
2627
await enterDataPluginNameInputBox.setText(randomName);
2728
await enterDataPluginNameInputBox.confirm();
2829
await new Promise(res => setTimeout(res, 500));
2930

3031
// Input box requesting a template?
3132
const chooseTemplateDropDown = await driver.wait(() => { return new InputBox(); }, 1000);
3233
const placeholderText2 = await chooseTemplateDropDown.getPlaceHolder();
33-
assert.equal('Please choose a template to start with', placeholderText2);
34+
assert.strictEqual('Please choose a template to start with', placeholderText2);
3435
await new Promise(res => setTimeout(res, 500));
3536
await chooseTemplateDropDown.setText('hello-world');
3637
await chooseTemplateDropDown.confirm();
@@ -41,6 +42,6 @@ describe('Basic UI Tests', () => {
4142
const sideBarContent = await sideBarView.getContent().wait();
4243
const section = await sideBarContent.getSection('Untitled (Workspace)');
4344
const item = await section.findItem(randomName);
44-
assert.notEqual(undefined, item);
45+
assert.notStrictEqual(undefined, item);
4546
}).timeout(40000);
4647
});

src/test/e2e/export-plugin.test.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import * as assert from 'assert';
2+
import * as fs from 'fs-extra';
3+
import * as path from 'path';
4+
import { ActivityBar, EditorView, InputBox, SideBarView, Workbench, WebDriver, VSBrowser } from 'vscode-extension-tester';
5+
6+
/*
7+
API Reference: https://github.com/redhat-developer/vscode-extension-tester/wiki/Page-Object-APIs
8+
*/
9+
10+
describe('Basic UI Tests', () => {
11+
let browser: VSBrowser;
12+
let driver: WebDriver;
13+
14+
before(() => {
15+
browser = VSBrowser.instance;
16+
driver = VSBrowser.instance.driver;
17+
});
18+
19+
it('Should export a DataPlugin from a sample workspace', async () => {
20+
await new Promise(res => setTimeout(res, 5000)); // wait for loading vscode completely
21+
22+
// Prepare Workspace
23+
const workspaceDir: string = path.join(__dirname, 'sample_workspace_1');
24+
// tslint:disable-next-line
25+
fs.existsSync(workspaceDir) && fs.rmdirSync(workspaceDir, { recursive: true });
26+
fs.mkdirSync(workspaceDir);
27+
fs.writeFileSync(path.join(workspaceDir, 'dataplugin.py'), 'class Plugin:');
28+
29+
// init
30+
const workbench = new Workbench();
31+
32+
// Open Workspace
33+
await workbench.executeCommand('Extest: Open Folder');
34+
await new Promise(res => setTimeout(res, 500));
35+
36+
// Enter Workspace path
37+
const enterFolderName = await driver.wait(() => { return new InputBox(); }, 1000);
38+
await enterFolderName.setText(workspaceDir);
39+
await enterFolderName.confirm();
40+
await new Promise(res => setTimeout(res, 2000));
41+
42+
// Open Explorer side bar
43+
const sideBarView = new SideBarView();
44+
const explorer = await new ActivityBar().getViewControl('Explorer');
45+
await explorer.openView();
46+
47+
// Find python file
48+
const content = await sideBarView.getContent();
49+
const section = await content.getSection('Untitled (Workspace)');
50+
await section.openItem('sample_workspace_1');
51+
const item = await section.findItem('dataplugin.py');
52+
assert.notStrictEqual(undefined, item);
53+
await new Promise(res => setTimeout(res, 2000));
54+
55+
// Ensure the remaining workflow is not disrupted by popping editor tabs from other extensions
56+
const editorView = new EditorView();
57+
await editorView.closeAllEditors();
58+
await new Promise(res => setTimeout(res, 2000));
59+
60+
// Delete file extensions settings
61+
const fileExtension = path.join(workspaceDir, '.file-extensions');
62+
// tslint:disable-next-line
63+
fs.existsSync(fileExtension) && fs.unlinkSync(fileExtension);
64+
65+
// Open ContextMenu and click Export
66+
const menu = await item?.openContextMenu();
67+
const menuItem = await menu?.getItem('NI DataPlugins: Export DataPlugin');
68+
assert.notStrictEqual(undefined, menuItem);
69+
await menuItem?.select();
70+
71+
// Choose File Extension
72+
const fileExtensionInputBox = await driver.wait(() => { return new InputBox(); }, 1000);
73+
const placeholderText = await fileExtensionInputBox.getPlaceHolder();
74+
assert.strictEqual('*.tdm; *.xls ...', placeholderText);
75+
await fileExtensionInputBox.setText('*.csv');
76+
await fileExtensionInputBox.cancel();
77+
78+
// Write .file-extensions file
79+
fs.writeFileSync(fileExtension, '*.csv');
80+
81+
// Click Export again, this time no file extension prompt should show up
82+
const menu2 = await item?.openContextMenu();
83+
const menuItem2 = await menu2?.getItem('NI DataPlugins: Export DataPlugin');
84+
await menuItem2?.select();
85+
const checkForInputBoxAgain = await driver.wait(() => { return new InputBox(); }, 1000);
86+
const isDisplayed = await checkForInputBoxAgain.isDisplayed();
87+
assert.strictEqual(false, isDisplayed);
88+
89+
}).timeout(40000);
90+
});

src/test/e2e/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
22
"typescript.updateImportsOnFileMove.enabled": "always",
3-
"workbench.editor.enablePreview": true
3+
"workbench.editor.enablePreview": true,
4+
"update.mode": "none"
45
}

src/test/suite/dataplugin.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ suite('DataPlugin Test Suite', () => {
5757
// tslint:disable-next-line: no-unused-expression
5858
new DataPlugin(randomName, 'hello-world', Languages.Python);
5959
} catch (e) {
60-
assert.equal(e.errorType, ErrorType.FILEEXISTS);
60+
assert.strictEqual(e.errorType, ErrorType.FILEEXISTS);
6161
}
6262
});
6363
});

src/test/suite/file-utils.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import * as assert from 'assert';
2+
import * as fs from 'fs-extra';
3+
import * as path from 'path';
4+
import * as vscode from 'vscode';
5+
import * as fileutils from '../../file-utils';
6+
7+
suite('File-Utils Test Suite', () => {
8+
vscode.window.showInformationMessage('Start File-Utils tests.');
9+
10+
test('should return correct values when reading file extension config', async () => {
11+
let fileExtensions: string;
12+
const filePath: string = path.join(__dirname, '.file-extensions');
13+
14+
// tslint:disable-next-line
15+
fs.existsSync(filePath) && fs.unlinkSync(filePath);
16+
17+
await assert.rejects(fileutils.readFileExtensionConfig(__dirname), /file not found/);
18+
19+
await fs.writeFile(filePath, '*.csv,*.txt');
20+
fileExtensions = await fileutils.readFileExtensionConfig(__dirname);
21+
assert.ok(fileExtensions === '*.csv,*.txt');
22+
23+
await fs.writeFile(filePath, '');
24+
fileExtensions = await fileutils.readFileExtensionConfig(__dirname);
25+
assert.ok(fileExtensions === '');
26+
}).timeout(10000);
27+
});

src/uri-template.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export class UriTemplate {
1212
`<easypluginparam><![CDATA[<dllpath>@USIBINDIR@\\PythonMarshaller\\uspPythonMarshaller.dll</dllpath><script>${pythonScriptPath}</script>]]></easypluginparam>` +
1313
'<querysupported>0</querysupported>' +
1414
'<fastloadsupported>0</fastloadsupported>' +
15-
`<filefilters extension="${fileExtensions}"><description>${fileName} Dateien (${fileExtensions})</description></filefilters>` +
15+
`<filefilters extension="${fileExtensions}"><description>${fileName} Files (${fileExtensions})</description></filefilters>` +
1616
'<platform>x64</platform></storetype></usireginfo>';
1717
}
1818

0 commit comments

Comments
 (0)