From da9c3d51fc5a2a838e06c4cde0a5bc009c2497e8 Mon Sep 17 00:00:00 2001 From: Sarah Wang <55766946+sarahsonder@users.noreply.github.com> Date: Tue, 18 Jun 2024 20:36:40 -0400 Subject: [PATCH] Add CLI for MemoryViz (#43) --- .github/workflows/ci.yml | 12 +- CHANGELOG.md | 4 + README.md | 10 ++ docs/docs/06-cli.md | 21 ++++ memory-viz/README.md | 10 ++ memory-viz/bin/cli.js | 55 +++++++++ memory-viz/package.json | 3 + .../src/tests/__snapshots__/cli.spec.tsx.snap | 3 + memory-viz/src/tests/cli.spec.tsx | 112 ++++++++++++++++++ package-lock.json | 15 ++- package.json | 3 +- 11 files changed, 241 insertions(+), 7 deletions(-) create mode 100644 docs/docs/06-cli.md create mode 100644 memory-viz/bin/cli.js create mode 100644 memory-viz/src/tests/__snapshots__/cli.spec.tsx.snap create mode 100644 memory-viz/src/tests/cli.spec.tsx diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 94611cb0..0ae6b4b4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,11 +21,11 @@ jobs: run: npm ci - name: Run prettier run: npx prettier . --write - - name: Commit changes - uses: stefanzweifel/git-auto-commit-action@v4 - with: - commit_message: Apply formatting changes - branch: ${{ github.head_ref }} + # - name: Commit changes + # uses: stefanzweifel/git-auto-commit-action@v4 + # with: + # commit_message: Apply formatting changes + # branch: ${{ github.head_ref }} jest: if: github.event.pull_request.draft == false runs-on: ubuntu-22.04 @@ -39,6 +39,8 @@ jobs: cache: npm - name: Install npm packages run: npm ci + - name: Compile JavaScript assets + run: npm run build --workspace=memory-viz - name: Run jest tests run: npm run test-cov - name: Coveralls (jest) diff --git a/CHANGELOG.md b/CHANGELOG.md index 207543e8..bcb10225 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### ✨ Enhancements +- Created a CLI for MemoryViz. + ### 🐛 Bug fixes - Fixed a bug where box fill colours would cover box text, and changed the implementation of `hide` style option. @@ -24,6 +26,8 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### 📚 Documentation and demo website changes +- Added documentation page for the MemoryViz CLI. + ### 🔧 Internal changes - Added a changelog and pull request template. diff --git a/README.md b/README.md index 48859ac2..86524587 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,16 @@ produces a file `simple_demo.svg` that looks like the following: For more information, check out the project [documentation website](https://www.cs.toronto.edu/~david/memory-viz/) and [demo](https://www.cs.toronto.edu/~david/memory-viz/demo/). +### MemoryViz CLI + +To run MemoryViz from your terminal, run: + +```console +$ npx memory-viz +``` + +replacing `` with the path to a file containing MemoryViz-compatible JSON. The output is an SVG image generated by MemoryViz and the image is saved in the current working directory. For more information, check out the project [documentation website](https://www.cs.toronto.edu/~david/memory-viz/docs/cli). + ## Developers ### Installation diff --git a/docs/docs/06-cli.md b/docs/docs/06-cli.md new file mode 100644 index 00000000..294ea486 --- /dev/null +++ b/docs/docs/06-cli.md @@ -0,0 +1,21 @@ +--- +title: MemoryViz CLI +--- + +# MemoryViz CLI + +You can run MemoryViz straight from your terminal! + +## Input + +To run the MemoryViz CLI, run: + +```console +$ npx memory-viz +``` + +replacing `` with the path to a file containing MemoryViz-compatible JSON. If the file content is not compatible with MemoryViz, an error will be thrown. + +## Output + +The output is an SVG image generated by MemoryViz and the image is saved in the current working directory. The name of the SVG will be the same as that of the inputted file (i.e., if the inputted file is `david-is-cool.json`, the output will be `david-is-cool.svg`). diff --git a/memory-viz/README.md b/memory-viz/README.md index f5d652c3..bac52335 100644 --- a/memory-viz/README.md +++ b/memory-viz/README.md @@ -52,6 +52,16 @@ produces a file `simple_demo.svg` that looks like the following: For more information, check out the project [documentation website](https://www.cs.toronto.edu/~david/memory-viz/) and [demo](https://www.cs.toronto.edu/~david/memory-viz/demo/). +### MemoryViz CLI + +To run MemoryViz from your terminal, run: + +```console +$ npx memory-viz +``` + +replacing `` with the path to a file containing MemoryViz-compatible JSON. The output is an SVG image generated by MemoryViz and the image is saved in the current working directory. For more information, check out the project [documentation website](https://www.cs.toronto.edu/~david/memory-viz/docs/cli). + ## Developers ### Installation diff --git a/memory-viz/bin/cli.js b/memory-viz/bin/cli.js new file mode 100644 index 00000000..257faed6 --- /dev/null +++ b/memory-viz/bin/cli.js @@ -0,0 +1,55 @@ +#!/usr/bin/env node + +const fs = require("fs"); +const path = require("path"); +const { draw } = require("memory-viz"); + +// Check for correct number of arguments +if (process.argv.length !== 3) { + console.error( + "Error: wrong number of arguments.\nUsage: memory-viz " + ); + process.exit(1); +} +const filePath = process.argv[2]; +const absolutePath = path.resolve(process.cwd(), filePath); + +// Checks if absolutePath exists and that it is a JSON file +let fileContent; +if (!fs.existsSync(absolutePath)) { + console.error(`Error: File ${absolutePath} does not exist.`); + process.exit(1); +} else { + fileContent = fs.readFileSync(absolutePath, "utf8"); +} + +let data; +try { + data = JSON.parse(fileContent); +} catch (err) { + console.error(`Error: Invalid JSON\n${err.message}.`); + process.exit(1); +} + +let m; +try { + // TODO: Replace width and seed with command-line arguments + m = draw(data, true, { + width: 1300, + roughjs_config: { options: { seed: 12345 } }, + }); +} catch (err) { + console.error( + `This is valid JSON but not valid Memory Models JSON.` + + `Please refer to the repo for more details.` + ); + process.exit(1); +} + +const outputName = path.parse(filePath).name + ".svg"; +try { + m.save(outputName); +} catch (err) { + console.error(`Error: ${err.message}`); + process.exit(1); +} diff --git a/memory-viz/package.json b/memory-viz/package.json index f053e23d..3079c1c7 100644 --- a/memory-viz/package.json +++ b/memory-viz/package.json @@ -37,6 +37,9 @@ "bugs": { "url": "https://github.com/david-yz-liu/memory-viz/issues" }, + "bin": { + "memory-viz": "bin/cli.js" + }, "dependencies": { "@xmldom/xmldom": "^0.8.6", "deepmerge": "^4.3.1", diff --git a/memory-viz/src/tests/__snapshots__/cli.spec.tsx.snap b/memory-viz/src/tests/__snapshots__/cli.spec.tsx.snap new file mode 100644 index 00000000..850f7f79 --- /dev/null +++ b/memory-viz/src/tests/__snapshots__/cli.spec.tsx.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`memory-viz cli should produce an svg that matches snapshot 1`] = `""David is cool!"id19str"`; diff --git a/memory-viz/src/tests/cli.spec.tsx b/memory-viz/src/tests/cli.spec.tsx new file mode 100644 index 00000000..cbd6570b --- /dev/null +++ b/memory-viz/src/tests/cli.spec.tsx @@ -0,0 +1,112 @@ +const { exec } = require("child_process"); +const path = require("path"); +const fs = require("fs"); +const tmp = require("tmp"); + +tmp.setGracefulCleanup(); + +describe("memory-viz cli", () => { + it("should produce an svg that matches snapshot", (done) => { + const tmpFile = tmp.fileSync({ postfix: ".json" }); + const filePath = tmpFile.name; + const input = JSON.stringify( + [ + { + type: "str", + id: 19, + value: "David is cool!", + style: ["highlight"], + }, + ], + null + ); + + fs.writeFileSync(filePath, input); + + exec(`memory-viz ${filePath}`, (err) => { + if (err) throw err; + const svgFilePath = path.resolve( + process.cwd(), + path.basename(filePath.replace(".json", ".svg")) + ); + const fileContent = fs.readFileSync(svgFilePath, "utf8"); + expect(fileContent).toMatchSnapshot(); + fs.unlinkSync(svgFilePath); + done(); + }); + }); +}); + +describe.each([ + { + errorType: "invalid arguments", + command: "memory-viz", + expectedErrorMessage: + `Command failed: memory-viz\n` + + `Error: wrong number of arguments.\n` + + `Usage: memory-viz \n`, + }, + { + errorType: "non-existent file", + command: "memory-viz cli-test.json", + expectedErrorMessage: + `Command failed: memory-viz cli-test.json\n` + + `Error: File ${path.resolve( + process.cwd(), + "cli-test.json" + )} does not exist.\n`, + }, + { + errorType: "invalid json", + expectedErrorMessage: "Error: Invalid JSON", + }, + { + errorType: "invalid memory-viz json", + expectedErrorMessage: + "This is valid JSON but not valid Memory Models JSON." + + "Please refer to the repo for more details.", + }, +])( + "these incorrect inputs to the memory-viz cli", + ({ errorType, command = undefined, expectedErrorMessage }) => { + it(`should display ${errorType} error`, (done) => { + const fileMockingTests = [ + "invalid file type", + "invalid json", + "invalid memory-viz json", + ]; + + let filePath: string; + if (fileMockingTests.includes(errorType)) { + let err_postfix: string; + if (errorType === "invalid file type") { + err_postfix = ".js"; + } else { + err_postfix = ".json"; + } + const tmpFile = tmp.fileSync({ postfix: err_postfix }); + filePath = tmpFile.name; + + if (errorType === "invalid json") { + fs.writeFileSync(filePath, "[1, 2, 3, 4,]"); + } else { + fs.writeFileSync( + filePath, + '[{ "name": "int", "id": 13, "value": 7 }]' + ); + } + } + + if (fileMockingTests.includes(errorType)) { + command = `memory-viz ${filePath}`; + } + exec(command, (err) => { + if (err) { + expect(err.code).toBe(1); + expect(err.message).toContain(expectedErrorMessage); + } + done(); + }); + }); + } +); diff --git a/package-lock.json b/package-lock.json index 5a9fc5c0..864cb09b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,8 @@ "devDependencies": { "husky": "^8.0.3", "lint-staged": "^14.0.1", - "prettier": "2.8.1" + "prettier": "2.8.1", + "tmp": "^0.2.3" } }, "demo": { @@ -83,6 +84,9 @@ "deepmerge": "^4.3.1", "roughjs": "^4.5.0" }, + "bin": { + "memory-viz": "bin/cli.js" + }, "devDependencies": { "@babel/core": "^7.20.7", "@babel/preset-env": "^7.20.2", @@ -19622,6 +19626,15 @@ "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" }, + "node_modules/tmp": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "dev": true, + "engines": { + "node": ">=14.14" + } + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", diff --git a/package.json b/package.json index 7f7094d8..deb39e9c 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "devDependencies": { "husky": "^8.0.3", "lint-staged": "^14.0.1", - "prettier": "2.8.1" + "prettier": "2.8.1", + "tmp": "^0.2.3" }, "lint-staged": { "**/*": "prettier --write --ignore-unknown"