diff --git a/.bauke/Deno.md b/.bauke/Deno.md index 01d6cbc..2cf40a5 100644 --- a/.bauke/Deno.md +++ b/.bauke/Deno.md @@ -2,6 +2,7 @@ ## Scripts +- [`bulk`]: bulk doer of things. - [`codium-extensions`]: save and install VS Codium extensions using their identifier. - [`copy-nixos-config`]: copies NixOS configuration from `$BAUKE_DIR/nix//` to `/etc/nixos/`. - [`desktop-wallpaper`]: desktop wallpaper changer. @@ -11,6 +12,7 @@ - [`simple-git-push`]: `git push` with extra semantics. - [`tauon-controls`]: remote control CLI for Tauon Music Box. +[`bulk`]: ./scripts/bulk/bulk.ts [`codium-extensions`]: ./scripts/codium-extensions.ts [`copy-nixos-config`]: ./scripts/copy-nixos-config.ts [`desktop-wallpaper`]: ./scripts/desktop-wallpaper.ts diff --git a/.bauke/bin/bulk b/.bauke/bin/bulk new file mode 100755 index 0000000..f37d47c --- /dev/null +++ b/.bauke/bin/bulk @@ -0,0 +1,7 @@ +#!/usr/bin/env zsh + +deno run \ + --allow-read \ + --allow-run \ + "$BAUKE_DIR/scripts/bulk/bulk.ts" \ + "$@" diff --git a/.bauke/scripts/bulk/bulk.ts b/.bauke/scripts/bulk/bulk.ts new file mode 100644 index 0000000..52cb081 --- /dev/null +++ b/.bauke/scripts/bulk/bulk.ts @@ -0,0 +1,15 @@ +import { Command } from "https://deno.land/x/cliffy@v0.25.5/command/mod.ts"; + +import { runCommand } from "./run.ts"; + +async function main(): Promise { + await new Command() + .name("bulk") + .description("Bulk doer of things.") + .command("run", runCommand) + .parse(Deno.args); +} + +if (import.meta.main) { + void main(); +} diff --git a/.bauke/scripts/bulk/run.ts b/.bauke/scripts/bulk/run.ts new file mode 100644 index 0000000..829f3f7 --- /dev/null +++ b/.bauke/scripts/bulk/run.ts @@ -0,0 +1,146 @@ +import { Command } from "https://deno.land/x/cliffy@v0.25.5/command/mod.ts"; +import * as prompt from "https://deno.land/x/cliffy@v0.25.5/prompt/mod.ts"; + +export const runCommand = new Command() + .name("run") + .description("Run a command over a group of files and directories.") + .option( + "-d, --directory ", + "Directories to include files from.", + { + collect: true, + required: true, + }, + ) + .option( + "--include-directories", + "Include directories found inside the directories.", + ) + .action(async ({ directory, includeDirectories }) => { + await actionHandler({ + directories: directory, + includeDirectories: includeDirectories ?? false, + }); + }); + +async function actionHandler( + options: { + directories: string[]; + includeDirectories: boolean; + }, +): Promise { + let command: string[] = []; + const previousPromptArgs: Map = new Map(); + const suggestedPromptArgs: Set = new Set(); + const substituteMarkers = { + absoluteFile: "$absoluteFile", + filename: "$filename", + }; + + for (const directory of options.directories) { + console.log(`\n## Input for ${directory}`); + const constructedCommands: string[][] = []; + const prompts = await prompt.prompt([ + { + type: prompt.List, + name: "command", + message: "Arguments of the command to run separated by comma", + default: command, + suggestions: Object.values(substituteMarkers), + }, + { + type: prompt.Confirm, + name: "confirmCommands", + message: "Manually approve each command invocation", + default: true, + }, + ]); + + command = prompts.command ?? command; + for await (const file of Deno.readDir(directory)) { + if (!options.includeDirectories && file.isDirectory) { + continue; + } + + const absoluteFile = await Deno.realPath(`${directory}/${file.name}`); + const constructedCommand: string[] = []; + let promptArgCount = 1; + const substitutes = [ + [substituteMarkers.absoluteFile, absoluteFile], + [substituteMarkers.filename, file.name], + ]; + + argumentLoop: + for (const argument of command) { + if (argument === "$prompt") { + const { promptedArg } = await prompt.prompt([{ + type: prompt.Input, + name: "promptedArg", + message: `$prompt ${promptArgCount} for ${absoluteFile}`, + default: previousPromptArgs.get(promptArgCount) ?? "", + suggestions: Array.from(suggestedPromptArgs), + }]); + + previousPromptArgs.set(promptArgCount, promptedArg!); + suggestedPromptArgs.add(promptedArg!); + constructedCommand.push(promptedArg!); + promptArgCount += 1; + } else { + for (const [marker, substitute] of substitutes) { + if (argument === marker) { + constructedCommand.push(substitute); + continue argumentLoop; + } + } + + constructedCommand.push(argument); + } + } + + if (prompts.confirmCommands) { + const { confirmedRun } = await prompt.prompt([ + { + type: prompt.Confirm, + name: "confirmedRun", + message: `Run "${constructedCommand.join(" ")}"`, + default: true, + }, + ]); + + if (!confirmedRun) { + continue; + } + } + + constructedCommands.push(constructedCommand); + } + + if (constructedCommands.length === 0) { + console.log("\n No commands to run."); + continue; + } + + console.log("\n## Commands"); + console.log(constructedCommands.map((c) => c.join(" ")).join("\n")); + + const { confirmedRun } = await prompt.prompt([ + { + type: prompt.Confirm, + name: "confirmedRun", + message: "Is this correct", + default: true, + }, + ]); + + if (!confirmedRun) { + continue; + } + + console.log("\n## Output"); + for (const constructedCommand of constructedCommands) { + await Deno.run({ + cmd: constructedCommand, + }).status(); + } + } +}