Skip to content

Commit

Permalink
Javascript support | Fix #15
Browse files Browse the repository at this point in the history
  • Loading branch information
atsyplenkov committed Nov 2, 2024
1 parent 3d97ed1 commit a7084d4
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 3 deletions.
26 changes: 25 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<!-- badges: end -->
# [Pastum: paste as ... dataframe](https://pastum.anatolii.nz)

`pastum` allows you to quickly transform any text/HTML table from your clipboard into a dataframe object in your favorite language — R, Python, or Julia. Almost all popular frameworks are supported; if something is missing, don't hesitate to raise an [issue](https://github.com/atsyplenkov/pastum/issues).
`pastum` allows you to quickly transform any text/HTML table from your clipboard into a dataframe object in your favorite language — R, Python, Julia or JavaScript. Almost all popular frameworks are supported; if something is missing, don't hesitate to raise an [issue](https://github.com/atsyplenkov/pastum/issues).

# Example usage

Expand All @@ -19,6 +19,30 @@ Or you can specify the `pastum.defaultDataframeR`/`pastum.defaultDataframePython

![](https://github.com/atsyplenkov/pastum/raw/master/assets/demo-r-tibble.gif)

# Test it by yourself

In the table below, the most unfortunate and complex situation is presented. It is a mixture of empty cells, strings, integer and float values. Select, copy and try to paste it into the IDE. The `{pastum}` will recognize all types correctly and fill empty cells with corresponding `NA`/`missing`/`None`/`null` values.

| Integer ID | Strings with missing values | Only float | Int and Float |
|------------|-----------------------------|------------|---------------|
| 1 | Javascript | 1.43 | 1 |
| 2 | Rust | 123,456.78 | 2 |
| 3 | | -45 | 3 |
| 4 | Clojure | 123456.78 | 4 |
| | Basic | -45.65 | 5.5 |

```r
# paste it as a tribble object in R
tibble::tribble(
~IntegerID, ~StringsWithMissingValues, ~OnlyFloat, ~IntAndFloat,
1L, "Javascript", 1.43, 1.0,
2L, "Rust", 123456.78, 2.0,
3L, NA, -45.0, 3.0,
4L, "Clojure", 123456.78, 4.0,
5L, "Basic", -45.65, 5.5
)
```

# Installation

The extension is published on both the [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=atsyplenkov.pastum) and the [Open VSX Registry](https://open-vsx.org/extension/atsyplenkov/pastum): just click `Install` there or manually install it with:
Expand Down
5 changes: 5 additions & 0 deletions extension.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const vscode = require("vscode");
const r = require("./src/paste-r.js");
const py = require("./src/paste-python.js");
const jl = require("./src/paste-julia.js");
const js = require("./src/paste-js.js");
const def = require("./src/paste-default.js");

function activate(context) {
Expand All @@ -18,6 +19,10 @@ function activate(context) {
"pastum.Jldataframe",
jl.clipboardToJuliaDataFrame
),
vscode.commands.registerCommand(
"pastum.JSdataframe",
js.clipboardToJSDataFrame
),
vscode.commands.registerCommand("pastum.Defaultdataframe", def.pasteDefault)
);
}
Expand Down
24 changes: 22 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@
"name": "pastum",
"displayName": "Pastum",
"description": "Convert table from clipboard to R, Python or Julia dataframe",
"version": "0.2.0",
"version": "0.2.1",
"publisher": "atsyplenkov",
"license": "MIT",
"pricing": "Free",
"sponsor": {
"url": "https://github.com/sponsors/atsyplenkov"
},
"icon": "assets/logo.png",
"repository": {
"type": "git",
Expand Down Expand Up @@ -48,6 +52,11 @@
"title": "Table ➔ Julia Dataframe",
"category": "Pastum"
},
{
"command": "pastum.JSdataframe",
"title": "Table ➔ JavaScript Dataframe",
"category": "Pastum"
},
{
"command": "pastum.Defaultdataframe",
"title": "Pastum: paste as default dataframe"
Expand All @@ -56,7 +65,7 @@
"menus": {
"editor/context": [
{
"when": "config.pastum.showContextMenu && (editorLangId == 'r' || editorLangId == 'python' || editorLangId == 'julia')",
"when": "config.pastum.showContextMenu && (editorLangId == 'r' || editorLangId == 'python' || editorLangId == 'julia'|| editorLangId == 'javascript')",
"command": "pastum.Defaultdataframe",
"group": "navigation@1"
}
Expand Down Expand Up @@ -112,6 +121,17 @@
],
"default": "pandas 🐼",
"markdownDescription": "Select the default framework for Python dataframes to be pasted using the `pastum.Defaultdataframe` command."
},
"pastum.defaultDataframeJavascript": {
"type": "string",
"enum": [
"base",
"polars 🐻",
"arquero 🏹",
"danfo 🐝"
],
"default": "polars 🐻",
"markdownDescription": "Select the default framework for JavaScript dataframes to be pasted using the `pastum.Defaultdataframe` command."
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions src/paste-default.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ const vscode = require("vscode");
const r = require("./paste-r.js");
const py = require("./paste-python.js");
const jl = require("./paste-julia.js");
const js = require("./paste-js.js");

function pasteDefault() {
// Get the default dataframe framework
const config = vscode.workspace.getConfiguration("pastum");
const frameR = config.get("defaultDataframeR");
const framePy = config.get("defaultDataframePython");
const frameJS = config.get("defaultDataframeJavascript");

// Get the active editor language
const editor = vscode.window.activeTextEditor;
Expand All @@ -27,6 +29,9 @@ function pasteDefault() {
case "julia":
jl.clipboardToJuliaDataFrame();
break;
case "javascript":
js.clipboardToJSDataFrame(frameJS);
break;
default:
vscode.window.showErrorMessage("No default framework selected");
}
Expand Down
139 changes: 139 additions & 0 deletions src/paste-js.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
const vscode = require("vscode");
const { parseClipboard } = require("./parse-table");
const { addTrailingZeroes } = require("./utils");

async function clipboardToJSDataFrame(framework = null) {
try {
// 1: Read the clipboard content
const clipboardContent = await vscode.env.clipboard.readText();

if (!clipboardContent) {
vscode.window.showErrorMessage(
"Clipboard is empty or contains unsupported content."
);
return;
}

// 2: Try to extract the table from clipboard content
let formattedData = null;
formattedData = parseClipboard(clipboardContent);

// 3: Ask the user which framework they want to use
if (framework === null) {
framework = await vscode.window.showQuickPick(
["base", "polars 🐻", "arquero 🏹", "danfo 🐝"],
{
placeHolder:
"Select the JavaScript framework for creating the dataframe",
}
);
}
framework = framework.split(" ")[0];

if (!framework) {
vscode.window.showErrorMessage("No framework selected.");
return;
}

// 4: Generate the JS code using the selected framework
const jsCode = createJSDataFrame(formattedData, framework);

if (!jsCode) {
vscode.window.showErrorMessage("Failed to generate JavaScript code.");
return;
}

// 5: Insert the generated code into the active editor
const editor = vscode.window.activeTextEditor;
if (editor) {
editor.edit((editBuilder) => {
editBuilder.insert(editor.selection.active, jsCode);
});
}
} catch (error) {
vscode.window.showErrorMessage(`Error: ${error.message}`);
}
}

/**
* Generates JS dataframe objects.
* Supports base, polars frameworks.
*
*/
function createJSDataFrame(tableData, framework) {
const { headers, data, columnTypes } = tableData;
let code = "";

/**
* Formats a value according to its column type for R syntax
* @param {any} value - The value to format
* @param {number} colIndex - Column index for type lookup
* @returns {string} Formatted value
*/
function formatValue(value, colIndex) {
if (value === "") {
return "null";
} else if (columnTypes[colIndex] === "string") {
return `"${value}"`;
} else if (columnTypes[colIndex] === "numeric") {
return addTrailingZeroes(value);
} else if (columnTypes[colIndex] === "integer") {
return value;
// FIXME:
// add BigInt?
// return `BigInt(${value})`;
} else {
return `"${value}"`;
}
}

// base
if (framework === "base") {
code = `const df = {\n`;
headers.forEach((header, i) => {
const values = data.map((row) => formatValue(row[i], i)).join(", ");
code += ` "${header}": [${values}]${
i < headers.length - 1 ? ",\n" : "\n"
}`;
});
code += `};`;
} else if (framework === "polars") {
code = `import pl from "nodejs-polars";\n\n`;
code += `const df = pl.DataFrame({\n`;
headers.forEach((header, i) => {
const values = data.map((row) => formatValue(row[i], i)).join(", ");
code += ` "${header}": [${values}]${
i < headers.length - 1 ? ",\n" : "\n"
}`;
});
code += `});`;
} else if (framework === "arquero") {
code = `import {table} from "arquero";\n\n`;
code += `const df = table({\n`;
headers.forEach((header, i) => {
const values = data.map((row) => formatValue(row[i], i)).join(", ");
code += ` "${header}": [${values}]${
i < headers.length - 1 ? ",\n" : "\n"
}`;
});
code += `});`;
} else if (framework === "danfo") {
code = `import * as dfd from "danfojs-node";\n\n`;
code += `obj_data = {\n`;
headers.forEach((header, i) => {
const values = data.map((row) => formatValue(row[i], i)).join(", ");
code += ` "${header}": [${values}]${
i < headers.length - 1 ? ",\n" : "\n"
}`;
});
code += `};\n\n`;
code += `df = new dfd.DataFrame(obj_data);\n\n`;
code += `df.print();`;
}

return code;
}

module.exports = {
clipboardToJSDataFrame,
};

0 comments on commit a7084d4

Please sign in to comment.