From d4651910e11aa175efd4f9ccb4813b988ff1c87f Mon Sep 17 00:00:00 2001 From: TheKnarf <213777+TheKnarf@users.noreply.github.com> Date: Sat, 12 Jul 2025 22:14:50 +0200 Subject: [PATCH] feat(cli): add margin option --- bin/modular-svg | 24 +++++++++++++++++++++--- bin/modular-svg.spec.ts | 24 ++++++++++++++++++++++-- src/output.ts | 10 +++++++--- 3 files changed, 50 insertions(+), 8 deletions(-) diff --git a/bin/modular-svg b/bin/modular-svg index 2c67736..6ac43f0 100755 --- a/bin/modular-svg +++ b/bin/modular-svg @@ -24,7 +24,25 @@ async function readInput(arg) { } try { - const inputArg = process.argv[2]; + let margin = 3; + const args = process.argv.slice(2); + const files = []; + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + if (arg === "-m" || arg === "--margin") { + const val = args[i + 1]; + margin = val ? Number(val) : margin; + i++; + continue; + } + const match = /^--margin=(.*)$/.exec(arg); + if (match) { + margin = Number(match[1]); + continue; + } + files.push(arg); + } + const inputArg = files[0]; const raw = await readInput(inputArg); const data = JSON.parse(raw); try { @@ -35,8 +53,8 @@ try { } const scene = buildSceneFromJson(data); const layout = solveLayout(scene); - const svg = layoutToSvg(layout, scene.nodes); - const out = process.argv[3]; + const svg = layoutToSvg(layout, scene.nodes, margin); + const out = files[1]; if (out && out !== "-") { writeFileSync(out, svg); } else { diff --git a/bin/modular-svg.spec.ts b/bin/modular-svg.spec.ts index b532b11..9e379fe 100644 --- a/bin/modular-svg.spec.ts +++ b/bin/modular-svg.spec.ts @@ -2,12 +2,13 @@ import { spawnSync } from "node:child_process"; import { readFileSync, unlinkSync } from "node:fs"; import { join } from "node:path"; import { describe, expect, it } from "vitest"; +import { buildSceneFromJson, layoutToSvg, solveLayout } from "../src/index"; const bin = join(__dirname, "modular-svg"); const example = join(__dirname, "../examples/stack.json"); describe("CLI", () => { - it("runs with stdin", () => { + it("uses default margin", () => { const json = readFileSync(example, "utf8"); const out = join(__dirname, "tmp.svg"); const { status } = spawnSync(bin, ["-", out], { @@ -16,7 +17,26 @@ describe("CLI", () => { }); expect(status).toBe(0); const svg = readFileSync(out, "utf8"); - expect(svg.startsWith(" { + const json = readFileSync(example, "utf8"); + const out = join(__dirname, "tmp.svg"); + const { status } = spawnSync(bin, ["-", out, "--margin", "10"], { + input: json, + encoding: "utf8", + }); + expect(status).toBe(0); + const svg = readFileSync(out, "utf8"); + const scene = buildSceneFromJson(JSON.parse(json)); + const layout = solveLayout(scene); + const expected = layoutToSvg(layout, scene.nodes, 10); + expect(svg.trim()).toBe(expected); unlinkSync(out); }); }); diff --git a/src/output.ts b/src/output.ts index 50d45ec..f934034 100644 --- a/src/output.ts +++ b/src/output.ts @@ -218,13 +218,17 @@ function arrow( export function layoutToSvg( layout: LayoutResult, nodes?: NodeRecord[], + margin = 0, ): string { const byId = new Map(); if (nodes) for (const n of nodes) byId.set(n.id, n); const bounds = layoutBounds(layout, nodes); const min = bounds.start; const max = bounds.end; - const offset = vec(min.x < 0 ? -min.x : 0, min.y < 0 ? -min.y : 0); + const offset = vec( + (min.x < 0 ? -min.x : 0) + margin, + (min.y < 0 ? -min.y : 0) + margin, + ); const body = Object.entries(layout).map(([id, box]) => { const n = byId.get(id); if (!n?.type) return ""; @@ -233,8 +237,8 @@ export function layoutToSvg( if (n.type === "arrow") return arrow(id, n, layout, offset) ?? ""; return rect(id, box, n, offset); }); - const width = max.x - min.x; - const height = max.y - min.y; + const width = max.x - min.x + margin * 2; + const height = max.y - min.y + margin * 2; return xml( "svg", { xmlns: "http://www.w3.org/2000/svg", width, height },