diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 5c0ebb2..f6a0a01 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -27,7 +27,7 @@ jobs:
run_install: false
- name: 🔨 Build
- run: "pnpm install && pnpm run build:executable"
+ run: "pnpm install && pnpm run bundle:executable"
- name: 🛒 Publish
run: pnpm publish --no-git-checks
diff --git a/.gitignore b/.gitignore
index f06235c..dd87e2d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,2 @@
node_modules
-dist
+build
diff --git a/package.json b/package.json
index 038e610..5721f1a 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,7 @@
"keywords": [
"saucer"
],
- "version": "4.1.1",
+ "version": "4.2.0",
"license": "MIT",
"author": "Curve (https://github.com/Curve)",
"type": "module",
@@ -17,13 +17,15 @@
},
"scripts": {
"build": "tsc",
- "build:executable": "pnpm run build && chmod +x dist/index.js && cp -r src/templates dist"
+ "copy": "cp -r src/templates build",
+ "bundle": "pnpm run build && pnpm run copy",
+ "bundle:executable": "pnpm run bundle && chmod +x build/index.js"
},
"bin": {
- "saucer": "dist/index.js"
+ "saucer": "build/index.js"
},
"files": [
- "dist"
+ "build"
],
"dependencies": {
"commander": "^11.0.0",
@@ -41,6 +43,7 @@
"devDependencies": {
"@sindresorhus/tsconfig": "^3.0.1",
"@types/fs-extra": "^11.0.2",
+ "@types/ink-spinner": "^3.0.4",
"@types/mime-types": "^2.1.1",
"@types/node": "^20.6.1",
"@types/react": "^18.0.32",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7d756db..d881af3 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -46,6 +46,9 @@ devDependencies:
'@types/fs-extra':
specifier: ^11.0.2
version: 11.0.3
+ '@types/ink-spinner':
+ specifier: ^3.0.4
+ version: 3.0.4
'@types/mime-types':
specifier: ^2.1.1
version: 2.1.3
@@ -158,6 +161,10 @@ packages:
engines: {node: '>=14'}
dev: true
+ /@types/cli-spinners@1.3.3:
+ resolution: {integrity: sha512-B5/ya7/tb6zH2sFza0WYMuuIP3klS94+bkGAJ4ISXstmIaesoG1PZ3glNllmPUx94Oh4kguzgoZucEBqBUds6w==}
+ dev: true
+
/@types/eslint-utils@3.0.5:
resolution: {integrity: sha512-dGOLJqHXpjomkPgZiC7vnVSJtFIOM1Y6L01EyUhzPuD0y0wfIGiqxiGs3buUBfzxLIQHrCvZsIMDaCZ8R5IIoA==}
dependencies:
@@ -183,6 +190,13 @@ packages:
'@types/node': 20.8.7
dev: true
+ /@types/ink-spinner@3.0.4:
+ resolution: {integrity: sha512-R9RFxdeYGCSN87zi79E8ZorHJdsSzrAnJ6Rn3wYATvTLYIEwejTQ7OLvgSPsstI2/+DoXXbfTUWlp3HRo2tZTw==}
+ dependencies:
+ '@types/cli-spinners': 1.3.3
+ '@types/react': 18.2.31
+ dev: true
+
/@types/json-schema@7.0.14:
resolution: {integrity: sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==}
diff --git a/src/commands/embed.tsx b/src/commands/embed.tsx
new file mode 100644
index 0000000..3d54356
--- /dev/null
+++ b/src/commands/embed.tsx
@@ -0,0 +1,145 @@
+import { Eta } from "eta";
+import figureSet from "figures";
+import { existsSync, lstatSync, statSync } from "fs";
+import { outputFileSync } from "fs-extra/esm";
+import { mkdir } from "fs/promises";
+import { Newline, Text, render } from "ink";
+import Spinner from "ink-spinner";
+import { fromPromise, fromThrowable } from "neverthrow";
+import path, { dirname, resolve } from "path";
+import { Fragment, ReactNode } from "react";
+import { Error } from "../components/error.js";
+import { Table } from "../components/table.js";
+import { File, parse } from "../file.js";
+import theme from "../theme/index.js";
+import { recursiveDirectoryIterator } from "../utils/fs.js";
+import { fileURLToPath } from "url";
+import { Line } from "../components/line.js";
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = dirname(__filename);
+
+export async function embed(source: string, destination: string)
+{
+ const errors: ReactNode[] = [];
+ const { unmount, rerender } = render();
+
+ const eta = new Eta({ views: path.join(__dirname, "..", "templates") });
+ const writeFile = fromThrowable(outputFileSync, error => (error as string));
+
+ if (!existsSync(destination))
+ {
+ const result = await fromPromise(mkdir(destination, { recursive: true }), error => (error as string));
+
+ if (result.isErr())
+ {
+ rerender(
+
+ );
+
+ unmount(1);
+ return;
+ }
+ }
+
+ if (!existsSync(source) || !lstatSync(source).isDirectory())
+ {
+ rerender(
+
+ );
+
+ unmount(1);
+ return;
+ }
+
+ const files: [string, File][] = [];
+
+ for await (const { absolute, relative } of recursiveDirectoryIterator(source))
+ {
+ const file = parse(absolute, relative);
+
+ if (file.isErr())
+ {
+ errors.push(
+
+ );
+
+ continue;
+ }
+
+ rerender(
+ <>
+ {...errors}
+
+
+
+ }
+ text={["Embedding ", [relative, "greenBright"]]}
+ />
+ >
+ );
+
+ const target = resolve(destination, `${relative}.hpp`);
+ const result = writeFile(target, eta.render("embed", { name: file.value.name, data: file.value.data, size: file.value.size }));
+
+ if (result.isErr())
+ {
+ errors.push(
+
+ );
+
+ continue;
+ }
+
+ files.push([target, file.value]);
+ }
+
+ const result = writeFile(resolve(destination, "all.hpp"), eta.render("all", { files: files.map(x => x[1]) }));
+
+ if (result.isErr())
+ {
+ errors.push(
+
+ );
+
+ unmount(1);
+ }
+
+ const table_data = files.map(([path, file]) => ({
+ File : file.path,
+ Mime : file.mime,
+ Header : path,
+ "Size (KB)": (statSync(path).size / 1024).toFixed(1),
+ }));
+
+ rerender(
+ <>
+
+
+ {...errors}
+ {figureSet.tick}}
+ text={["Embedded ", [files.length.toString(), theme.colors.purple], " file(s)\n"]}
+ />
+ >
+ );
+
+ unmount(0);
+}
diff --git a/src/components/error.tsx b/src/components/error.tsx
new file mode 100644
index 0000000..9d99073
--- /dev/null
+++ b/src/components/error.tsx
@@ -0,0 +1,17 @@
+import figureSet from "figures";
+import { Text } from "ink";
+import { ColoredText, Line } from "./line.js";
+
+interface ErrorProps
+{
+ error?: string | ColoredText[];
+ description: string | ColoredText[];
+}
+
+export function Error({ description, error }: ErrorProps)
+{
+ return {figureSet.cross}}
+ text={[...description, ...(error ? [": ", ...error] : [])]}
+ />;
+}
diff --git a/src/components/line.tsx b/src/components/line.tsx
new file mode 100644
index 0000000..ae98f07
--- /dev/null
+++ b/src/components/line.tsx
@@ -0,0 +1,31 @@
+import { Text, TextProps } from "ink";
+
+export type ColoredText = string | [text: string, color: TextProps["color"]];
+
+interface LineProps
+{
+ icon: React.JSX.Element;
+ text: string | ColoredText[];
+}
+
+function convert(item: ColoredText)
+{
+ if (typeof item === "string")
+ {
+ return {item};
+ }
+
+ return {item[0]};
+}
+
+export function Line({ icon, text }: LineProps)
+{
+ return
+ {icon}
+ {" "}
+ {
+ Array.isArray(text) ?
+ text.map(convert) : convert(text)
+ }
+ ;
+}
diff --git a/src/components/table.tsx b/src/components/table.tsx
index e87cfa8..eeeb55c 100644
--- a/src/components/table.tsx
+++ b/src/components/table.tsx
@@ -1,14 +1,21 @@
import figureSet from "figures";
import { Box, BoxProps, Text, TextProps } from "ink";
import { ReactNode } from "react";
-import colors from "../utils/colors.js";
+import theme from "../theme/index.js";
function unique(array: T[])
{
return [...new Set(array)];
}
-export function Table({ data, distribution, color, ...props }: { data: any[], distribution: number[], color?: TextProps["color"][] } & BoxProps)
+interface TableProps extends BoxProps
+{
+ data: any[];
+ distribution: number[];
+ colors?: TextProps["color"][];
+}
+
+export function Table({ data, distribution, colors, ...props }: TableProps)
{
const headers = unique(data.map(item => Object.keys(item)).flat());
const width = (index: number) => `${distribution[index]}%`;
@@ -48,34 +55,38 @@ export function Table({ data, distribution, color, ...props }: { data: any[], di
{
const rtn = { ...style, borderBottom: false };
- if (headers.length <= 1)
- {
- return rtn;
- }
-
- switch (index)
+ if (headers.length > 1 && index < headers.length - 1)
{
- default:
- case 0:
rtn.borderRight = true;
- break;
- case (headers.length - 1):
- break;
}
return rtn;
};
const Cell = ({ index, children }: { index: number, children: ReactNode}) =>
-
+
{children}
;
- return
+ return
{headers.map((header, index) =>
-
-
+
+
{header}
|
@@ -83,10 +94,16 @@ export function Table({ data, distribution, color, ...props }: { data: any[], di
|
{data.map((row, key) =>
-
+
{headers.map((header, index) =>
-
-
+
+
{row[header]}
|
diff --git a/src/embed.tsx b/src/embed.tsx
deleted file mode 100644
index f792762..0000000
--- a/src/embed.tsx
+++ /dev/null
@@ -1,157 +0,0 @@
-import { Eta } from "eta";
-import figureSet from "figures";
-import { existsSync, lstatSync, statSync } from "fs";
-import { outputFileSync } from "fs-extra/esm";
-import { mkdir } from "fs/promises";
-import { Newline, Text, render } from "ink";
-import Spinner from "ink-spinner";
-import { Result } from "neverthrow";
-import path, { resolve } from "path";
-import { Fragment, ReactNode } from "react";
-import { Table } from "./components/table.js";
-import { File, parse } from "./file.js";
-import colors from "./utils/colors.js";
-import { recursiveDirectoryIterator } from "./utils/fs.js";
-
-export async function embed(source: string, destination: string)
-{
- const writeFile = Result.fromThrowable(outputFileSync);
- const eta = new Eta({ views: path.join(new URL(path.dirname(import.meta.url)).pathname, "templates") });
-
- const { unmount, rerender } = render();
- const errors: ReactNode[] = [];
-
- if (!existsSync(destination))
- {
- try
- {
- await mkdir(destination, { recursive: true });
- }
- catch (error)
- {
- rerender(
-
- {figureSet.cross}
- {" Failed to create destination ("}
- {destination}
- {`): ${error}`}
-
- );
-
- unmount(1);
- return;
- }
- }
-
- if (!existsSync(source) || !lstatSync(source).isDirectory())
- {
- rerender(
-
- {figureSet.cross}
- {" Expected "}
- {source}
- {" to be directory"}
-
- );
-
- unmount(1);
- return;
- }
-
- const files: [string, File][] = [];
-
- for await (const { absolute, relative } of recursiveDirectoryIterator(source))
- {
- const file = parse(absolute, relative);
-
- if (file.isErr())
- {
- errors.push(
-
- {figureSet.cross}
- {" Failed to embed "}
- {relative}
- {`: ${file.error}`}
-
- );
-
- continue;
- }
-
- rerender(
- <>
- {...errors}
-
-
-
-
- {" Embedding "}
-
- {relative}
-
-
- >
- );
-
- const target = resolve(destination, `${relative}.hpp`);
- const result = writeFile(target, eta.render("embed", { name: file.value.name, data: file.value.data, size: file.value.size }));
-
- if (result.isErr())
- {
- errors.push(
-
- {figureSet.cross}
- {" Failed to write "}
- {target}
- {`: ${result.error}`}
-
- );
-
- continue;
- }
-
- files.push([target, file.value]);
- }
-
- const result = writeFile(resolve(destination, "all.hpp"), eta.render("all", { files: files.map(x => x[1]) }));
-
- if (result.isErr())
- {
- errors.push(
-
- {figureSet.cross}
- {" Failed to write "}
- all.hpp
- {`: ${result.error}`}
-
- );
-
- unmount(1);
- }
-
- const table_data = files.map(([path, file]) =>
- {
- return {
- File : file.path,
- Mime : file.mime,
- Header : path,
- "Size (KB)": (statSync(path).size / 1024).toFixed(1),
- };
- });
-
- rerender(
- <>
-
-
- {...errors}
-
- {figureSet.tick}
- {" Embedded "}
- {files.length}
- {" file(s)"}
-
- >
- );
-
- unmount(0);
-}
diff --git a/src/file.ts b/src/file.ts
index 6e2fcb5..5cee074 100644
--- a/src/file.ts
+++ b/src/file.ts
@@ -1,6 +1,6 @@
import { existsSync, lstatSync, readFileSync } from "fs";
-import mimes from "mime-types";
import { Result, err, ok } from "neverthrow";
+import mimes from "mime-types";
export interface File
{
diff --git a/src/index.ts b/src/index.ts
index 03f26f4..efc8cae 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,7 +1,7 @@
#!/usr/bin/env node
import { program } from "commander";
-import { embed } from "./embed.js";
+import { embed } from "./commands/embed.js";
program.command("embed")
.description("Generate embedding headers for given files")
diff --git a/src/templates/all.eta b/src/templates/all.eta
index bb9ee34..1185a09 100644
--- a/src/templates/all.eta
+++ b/src/templates/all.eta
@@ -13,9 +13,9 @@ namespace saucer::embedded
{
std::map rtn;
- <% it.files.forEach(function(file){ %>
+<% it.files.forEach(function(file){ %>
rtn.emplace("<%= file.path %>", embedded_file{"<%= file.mime %>", <%= file.name %>);
- <% }) %>
+<% }) %>
return rtn;
}
diff --git a/src/theme/index.ts b/src/theme/index.ts
new file mode 100644
index 0000000..6125d8c
--- /dev/null
+++ b/src/theme/index.ts
@@ -0,0 +1,6 @@
+export default
+{
+ colors: {
+ purple: "#7474FF"
+ }
+} as const;
diff --git a/src/utils/colors.ts b/src/utils/colors.ts
deleted file mode 100644
index 3e9e15c..0000000
--- a/src/utils/colors.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export default {
- purple: "#7474FF"
-} as const;
diff --git a/tsconfig.json b/tsconfig.json
index f22f612..425d5f1 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,10 +1,21 @@
{
- "extends": "@sindresorhus/tsconfig",
"compilerOptions": {
+ "outDir": "build",
+ "target": "ESNext",
+ "module": "NodeNext",
+ "lib": [
+ "dom",
+ "ES2023",
+ ],
+ "strict": true,
+ "strictNullChecks": true,
"jsx": "react-jsx",
- "outDir": "dist",
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noImplicitReturns": true,
+ "moduleResolution": "NodeNext",
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "skipDefaultLibCheck": true,
},
- "include": [
- "src",
- ]
}
|