diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5e56e04 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/bin diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..b943dbc --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "deno.enable": true +} \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..9be73a6 --- /dev/null +++ b/build.sh @@ -0,0 +1,16 @@ +#!/bin/sh +set -e +cd "$(dirname "$(realpath "$0")")" + + +programs="gen_module install" + + +rm -fr bin +mkdir -p bin + + +for program in $programs +do + deno compile --output="bin/mbztr_$program" --allow-all "src/$program.ts" +done diff --git a/complete.ts b/complete.ts deleted file mode 100644 index 9891f59..0000000 --- a/complete.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { success_format, value_format, process_format, prompt, failure_format } from "./lib/utilities.ts" -import { Context, Config } from "./lib/context.ts" - -// import { HelloWorldSetup } from "./parts/setup-hello-world.ts" -import { BatSetup } from "./parts/bat.ts" - -const config: Config = { - working_directory: "./barnulfizator-wd" -} -const all = [ - //new HelloWorldSetup(), - // nano - // sudo - // rustup - // paru - // zsh - // kitty - // lvim - // n - // tldr - new BatSetup(), - // rc -]; - -async function main() { - - console.log( - success_format("all components:\n") - + all - .map(s => `- '${value_format(s.name)}'`) - .join("\n") - + "\n" - ); - - const context = new Context(config); - - for (const setup of all) { - const input = await prompt(`Install '${setup.name}' ?`, ["y", "n"], "y"); - if (input == "y") context.push_to_install(setup); - } - - while (true) { - const setup = context.next_to_install(); - if (setup == undefined) break; - - console.log(process_format("Installing '") + value_format(setup.name) + process_format("' ...")) - const result = await setup.install(context); - - if (result == "Ok") { - console.log(success_format("Installed '") + value_format(setup.name) + success_format("' successfully.")); - context.set_installed(setup); - } - else { - console.log(failure_format("Failed to install '") + value_format(setup.name) + failure_format("'.")); - context.set_failed(setup); - } - } -} - -await main() \ No newline at end of file diff --git a/deno.json b/deno.json new file mode 100644 index 0000000..38d5831 --- /dev/null +++ b/deno.json @@ -0,0 +1,6 @@ +{ + "fmt": { + "useTabs": true, + "lineWidth": 120 + } +} \ No newline at end of file diff --git a/deno.lock b/deno.lock new file mode 100644 index 0000000..5707d23 --- /dev/null +++ b/deno.lock @@ -0,0 +1,7 @@ +{ + "version": "3", + "redirects": { + "https://deno.land/x/zod/mod.ts": "https://deno.land/x/zod@v3.22.4/mod.ts" + }, + "remote": {} +} diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..1a24852 --- /dev/null +++ b/install.sh @@ -0,0 +1 @@ +#!/bin/sh diff --git a/lib/context.ts b/lib/context.ts deleted file mode 100644 index 93e3a8f..0000000 --- a/lib/context.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Setup } from "./setup.ts" - -export type Config = { - working_directory: string, -} - -export class Context { - installed_names: string[]; - to_install: Setup[]; - working_dir: string; - - constructor(config: Config) { - this.installed_names = []; - this.to_install = []; - this.working_dir = config.working_directory; - } - - push_to_install(setup: Setup) { - this.to_install.push(setup) - } - - set_installed(setup: Setup) { - const index = this.to_install.findIndex(e => e.name == setup.name); - if (index != -1) this.to_install.splice(index, 1); - this.installed_names.push(setup.name); - } - - set_failed(setup: Setup) { - const index = this.to_install.findIndex(e => e.name == setup.name); - if (index != -1) this.to_install.splice(index, 1); - } - - next_to_install(): Setup | undefined { - return this.to_install[0] - } -} \ No newline at end of file diff --git a/lib/deps.ts b/lib/deps.ts deleted file mode 100644 index b5fe24b..0000000 --- a/lib/deps.ts +++ /dev/null @@ -1 +0,0 @@ -export { styles } from "https://deno.land/x/ansi_styles@1.0.0/mod.ts"; diff --git a/lib/setup.ts b/lib/setup.ts deleted file mode 100644 index 3587d88..0000000 --- a/lib/setup.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Context } from "./context.ts" - -export type SetupResult = "Ok" | "Error"; - -export abstract class Setup { - name: string; - dependencies: string[]; - - constructor(options: { name: string, dependencies?: string[] }) { - this.dependencies = options.dependencies ?? []; - this.name = options.name; - } - - abstract install(context: Context): Promise; -} diff --git a/lib/utilities.ts b/lib/utilities.ts deleted file mode 100644 index 9a32f10..0000000 --- a/lib/utilities.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { readLines } from "https://deno.land/std@0.140.0/io/mod.ts"; -import { writeAll } from "https://deno.land/std@0.140.0/streams/conversion.ts "; -import { styles } from "./deps.ts" - -export type CommandResult = { - status: "ok" -} | { - status: "Error", - stdout: string, - stderr: string -} - -/** Synthesize platform dependent shell command arguments. */ -function shArgs(command: string): string[] { - if (Deno.build.os === "windows") { - return ["PowerShell.exe", "-Command", command]; - } else { - const shellExe = Deno.env.get("SHELL") ?? "/bin/sh"; - return [shellExe, "-c", command]; - } -} - -/* -0x6a j ┘ -0x6b k ┐ -0x6c l ┌ -0x6d m └ -0x6e n ┼ -0x71 q ─ -0x74 t ├ -0x75 u ┤ -0x76 v ┴ -0x77 w ┬ -0x78 x │ - -*/ - - -export function value_format(text: string): string { - return `${styles.bold.open}${styles.white.open}${text}${styles.white.close}${styles.bold.close}`; -} - -export function process_format(text: string): string { - return `${styles.blue.open}${text}${styles.blue.close}`; -} - -export function prompt_format(text: string): string { - return `${styles.yellow.open}${text}${styles.yellow.close}`; -} - -export function success_format(text: string): string { - return `${styles.green.open}${text}${styles.green.close}`; -} - -export function failure_format(text: string): string { - return `${styles.red.open}${text}${styles.red.close}`; -} - -export async function run(command: string): Promise { - //throw "todo"; - - console.log(`${value_format("┌")} ${process_format("running '")}${value_format(command)}${process_format("'")}`); - - const cmd = shArgs(command); - const process = Deno.run({ - cmd: cmd, - stdin: "piped", - stdout: "piped", - stderr: "piped" - }); - - const [_o, _e, satus] = await Promise.all([pipe_out(process.stdout), pipe_out(process.stderr), process.status()]); - const code = satus.code; - return code; -} - -export async function pipe_out(out: Deno.Reader) { - const encoder = new TextEncoder(); - for await (const line of readLines(out)) - await writeAll(Deno.stdout, encoder.encode(`${value_format("│")} ${line}\n`)); -} - -// asks a question to the user -export async function prompt(line: string, options?: string[], default_?: string) { - - let options_part = ""; - if (options != undefined) options_part = `[${options.map(s => value_format(s)).join('/')}]`; - - let default_part = ""; - if (default_ != undefined) default_part = `(default: '${default_}')`; - - const text = `─> ${prompt_format(line)} ${options_part} ${default_part}`; - console.log(text); - - for await (const line of readLines(Deno.stdin)) { - if (default_ != undefined && line == "") return default_; - - if (options != undefined && !options.includes(line)) { - console.log(text); - continue; - } - - return line - } -} diff --git a/parts/bat.ts b/parts/bat.ts deleted file mode 100644 index c09b4e6..0000000 --- a/parts/bat.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Setup, SetupResult } from "../lib/setup.ts" -import { Context } from "../lib/context.ts" -import { prompt, run, prompt_format } from "../lib/utilities.ts" - -export class BatSetup extends Setup { - constructor() { - super({ name: "bat" }) - } - - async install(context: Context): Promise { - const method = await prompt("Which method ?", ["pacman", "github", "cargo"], "pacman"); - - if (method == "pacman") { - await run("sudo pacman -S --noconfirm community/bat"); - } - - if (method == "github") { - await run(`mkdir -p ${context.working_dir}`); - await run(`wget "https://github.com/sharkdp/bat/releases/download/v0.21.0/bat-v0.21.0-i686-unknown-linux-gnu.tar.gz" -O "${context.working_dir}/bat-bin-linux.tar.gz"`); - await run(`tar xf "${context.working_dir}/bat-bin-linux.tar.gz" -C "${context.working_dir}"`); - await run(`mkdir -p "$HOME/.local/bin"`); - await run(`cp "${context.working_dir}/bat-v0.21.0-i686-unknown-linux-gnu/bat" "$HOME/.local/bin/"`); - await run(`rm -rf "${context.working_dir}/bat-v0.21.0-i686-unknown-linux-gnu" "${context.working_dir}/bat-bin-linux.tar.gz"`) - console.log(prompt_format("Don't forget to add '$HOME/.local/bin' to your path.")); - } - - if (method == "cargo") { - throw "TODO" - } - - return "Ok" - } -} diff --git a/parts/hello-world.ts b/parts/hello-world.ts deleted file mode 100644 index 673e4b3..0000000 --- a/parts/hello-world.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { run } from "../lib/utilities.ts" -import { Setup, SetupResult } from "../lib/setup.ts" - -export class HelloWorldSetup extends Setup { - constructor() { - super({ name: "hello-world" }); - } - - async install(): Promise { - await run("echo hello yorld"); - return "Ok" - } -} diff --git a/parts/lvim.ts b/parts/lvim.ts deleted file mode 100644 index e69de29..0000000 diff --git a/parts/n.ts b/parts/n.ts deleted file mode 100644 index e69de29..0000000 diff --git a/parts/nano.ts b/parts/nano.ts deleted file mode 100644 index e69de29..0000000 diff --git a/parts/paru.ts b/parts/paru.ts deleted file mode 100644 index e69de29..0000000 diff --git a/parts/rc.ts b/parts/rc.ts deleted file mode 100644 index f50ee6d..0000000 --- a/parts/rc.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Setup, SetupResult } from "../lib/setup.ts" - -export class RcSetup extends Setup { - async install(): Promise { - return "Ok"; - } -} diff --git a/parts/rustup.ts b/parts/rustup.ts deleted file mode 100644 index e69de29..0000000 diff --git a/parts/sudo.ts b/parts/sudo.ts deleted file mode 100644 index e69de29..0000000 diff --git a/parts/tldr.ts b/parts/tldr.ts deleted file mode 100644 index e69de29..0000000 diff --git a/parts/zsh.ts b/parts/zsh.ts deleted file mode 100644 index e69de29..0000000 diff --git a/prelude.sh b/prelude.sh deleted file mode 100644 index 8036b82..0000000 --- a/prelude.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/sh - -# installing deno without sudo (necessary for continuation) -curl -fsSL https://deno.land/x/install/install.sh | sh - -# adding deno to the environment -export DENO_INSTALL="~/.deno" -export PATH="$DENO_INSTALL/bin:$PATH" - -# indications -echo "# run with : -deno run -A \"https://raw.githubusercontent.com/MajorBarnulf/mbztr/main/complete.ts\"" diff --git a/src/gen_module.ts b/src/gen_module.ts new file mode 100644 index 0000000..3ed7365 --- /dev/null +++ b/src/gen_module.ts @@ -0,0 +1,64 @@ +import * as cli from "https://deno.land/std@0.219.0/cli/mod.ts"; + +const USAGE = `mbztr_gen_module - Interactively generate a MBZTR module. + +Usage: + mbztr_gen_module [options] [...file] + +Arguments: + name Name for the module. + file List of files or directories in the module. + +Options: + --help, -h Prints help message. +`; + +async function main() { + const { module_name, file_roots } = parse_args(Deno.args); + const file_paths = await select_files(file_roots); +} + +function fail_with_invalid_args() { + console.log(USAGE); + Deno.exit(1); +} + +function parse_args(ags: string[]) { + const parsed_args = cli.parseArgs(ags); + for (const h of ["-h", "--help"]) if (parsed_args[h] !== undefined) fail_with_invalid_args(); + for (const arg of parsed_args._) if (typeof arg !== "string") fail_with_invalid_args(); + const [module_name, ...file_roots] = parsed_args._; + if (module_name === undefined) fail_with_invalid_args(); + return { module_name, file_roots }; +} + +/* + +cases : +- contains bin : ask for all bins +- contains dir/files > 1Mb : walk dir + + */ + +async function* select_files(file_roots: string[]) { + for (const root of file_roots) { + const all_files = await discover_files(root); + } +} + +async function* discover_files(root: string) { + if (await disk_usage(root) > 5_000_000) for await (const selected of prompt_select_per_size(root)) yield selected; + else for await (const file of walk_dir(root)) yield file; +} + +async function* prompt_select_per_size(root: string) { + throw "todo"; + yield ""; +} + +async function disk_usage(path: string) { + throw "todo"; + return 0; +} + +await main(); diff --git a/parts/kitty.ts b/src/install.ts similarity index 100% rename from parts/kitty.ts rename to src/install.ts diff --git a/src/lib/module.ts b/src/lib/module.ts new file mode 100644 index 0000000..e72fd57 --- /dev/null +++ b/src/lib/module.ts @@ -0,0 +1,65 @@ +import { z } from "https://deno.land/x/zod@v3.22.4/mod.ts"; + +export type Rule = { + path: string; + kind: "include" | "exclude"; +}; + +export class ModuleLayout { + rules; + + constructor(rules: Rule[]) { + this.rules = rules; + } + + public static new_empty() { + return new ModuleLayout([]); + } + + public append_rule(rule: Rule) { + // + } + + public package_to() { + } +} + +export type ModuleFile = { + path: string; + kind: "text" | "bin"; + content: string; +}; + +export class Module { + layout; + content; + + constructor(layout: ModuleLayout, content: ModuleFile[]) { + this.layout = layout; + this.content = content; + } + + public serialize() { + const layout = { rules: this.layout.rules }; + const content = this.content; + JSON.stringify({ layout, content }); + } + + public static deserialize(serialized: string) { + const { layout: { rules }, content } = z.object({ + layout: z.object({ + rules: z.array(z.object({ + path: z.string(), + kind: z.literal("include").or(z.literal("exclude")), + })), + }), + content: z.array(z.object({ + path: z.string(), + kind: z.literal("text").or(z.literal("bin")), + content: z.string(), + })), + }).parse(JSON.parse(serialized)); + const layout = new ModuleLayout(rules); + return new Module(layout, content); + } +}