Begin the bulk script.
This commit is contained in:
		
							parent
							
								
									9761204fda
								
							
						
					
					
						commit
						f350e75ff5
					
				| 
						 | 
				
			
			@ -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/<hostname>/` 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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,7 @@
 | 
			
		|||
#!/usr/bin/env zsh
 | 
			
		||||
 | 
			
		||||
deno run \
 | 
			
		||||
  --allow-read \
 | 
			
		||||
  --allow-run \
 | 
			
		||||
  "$BAUKE_DIR/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<void> {
 | 
			
		||||
  await new Command()
 | 
			
		||||
    .name("bulk")
 | 
			
		||||
    .description("Bulk doer of things.")
 | 
			
		||||
    .command("run", runCommand)
 | 
			
		||||
    .parse(Deno.args);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
if (import.meta.main) {
 | 
			
		||||
  void main();
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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 <directory:string>",
 | 
			
		||||
    "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<void> {
 | 
			
		||||
  let command: string[] = [];
 | 
			
		||||
  const previousPromptArgs: Map<number, string> = new Map();
 | 
			
		||||
  const suggestedPromptArgs: Set<string> = 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();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue