Skip to content

Instantly share code, notes, and snippets.

@anonhostpi
Last active November 20, 2025 01:15
Show Gist options
  • Select an option

  • Save anonhostpi/6a8e78b65f58177a17326e9c336d275f to your computer and use it in GitHub Desktop.

Select an option

Save anonhostpi/6a8e78b65f58177a17326e9c336d275f to your computer and use it in GitHub Desktop.
Generate a yaml tree representing a directory
#!/usr/bin/env -S deno run --allow-read --allow-run
import { parseArgs } from "jsr:@std/cli/parse-args";
import { resolve, normalize, relative } from "jsr:@std/path";
interface Tree {
[key: string]: string | Tree;
}
// -------------------------------------------------------------
// Helpers
// -------------------------------------------------------------
// Returns true if the path is ignored by .gitignore (if inside a git repo)
async function gitIgnored(absPath: string): Promise<boolean> {
try {
const p = Deno.run({
cmd: ["git", "check-ignore", absPath],
stdout: "null",
stderr: "null",
});
const status = await p.status();
return status.success;
} catch {
return false; // if not a git repo or git unavailable
}
}
// Ignore any file or directory starting with "."
function isHidden(name: string): boolean {
return name.startsWith(".");
}
// -------------------------------------------------------------
// Main tree reader
// -------------------------------------------------------------
async function treeToObject(
dir: string,
maxDepth?: number,
currentDepth = 0,
): Promise<Tree> {
const tree: Tree = {};
try {
for await (const entry of Deno.readDir(dir)) {
const absPath = resolve(dir, entry.name);
// Skip hidden
if (isHidden(entry.name)) continue;
// Skip .gitignore-ignored paths
if (await gitIgnored(absPath)) continue;
if (entry.isDirectory) {
if (maxDepth !== undefined && currentDepth >= maxDepth) {
tree[entry.name + "/"] = "__skipped (max depth)__";
} else {
tree[entry.name] = await treeToObject(
absPath,
maxDepth,
currentDepth + 1,
);
}
} else if (entry.isFile) {
try {
const text = await Deno.readTextFile(absPath);
tree[entry.name] = text;
} catch (err) {
tree[entry.name] = err instanceof Deno.errors.InvalidData
? "__binary__"
: `__error__: ${err.message}`;
}
}
}
} catch (err) {
tree["__error__"] = err.message;
}
return tree;
}
// -------------------------------------------------------------
// YAML literal-block output
// -------------------------------------------------------------
function literalBlockYaml(obj: unknown, indent = 0): string {
const pad = " ".repeat(indent);
if (obj === null || obj === undefined) return "null";
if (typeof obj === "string") {
const blockIndent = pad + " ";
const lines = obj.split(/\r?\n/)
.map((line) => blockIndent + line)
.join("\n");
return `|\n${lines}`;
}
if (typeof obj !== "object") return JSON.stringify(obj);
return Object.entries(obj as Record<string, unknown>)
.map(([key, value]) => {
const rendered = literalBlockYaml(value, indent + 1);
if (rendered.startsWith("|")) {
return `${pad}${key}: ${rendered}`;
} else if (rendered.includes("\n")) {
return `${pad}${key}:\n${rendered}`;
}
return `${pad}${key}: ${rendered}`;
})
.join("\n");
}
// -------------------------------------------------------------
// CLI
// -------------------------------------------------------------
if (import.meta.main) {
const args = parseArgs(Deno.args, {
string: ["o", "output", "d", "depth"],
boolean: ["sort"],
alias: { o: "output", d: "depth" },
});
const input = args._[0];
if (!input || typeof input !== "string") {
console.error("Usage: tree2yaml.ts <path> [-o file.yaml] [--depth N] [--sort]");
Deno.exit(1);
}
const root = normalize(resolve(Deno.cwd(), input));
const depth = args.depth ? Number(args.depth) : undefined;
console.error(`πŸ“‚ Scanning ${root}`);
const tree = await treeToObject(root, depth);
function sortObject(obj: Tree): Tree {
return Object.fromEntries(
Object.entries(obj)
.sort(([a], [b]) => a.localeCompare(b))
.map(([k, v]) => [
k,
typeof v === "object" ? sortObject(v as Tree) : v,
]),
);
}
const finalTree = args.sort ? sortObject(tree) : tree;
const yaml = literalBlockYaml(finalTree) + "\n";
if (args.output) {
await Deno.writeTextFile(args.output, yaml);
console.error(`πŸ“ Wrote to ${args.output}`);
} else {
console.log(yaml);
}
console.error("βœ… Done");
}
@anonhostpi
Copy link
Author

anonhostpi commented Nov 3, 2025

run with:

$path = "."

$USER = 'anonhostpi';
$GIST_ID = '6a8e78b65f58177a17326e9c336d275f'

$url = "https://gist.githubusercontent.com/$USER/$GIST_ID/raw/tree.yaml.ts"
deno run --allow-read "$url\?nocache=$(Get-Random)" "$path" | Set-Clipboard

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment