Skip to content

Instantly share code, notes, and snippets.

@kawarimidoll
Last active July 8, 2021 07:09
Show Gist options
  • Save kawarimidoll/c6f1c1007370b00bd4d345525490cdb8 to your computer and use it in GitHub Desktop.
Save kawarimidoll/c6f1c1007370b00bd4d345525490cdb8 to your computer and use it in GitHub Desktop.
denotree

denotree

tree powered by Deno 🦕

This automatically respects .gitignore.

install

$ deno install --allow-read --allow-run --force --name denotree https://gist.githubusercontent.com/kawarimidoll/c6f1c1007370b00bd4d345525490cdb8/raw/af0ed893aec412bbf1dcb0e4a87f56507ef831b8/tree.ts

You can use other name by changing --name denotree option.

usage

Run tree from the current directory:

$ denotree

Run tree from the specific directory:

$ denotree dirname

options

  • a
    • Show dotfiles
  • d
    • Show only directories
  • u
    • Show git ignored files
  • L=num
    • Limit depth
  • h
    • Show help message
import { parse } from "https://deno.land/std@0.100.0/flags/mod.ts";
import { join, resolve } from "https://deno.land/std@0.100.0/path/mod.ts";
// ref: WalkEntry and WalkOptions
// in https://deno.land/std@0.100.0/fs/mod.ts
export interface TreeEntry extends Deno.DirEntry {
path: string;
}
export interface TreeOptions {
maxDepth?: number;
includeFiles?: boolean;
followSymlinks?: boolean;
exts?: string[];
match?: RegExp[];
skip?: RegExp[];
}
function include(
path: string,
exts?: string[],
match?: RegExp[],
skip?: RegExp[],
) {
if (exts && !exts.some((ext) => path.endsWith(ext))) {
return false;
}
if (match && !match.some((pattern) => !!path.match(pattern))) {
return false;
}
if (skip && skip.some((pattern) => !!path.match(pattern))) {
return false;
}
return true;
}
const tree = async (
root: string,
prefix = "",
{
maxDepth = Infinity,
includeFiles = true,
followSymlinks = false,
exts = undefined,
match = undefined,
skip = undefined,
}: TreeOptions = {},
) => {
if (maxDepth < 1 || !include(root, undefined, undefined, skip)) {
return;
}
const entries: TreeEntry[] = [];
for await (const entry of Deno.readDir(root)) {
if (entry.isFile && !includeFiles) {
continue;
}
entries.push({ ...entry, path: join(root, entry.name) });
}
if (entries.length == 0) {
return;
}
const sortedEntries = entries.sort((a, b) =>
a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1
);
const lastOne = sortedEntries[entries.length - 1];
for await (const entry of sortedEntries) {
const branch = entry === lastOne ? "└── " : "├── ";
const suffix = (entry.isDirectory) ? "/" : "";
if (include(entry.path, exts, match, skip)) {
console.log(prefix + branch + entry.name + suffix);
}
if (entry.isDirectory && entry.name !== ".git") {
const indent = entry === lastOne ? " " : "│ ";
await tree(entry.path, prefix + indent, {
maxDepth: maxDepth - 1,
includeFiles,
followSymlinks,
exts,
match,
skip,
});
}
}
};
const {
a,
d,
L,
u,
h,
_: [dir = "."],
} = parse(Deno.args);
if (h) {
const msg = `denotree
'tree' powered by Deno
USAGE
denotree [dirname] : Show children of dirname. default dirname is pwd.
OPTIONS
a : Show dotfiles
d : Show only directories
u : Show git ignored files
L=num : Limit depth
h : Show this help message
`;
console.log(msg);
Deno.exit(0);
}
const skip = [];
if (!a) {
skip.push(/(^|\/)\./);
}
if (!u) {
const process = Deno.run({
cmd: ["git", "status", "--ignored", "-s"],
stdout: "piped",
stderr: "piped",
});
const outStr = new TextDecoder().decode(await process.output());
process.close();
const ignoredList = outStr.replace(/^[^!].+$/gm, "").replace(/^!! /mg, "")
.split("\n").filter((item) => item).concat(".git");
ignoredList.forEach((str) => {
skip.push(new RegExp(str.replace(".", "\\.")));
});
}
console.log(dir);
await tree(resolve(Deno.cwd(), String(dir)), "", {
maxDepth: L,
includeFiles: !d,
followSymlinks: false,
exts: undefined,
match: undefined,
skip,
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment