From ea26d94b32c737633df76ccc7410fbe00bee5dfa Mon Sep 17 00:00:00 2001 From: JCake Date: Sun, 15 Sep 2024 22:52:19 +0200 Subject: [PATCH] New Build system --- bin/build.js | 57 -------------------------- bin/components.js | 89 ----------------------------------------- bin/log.js | 39 ------------------ bin/path.js | 97 --------------------------------------------- bin/util.js | 30 -------------- build.js | 90 +++++++++++++++++++++++++++++++++++++++++ package.json | 7 ++-- src/formula.tsx | 20 ++++++---- src/modal.tsx | 15 +++++++ src/spreadsheet.tsx | 56 +++++++++++++++++++++----- styles.css | 20 ++++++++++ 11 files changed, 187 insertions(+), 333 deletions(-) delete mode 100644 bin/build.js delete mode 100644 bin/components.js delete mode 100644 bin/log.js delete mode 100644 bin/path.js delete mode 100644 bin/util.js create mode 100644 build.js create mode 100644 src/modal.tsx diff --git a/bin/build.js b/bin/build.js deleted file mode 100644 index c00fee8..0000000 --- a/bin/build.js +++ /dev/null @@ -1,57 +0,0 @@ -import * as fs from 'node:fs/promises'; -import State from '@j-cake/jcake-utils/state'; -import { iterSync } from '@j-cake/jcake-utils/iter'; -import * as Format from '@j-cake/jcake-utils/args'; -import chalk from 'chalk'; - -import log from './log.js'; -import Path from './path.js'; -import * as comp from './components.js'; - -export const config = new State({ - logLevel: 'info', - force: false, - - root: new Path(process.cwd()), - out: new Path(process.cwd()).concat('build'), - - components: [] -}); - -export default async function main(argv) { - const logLevel = Format.oneOf(Object.keys(log), false); - - for (const { current: i, skip: next } of iterSync.peekable(argv)) - if (i == '--log-level') - config.setState({ logLevel: logLevel(next()) }); - - else if (i == '-f' || i == '--force') - config.setState({ force: true }); - - else if (i == '-o' || i == '--out') - config.setState({ out: new Path(next()) }); - - else - config.setState(prev => ({ components: [...prev.components, i] })); - - log.debug(config.get()); - - await fs.mkdir(config.get().out.path, { recursive: true }); - - for (const component of config.get().components) - if (component in components) - await components[component]() - .then(status => log.info(`${chalk.grey(component)}: Done`)); -} - -export const components = { - "build:plugin": () => comp.build_plugin(), - "build:package.json": () => comp.build_package_json(), - "build:manifest.json": () => comp.build_manifest_json(), - "build:style.css": () => comp.build_style_css(), - - "phony:install": () => comp.phony_install(), - "phony:all": () => Promise.all(Object.entries(components) - .filter(([comp, _]) => comp.startsWith("build:")) - .map(([_, fn]) => fn())), -} diff --git a/bin/components.js b/bin/components.js deleted file mode 100644 index 20b41b8..0000000 --- a/bin/components.js +++ /dev/null @@ -1,89 +0,0 @@ -import * as fs from 'node:fs/promises'; -import * as fss from 'node:fs'; -import * as cp from 'node:child_process'; -import esbuild from 'esbuild'; - -import { config } from './build.js'; -import log from './log.js'; -import { has_changed } from './util.js'; - -const is_source = path => path.startsWith(config.get().root.join("src")); - -export async function build_plugin() { - if (!await has_changed({ - glob: path => is_source(path), - dependents: [config.get().out.join("main.js")] - })) - return log.verbose("Skipping Rebuild"); - - await esbuild.build({ - entryPoints: ["src/main.ts"], - bundle: true, - sourcemap: true, - platform: 'node', - format: 'cjs', - external: ['electron', 'obsidian'], - outdir: config.get().out.path - }); -} - -export async function build_package_json() { - if (!await has_changed({ - glob: path => is_source(path), - dependents: [config.get().out.join("package.json")] - })) - return log.verbose("Skipping Rebuild"); - - const jq = cp.spawn('jq', ['-r', '. * .deploy * {deploy:null} | with_entries(select(.value |. != null))']); - - fss.createReadStream(config.get().root.join("package.json").path) - .pipe(jq.stdin); - - jq.stdout.pipe(fss.createWriteStream(config.get().out.join("package.json").path), 'utf8'); - - await new Promise((ok, err) => jq.on("exit", code => code == 0 ? ok() : err(code))); -} - -export async function build_manifest_json() { - if (!await has_changed({ - glob: path => is_source(path), - dependents: [config.get().out.join("manifest.json")] - })) - return log.verbose("Skipping Rebuild"); - - const jq = cp.spawn('jq', ['-r', '.']); - - fss.createReadStream(config.get().root.join("manifest.json").path) - .pipe(jq.stdin); - - jq.stdout.pipe(fss.createWriteStream(config.get().out.join("manifest.json").path), 'utf8'); - - await new Promise((ok, err) => jq.on("exit", code => code == 0 ? ok() : err(code))); -} - -export async function build_style_css() { - if (!await has_changed({ - glob: path => is_source(path), - dependents: [config.get().out.join("styles.css")] - })) - return log.verbose("Skipping Rebuild"); - - await esbuild.build({ - entryPoints: ["styles.css"], - bundle: true, - sourcemap: true, - outdir: config.get().out.path - }); -} - -export async function phony_install() { - const pkg = await fs.readFile(config.get().root.join("package.json")) - .then(pkg => JSON.parse(pkg).name); - - const install = config.get().vault.join(".obsidian/plugins").join(pkg).path; - - await fs.mkdir(install, { recursive: true }); - - for await (const file of config.get().out.readdir()) - await fs.copyFile(file.path, install); -} diff --git a/bin/log.js b/bin/log.js deleted file mode 100644 index 016a82e..0000000 --- a/bin/log.js +++ /dev/null @@ -1,39 +0,0 @@ -import util from 'node:util'; -import chalk from 'chalk'; - -import { config } from './build.js'; - -export const stripAnsi = str => str.replace(/[\u001b\u009b][[()#;?]*(?:\d{1,4}(?:;\d{0,4})*)?[\dA-ORZcf-nqry=><]/g, ''); -export const centre = (text, width) => { - const colourless = stripAnsi(text); - const pad = Math.floor((width - colourless.length) / 2); - return `${' '.repeat(pad)}${text}${' '.repeat(pad)}`.padStart(width, ' '); -} -export function stdout(tag, ...msg) { - const log = msg - .map(i => ['string', 'number', 'bigint', 'boolean'].includes(typeof i) ? i : util.inspect(i, false, null, true)) - .join(' ') - .split('\n') - .map((i, a) => `${a ? centre('\u2502', stripAnsi(tag).length) : tag} ${i}\n`); - - for (const i of log) - process.stdout.write(i); -} - -export function stderr(tag, ...msg) { - const log = msg - .map(i => ['string', 'number', 'bigint', 'boolean'].includes(typeof i) ? i : util.inspect(i, false, null, true)) - .join(' ') - .split('\n') - .map((i, a) => `${a ? centre('\u2502', stripAnsi(tag).length) : tag} ${i}\n`); - - for (const i of log) - process.stderr.write(i); -} - -export default { - err: (...arg) => void (['err', 'info', 'verbose', 'debug'].includes(config.get().logLevel) && stderr(chalk.grey(`[${chalk.red('Error')}]`), ...arg)), - info: (...arg) => void (['info', 'verbose', 'debug'].includes(config.get().logLevel) && stdout(chalk.grey(`[${chalk.blue('Info')}]`), ...arg)), - verbose: (...arg) => void (['verbose', 'debug'].includes(config.get().logLevel) && stdout(chalk.grey(`[${chalk.yellow('Verbose')}]`), ...arg)), - debug: (...arg) => void (['debug'].includes(config.get().logLevel) && stdout(chalk.grey(`[${chalk.cyan('Debug')}]`), ...arg)) -} \ No newline at end of file diff --git a/bin/path.js b/bin/path.js deleted file mode 100644 index 0b1a24b..0000000 --- a/bin/path.js +++ /dev/null @@ -1,97 +0,0 @@ -import * as fs from 'node:fs/promises'; -import * as fss from 'node:fs'; -import * as path from 'node:path'; - -import log from './log.js'; - -export default class Path { - constructor(str) { - if (str instanceof fss.Dirent) - this.path = path.join(str.parentPath, str.name); - else if (str instanceof Path) - this.path = str.path; - else - this.path = path.isAbsolute(str) ? str : path.join(process.cwd(), str); - } - - concat(...paths) { - this.path = path.join(this.path, ...paths.map(i => i instanceof Path ? i.path : i)); - return this; - } - - join(...paths) { - return new Path(path.join(this.path, ...paths.map(i => i instanceof Path ? i.path : i))); - } - - ext() { - return this.path.split(path.sep).pop()?.split('.').pop()?.toLowerCase() ?? ''; - } - - async *readdir(recursive = true) { - const files = await fs.readdir(this.path, { recursive, withFileTypes: true }) - - for (const dirent of files) - yield new Path(dirent); - - // for await (const dir of await fs.readdir(config.get().root.path, { recursive: true, withFileTypes: true })) - // if (dir.isFile()) - // log.info(new Path(dir)); - } - - async mtime() { - return await fs.stat(this.path).then(stat => stat.mtime); - } - - async isFile() { - return await fs.stat(this.path).then(stat => stat.isFile()); - } - - async isDir() { - return await fs.stat(this.path).then(stat => stat.isDirectory()); - } - - async isBlockdev() { - return await fs.stat(this.path).then(stat => stat.isBlockDevice()); - } - - async isChardev() { - return await fs.stat(this.path).then(stat => stat.isCharacterDevice()); - } - - async isPipe() { - return await fs.stat(this.path).then(stat => stat.isFIFO() || stat.isSocket()); - } - - async isFifo() { - return await fs.stat(this.path).then(stat => stat.isFIFO()); - } - - async isSocket() { - return await fs.stat(this.path).then(stat => stat.isSocket()); - } - - async isSymlink() { - return await fs.stat(this.path).then(stat => stat.isSymbolicLink()); - } - - replaceBase(base, newBase) { - if (this.path.startsWith(base.path)) - return newBase.join(this.path.slice(base.path.length)); - - else - throw { - err: 'Could not substitute path', - reason: `Path does not start with '${base.path}'`, - path: this - }; - } - - parent() { - return new Path(this.path.split(path.sep).slice(0, -1).join(path.sep)); - } - - startsWith(base) { - const chunks = this.path.split(path.sep); - return base.path.split(path.sep).every(i => chunks.shift() == i); - } -} \ No newline at end of file diff --git a/bin/util.js b/bin/util.js deleted file mode 100644 index 1d1d230..0000000 --- a/bin/util.js +++ /dev/null @@ -1,30 +0,0 @@ -import { config } from './build.js'; - -const file_tree = async function() { - const files = []; - - if (files.length == 0) - for await (const path of config.get().root.readdir()) - files.push(path); - - return files; -} - -export async function* get_files(glob) { - for (const path of await file_tree()) - if (await glob(path)) - yield path; -} - -export async function has_changed(glob) { - if (config.get().force) - return true; - - const mtimes = await Promise.all(glob.dependents.map(i => i.mtime().catch(_ => new Date(0)))); - - for await (const file of get_files(glob.glob)) - if (await Promise.race(mtimes.map(async i => await file.mtime() > i))) - return true; - - return false; -} diff --git a/build.js b/build.js new file mode 100644 index 0000000..fe656ce --- /dev/null +++ b/build.js @@ -0,0 +1,90 @@ +import fss from "node:fs"; +import fs from "node:fs/promises"; +import cp from "node:child_process"; +import { util, esbuild, log, is_source, Path, chalk } from 'builder'; + +export default { + async "build:plugin"(config) { + if (!await util.has_changed({ + glob: path => is_source(path), + dependents: [config.out.join("main.js")] + })) + return log.verbose("Skipping Rebuild"); + + await esbuild.build({ + entryPoints: ["src/main.ts"], + bundle: true, + sourcemap: true, + platform: 'node', + format: 'cjs', + external: ['electron', 'obsidian'], + outdir: config.out.path + }); + }, + async "build:package.json"(config) { + if (!await util.has_changed({ + glob: path => is_source(path), + dependents: [config.out.join("package.json")] + })) + return log.verbose("Skipping Rebuild"); + + const jq = cp.spawn('jq', ['-r', '. * .deploy * {deploy:null} | with_entries(select(.value |. != null))']); + + fss.createReadStream(config.root.join("package.json").path) + .pipe(jq.stdin); + + jq.stdout.pipe(fss.createWriteStream(config.out.join("package.json").path)); + + await new Promise((ok, err) => jq.on("exit", code => code === 0 ? ok() : err(code))); + }, + async "build:manifest.json"(config) { + if (!await util.has_changed({ + glob: path => is_source(path), + dependents: [config.out.join("manifest.json")] + })) + return log.verbose("Skipping Rebuild"); + + const jq = cp.spawn('jq', ['-r', '.']); + + fss.createReadStream(config.root.join("manifest.json").path) + .pipe(jq.stdin); + + jq.stdout.pipe(fss.createWriteStream(config.out.join("manifest.json").path)); + + await new Promise((ok, err) => jq.on("exit", code => code === 0 ? ok() : err(code))); + }, + async "build:style.css"(config) { + if (!await util.has_changed({ + glob: path => is_source(path), + dependents: [config.out.join("styles.css")] + })) + return log.verbose("Skipping Rebuild"); + + await esbuild.build({ + entryPoints: ["styles.css"], + bundle: true, + sourcemap: true, + outdir: config.out.path + }); + }, + async "phony:install"(config) { + const pkg = await fs.readFile(config.root.join("package.json").path, 'utf8') + .then(pkg => JSON.parse(pkg).name); + + const install = new Path(process.env['vault_dir']).join(".obsidian/plugins").join(pkg); + + await fs.mkdir(install.path, { recursive: true }); + + for await (const file of config.out.readdir()) + if (await file.isFile()) + await fs.copyFile(file.path, file.replaceBase(config.out, install).path); + else if (await file.isDir()) + await fs.mkdir(file.replaceBase(config.out, install).path, { recursive: true }); + }, + async "phony:all"(config) { + for (const [key, comp] of Object.entries(config.components)) + if (key.startsWith("build:")) + await comp(config) + .then(_ => log.info(` ${chalk.grey(key)}: Done`)); + } +} \ No newline at end of file diff --git a/package.json b/package.json index 458674b..938fa51 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "spreadsheet", - "main": "./build/index.js", + "build": "./build.js", "version": "0.1.0", "type": "module", "dependencies": { @@ -14,17 +14,16 @@ "chrono-node": "https://github.com/wanasit/chrono.git" }, "scripts": { - "build": "node -e \"await import('./bin/build.js').then(script => script.default(process.argv.slice(1)))\" --" + "build": "build phony:all phony:install" }, "devDependencies": { - "@j-cake/mkjson": "latest", "@types/luxon": "latest", "@types/node": "latest", "@types/react": "latest", "@types/react-dom": "latest", + "builder": "link:../../builder", "chalk": "latest", "electron": "latest", - "esbuild": "latest", "obsidian": "latest", "typescript": "latest" }, diff --git a/src/formula.tsx b/src/formula.tsx index c7a205d..e03801f 100644 --- a/src/formula.tsx +++ b/src/formula.tsx @@ -78,11 +78,11 @@ export namespace renderers { }, formula(props: Props): React.ReactNode { React.useSyncExternalStore(props.onChange, () => props.getRaw()); - const ref = React.createRef(); - setTimeout(() => ref.current?.focus()); + // const ref = React.createRef(); + // setTimeout(() => ref.current?.focus()); return