DenoでCLIツール作ってみる
Deno.args
で引数を取得できる
console.log(Deno.args);
❯ deno run main.ts param1 param2 param3
[ "param1", "param2", "param3" ]
stg/flags
のparse
を使うと引数をフラグで分けて取得できる
+ import { parse } from "https://deno.land/std@0.100.0/flags/mod.ts";
console.log(Deno.args);
+ console.log(parse(Deno.args));
❯ deno run main.ts -a AA -b BB --long LONG param1 param2 param3
[
"-a", "AA",
"-b", "BB",
"--long", "LONG",
"param1", "param2",
"param3"
]
{ _: [ "param1", "param2", "param3" ], a: "AA", b: "BB", long: "LONG" }
Deno.args
だけだと単に配列だがparse
を使うことでオブジェクトになっている
_
にはフラグと関係ない引数が入る
❯ deno run main.ts -a -b --long=LONG --long=LONG2 param1 param2 param3
[
"-a",
"-b",
"--long=LONG",
"--long=LONG2",
"param1",
"param2",
"param3"
]
{ _: [ "param1", "param2", "param3" ], a: true, b: true, long: [ "LONG", "LONG2" ] }
パラメータが渡されなかった引数はtrue
になる
同じ名前のものは配列にまとめられる
順番が前後したけど参考ページ
std/path
を使ってディレクトリパスを取得し、Deno.readDir()
で内部を表示する
import { parse } from "https://deno.land/std@0.100.0/flags/mod.ts";
import { resolve } from "https://deno.land/std@0.100.0/path/mod.ts";
const { _: [dir = "."] } = parse(Deno.args);
const dirFullPath = resolve(Deno.cwd(), String(dir));
console.log(dirFullPath);
for await (const entry of Deno.readDir(dirFullPath)) {
console.log(entry.name);
}
Deno.cwd()
の事項にはread permissionが必要
❯ deno run --allow-read main.ts
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground
.vim
.DS_Store
main.ts
LICENSE
line_notify.ts
fetch_twilog.ts
tweet_with_ifttt.ts
README.md
logger.ts
.gitignore
.env
examples
.github
.env.example
env.ts
.git
app.log
assets
deps.ts
rss.ts
promote_zenn_article.ts
velociraptor.yml
❯ deno run --allow-read main.ts examples
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/examples
line_notify.ts
tweet_with_ifttt.ts
rss.ts
zenn_api.ts
ディレクトリの中身を表示できた
これを辿っていくのはちょっとtrickyということでstd/fs
のwalk
を使う
import { parse } from "https://deno.land/std@0.100.0/flags/mod.ts";
import { resolve } from "https://deno.land/std@0.100.0/path/mod.ts";
+ import { walk } from "https://deno.land/std@0.100.0/fs/mod.ts";
const { _: [dir = "."] } = parse(Deno.args);
const dirFullPath = resolve(Deno.cwd(), String(dir));
console.log(dirFullPath);
- for await (const entry of Deno.readDir(dirFullPath)) {
- console.log(entry.name);
+ for await (const entry of walk(dirFullPath, { skip: [/\.git$/] })) {
+ console.log(entry);
}
skip: [/\.git$/]
オプションで.git
ディレクトリの中は見ないようにしている
wak()
の実行はunstable
フラグが必要
❯ deno run --allow-read --unstable main.ts
Check file:///Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/main.ts
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground
{
path: "/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground",
name: "deno-dev-playground",
isFile: false,
isDirectory: true,
isSymlink: false
}
{
path: "/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/.vim",
name: ".vim",
isFile: false,
isDirectory: true,
isSymlink: false
}
{
path: "/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/.vim/coc-settings.json",
name: "coc-settings.json",
isFile: true,
isDirectory: false,
isSymlink: false
}
{
path: "/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/.DS_Store",
name: ".DS_Store",
isFile: true,
isDirectory: false,
isSymlink: false
}
{
path: "/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/main.ts",
name: "main.ts",
isFile: true,
isDirectory: false,
isSymlink: false
}
{
path: "/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/LICENSE",
name: "LICENSE",
isFile: true,
isDirectory: false,
isSymlink: false
}
(略)
配下のファイルが一覧できている
walk()
はskip
の他にも以下のオプションを持つ:maxDepth, includeFiles, includeDirs,followSymlinks, exts, match
ということでこんなパラメータにする
-
type
-
dir
ディレクトリを含める -
file
ファイルを含める
-
-
depth
深度を指定 デフォルトは2
import { parse } from "https://deno.land/std@0.100.0/flags/mod.ts";
import { resolve } from "https://deno.land/std@0.100.0/path/mod.ts";
import { walk } from "https://deno.land/std@0.100.0/fs/mod.ts";
- const { _: [dir = "."] } = parse(Deno.args);
+ const { depth = "2", type, _: [dir = "."] } = parse(Deno.args);
+ const types = type ? (Array.isArray(type) ? type : [type]) : ["file", "dir"];
+ const options = {
+ maxDepth: Number(depth),
+ includeFiles: !types.includes("file"),
+ includeDirs: !types.includes("dir"),
+ skip: [/\.git$/],
+ };
const dirFullPath = resolve(Deno.cwd(), String(dir));
console.log(dirFullPath);
- for await (const entry of walk(dirFullPath, { skip: [/\.git$/] })) {
+ for await (const entry of walk(dirFullPath, options)) {
console.log(entry);
}
これで実行する
❯ deno run --allow-read --unstable main.ts
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground
deno-dev-playground/
.vim/
coc-settings.json
.DS_Store
main.ts
LICENSE
line_notify.ts
fetch_twilog.ts
tweet_with_ifttt.ts
README.md
logger.ts
dgc.ts
.gitignore
.env
examples/
line_notify.ts
tweet_with_ifttt.ts
rss.ts
zenn_api.ts
.github/
workflows/
.env.example
env.ts
app.log
assets/
deps.ts
rss.ts
promote_zenn_article.ts
velociraptor.yml
❯ deno run --allow-read --unstable main.ts --type=dir
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground
deno-dev-playground/
.vim/
examples/
.github/
workflows/
assets/
良さげだ
regex
パラメータも追加しよう
入力されたものを正規表現としてオプションに渡す
import { parse } from "https://deno.land/std@0.100.0/flags/mod.ts";
import { resolve } from "https://deno.land/std@0.100.0/path/mod.ts";
import { walk } from "https://deno.land/std@0.100.0/fs/mod.ts";
const { depth = "2", type, regex, _: [dir = "."] } = parse(Deno.args);
const types = type ? (Array.isArray(type) ? type : [type]) : ["file", "dir"];
+ const match = regex
+ ? (Array.isArray(regex) ? regex : [regex]).map(
+ (str) => new RegExp(str),
+ )
+ : undefined;
const options = {
maxDepth: Number(depth),
includeFiles: types.includes("file"),
includeDirs: types.includes("dir"),
+ match,
skip: [/\.git$/],
};
const dirFullPath = resolve(Deno.cwd(), String(dir));
console.log(dirFullPath);
for await (const entry of walk(dirFullPath, options)) {
console.log(entry.name + (entry.isDirectory ? "/" : ""));
}
❯ deno run --allow-read --unstable main.ts --regex=".*\.ts"
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground
main.ts
line_notify.ts
fetch_twilog.ts
tweet_with_ifttt.ts
logger.ts
dgc.ts
line_notify.ts
tweet_with_ifttt.ts
rss.ts
zenn_api.ts
env.ts
deps.ts
rss.ts
promote_zenn_article.ts
最後にヘルプも載せておこう
// (略)
- const { depth = "2", type, regex, _: [dir = "."] } = parse(Deno.args);
+ const { depth = "2", type, help, regex, _: [dir = "."] } = parse(Deno.args);
+ if (help) {
+ console.log("denofind");
+ console.log("Usage");
+ console.log(` denofind --type=file --regex="*.\.ts" --depth=3 target_dir`);
+ Deno.exit(0);
+ }
// (略)
❯ deno run --allow-read --unstable main.ts --help
denofind
Usage
denofind --type=file --regex="*..ts" --depth=3 target_dir
これtree
作れそうだな やってみよう
entry.path.replace(dirFullPath, ".")
という手もあるけどstd/path
にrelative
というのが定義されている
- import { resolve } from "https://deno.land/std@0.100.0/path/mod.ts";
+ import { relative, resolve } from "https://deno.land/std@0.100.0/path/mod.ts";
// (略)
const dirFullPath = resolve(Deno.cwd(), String(dir));
for await (const entry of walk(dirFullPath, options)) {
- console.log(entry.name + (entry.isDirectory ? "/" : ""));
+ const relativePath = relative(dirFullPath, entry.path) +
+ (entry.isDirectory ? "/" : "");
+ console.log(relativePath);
}
❯ vr start
/
.vim/
.vim/coc-settings.json
.DS_Store
main.ts
LICENSE
line_notify.ts
fetch_twilog.ts
tweet_with_ifttt.ts
README.md
logger.ts
.gitignore
.env
examples/
examples/line_notify.ts
examples/tweet_with_ifttt.ts
examples/rss.ts
examples/zenn_api.ts
.github/
.github/workflows/
.env.example
env.ts
app.log
assets/
deps.ts
rss.ts
promote_zenn_article.ts
velociraptor.yml
こちらを参考にこんな感じになった
//(前略)
const dirFullPath = resolve(Deno.cwd(), String(dir));
const entries: WalkEntry[] = [];
for await (const entry of walk(dirFullPath, options)) {
entries.push(entry);
}
entries.sort((a, b) => a.path > b.path ? 1 : -1).forEach((entry) => {
console.log(
entry.path === dirFullPath
? "."
: relative(dirFullPath, entry.path).replace(
entry.name,
"|--" + entry.name,
)
.replace(/[^\/]+\//g, "| "),
);
});
❯ vr start
.
|--.DS_Store
|--.env
|--.env.example
|--.github
| |--workflows
| | |--promote_zenn_article.yml
| | |--welcome-deno.yml
|--.gitignore
|--.vim
| |--coc-settings.json
|--LICENSE
|--README.md
|--app.log
|--assets
|--deep
| |--nested
| | |--dir
| | | |--hidden
| | | | |--file.ts
|--deps.ts
|--env.ts
|--examples
| |--line_notify.ts
| |--rss.ts
| |--tweet_with_ifttt.ts
| |--zenn_api.ts
|--fetch_twilog.ts
|--line_notify.ts
|--logger.ts
|--main.ts
|--promote_zenn_article.ts
|--rss.ts
|--tweet_with_ifttt.ts
|--velociraptor.yml
うーん簡易版だな、せっかくだしwalk
じゃなくてちゃんと再帰しよう
walkのソースを確認する
export async function* walk(
root: string,
{
maxDepth = Infinity,
includeFiles = true,
includeDirs = true,
followSymlinks = false,
exts = undefined,
match = undefined,
skip = undefined,
}: WalkOptions = {},
): AsyncIterableIterator<WalkEntry> {
if (maxDepth < 0) {
return;
}
if (includeDirs && include(root, exts, match, skip)) {
yield await _createWalkEntry(root);
}
if (maxDepth < 1 || !include(root, undefined, undefined, skip)) {
return;
}
try {
for await (const entry of Deno.readDir(root)) {
assert(entry.name != null);
let path = join(root, entry.name);
if (entry.isSymlink) {
if (followSymlinks) {
path = await Deno.realPath(path);
} else {
continue;
}
}
if (entry.isFile) {
if (includeFiles && include(path, exts, match, skip)) {
yield { path, ...entry };
}
} else {
yield* walk(path, {
maxDepth: maxDepth - 1,
includeFiles,
includeDirs,
followSymlinks,
exts,
match,
skip,
});
}
}
} catch (err) {
throw wrapErrorWithRootPath(err, normalize(root));
}
}
Deno.readDir
で現在のディレクトリの配下を表示し、そのうちディレクトリに関しては再帰している
途中のassert
の実装は以下 要するにfalsy
のときエラーを投げる
export class DenoStdInternalError extends Error {
constructor(message: string) {
super(message);
this.name = "DenoStdInternalError";
}
}
/** Make an assertion, if not `true`, then throw. */
export function assert(expr: unknown, msg = ""): asserts expr {
if (!expr) {
throw new DenoStdInternalError(msg);
}
}
今回tree
は見つけたファイルを再利用したいわけではなく単に表示したいのでジェネレータとして作る必要はない
単純に再帰関数で作ればよい
では実装しよう
公式のwalk
を読み込むとunstabled
フラグが必要なのでWalkEntry
も読み込まずTreeEntry
を作成
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";
// WalkEntry of https://deno.land/std@0.100.0/fs/mod.ts
export interface TreeEntry extends Deno.DirEntry {
path: string;
}
const tree = async (root: string) => {
const entries: TreeEntry[] = [];
for await (const entry of Deno.readDir(root)) {
const treeEntry = { ...entry, path: join(root, entry.name) };
entries.push(treeEntry);
}
for await (
const entry of entries.sort((a, b) =>
a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1
)
) {
console.log(entry.path);
}
};
const { _: [dir = "."] } = parse(Deno.args);
await tree(resolve(Deno.cwd(), String(dir)));
これで実行する
現在のディレクトリの配下の情報が一覧される
❯ deno run --allow-read tree.ts
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/.DS_Store
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/.env
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/.env.example
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/.git
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/.github
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/.gitignore
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/.vim
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/app.log
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/assets
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/deep
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/deps.ts
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/env.ts
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/examples
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/fetch_twilog.ts
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/LICENSE
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/line_notify.ts
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/logger.ts
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/main.ts
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/promote_zenn_article.ts
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/README.md
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/rss.ts
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/tree.ts
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/tweet_with_ifttt.ts
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/velociraptor.yml
まだ一段階しか見ていない
表示する際にそれが
- ファイルだったら名前を表示して次へ
- ディレクトリだったらそこをルートとして再帰
.git
ディレクトリを掘ってしまうと大変なので決め打ちで除外
(略)
console.log(entry.path);
+ if (entry.isDirectory && entry.name !== ".git") {
+ await tree(entry.path);
+ }
(略)
❯ deno run --allow-read tree.ts
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/.DS_Store
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/.env
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/.env.example
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/.git
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/.github
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/.github/workflows
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/.github/workflows/promote_zenn_article.yml
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/.github/workflows/welcome-deno.yml
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/.gitignore
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/.vim
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/.vim/coc-settings.json
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/app.log
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/assets
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/deep
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/deep/nested
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/deep/nested/dir
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/deep/nested/dir/hidden
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/deep/nested/dir/hidden/file.ts
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/deps.ts
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/env.ts
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/examples
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/examples/line_notify.ts
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/examples/rss.ts
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/examples/tweet_with_ifttt.ts
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/examples/zenn_api.ts
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/fetch_twilog.ts
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/LICENSE
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/line_notify.ts
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/logger.ts
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/main.ts
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/promote_zenn_article.ts
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/README.md
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/rss.ts
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/tree.ts
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/tweet_with_ifttt.ts
/Users/kawarimidoll/ghq/github.com/kawarimidoll/deno-dev-playground/velociraptor.yml
深い階層まで見ることができている
- フルパスではなく名前だけを表示するようにする
- 階層を掘るときに
prefix
を渡すことにする
(略)
- const tree = async (root: string) => {
+ const tree = async (root: string, prefix = "") => {
(略)
- console.log(entry.name);
+ console.log(prefix + entry.name);
if (entry.isDirectory && entry.name !== ".git") {
- await tree(entry.path);
+ await tree(entry.path, prefix + " ");
}
(略)
❯ deno run --allow-read tree.ts
.DS_Store
.env
.env.example
.git
.github
workflows
promote_zenn_article.yml
welcome-deno.yml
.gitignore
.vim
coc-settings.json
app.log
assets
deep
nested
dir
hidden
file.ts
deps.ts
env.ts
examples
line_notify.ts
rss.ts
tweet_with_ifttt.ts
zenn_api.ts
fetch_twilog.ts
LICENSE
line_notify.ts
logger.ts
main.ts
promote_zenn_article.ts
README.md
rss.ts
tree.ts
tweet_with_ifttt.ts
velociraptor.yml
階層に応じたインデントができている
階層に応じて罫線を表示させる
そのファイルがディレクトリ内の最後のファイルかどうかで出力を分岐させたいため事前にソートして最後のファイルを保存しておく必要がある
diffが面倒なのでtree()
を更新した形ですべて再掲
const tree = async (root: string, prefix = "") => {
const entries: TreeEntry[] = [];
for await (const entry of Deno.readDir(root)) {
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 ? "└── " : "├── ";
console.log(prefix + branch + entry.name);
if (entry.isDirectory && entry.name !== ".git") {
await tree(entry.path, prefix + " ");
}
}
};
❯ deno run --allow-read tree.ts
├── .DS_Store
├── .env
├── .env.example
├── .git
├── .github
└── workflows
├── promote_zenn_article.yml
└── welcome-deno.yml
├── .gitignore
├── .vim
└── coc-settings.json
├── app.log
├── assets
├── deep
└── nested
└── dir
└── hidden
└── file.ts
├── deps.ts
├── env.ts
├── examples
├── line_notify.ts
├── rss.ts
├── tweet_with_ifttt.ts
└── zenn_api.ts
├── fetch_twilog.ts
├── LICENSE
├── line_notify.ts
├── logger.ts
├── main.ts
├── promote_zenn_article.ts
├── README.md
├── rss.ts
├── tree.ts
├── tweet_with_ifttt.ts
└── velociraptor.yml
良さげ
prefix
を調整すれば途切れた枝をつなげることが可能
if (entry.isDirectory && entry.name !== ".git") {
+ const indent = entry === lastOne ? " " : "│ ";
- await tree(entry.path, prefix + " ");
+ await tree(entry.path, prefix + indent);
}
❯ deno run --allow-read tree.ts
├── .DS_Store
├── .env
├── .env.example
├── .git
├── .github
│ └── workflows
│ ├── promote_zenn_article.yml
│ └── welcome-deno.yml
├── .gitignore
├── .vim
│ └── coc-settings.json
├── app.log
├── assets
├── deep
│ └── nested
│ └── dir
│ └── hidden
│ └── file.ts
├── deps.ts
├── env.ts
├── examples
│ ├── line_notify.ts
│ ├── rss.ts
│ ├── tweet_with_ifttt.ts
│ └── zenn_api.ts
├── fetch_twilog.ts
├── LICENSE
├── line_notify.ts
├── logger.ts
├── main.ts
├── promote_zenn_article.ts
├── README.md
├── rss.ts
├── tree.ts
├── tweet_with_ifttt.ts
└── velociraptor.yml
つながった
オプションに対応しよう
walk.ts
より、オプションのインターフェースとそれに関する条件判断を追加
ディレクトリは必ず表示したいのでincludeDirs
は不要
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[],
): boolean {
if (exts && !exts.some((ext): boolean => path.endsWith(ext))) {
return false;
}
if (match && !match.some((pattern): boolean => !!path.match(pattern))) {
return false;
}
if (skip && skip.some((pattern): boolean => !!path.match(pattern))) {
return false;
}
return true;
}
オプションは標準のtreeを参考に
-
-a
でdotfiles表示(通常は表示しない) -
-d
でディレクトリのみ -
-L num
で階層を制限
またdiffが面倒なのでtree関数をすべて表示
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,
_: [dir = "."],
} = parse(Deno.args);
console.log(dir);
await tree(resolve(Deno.cwd(), String(dir)), "", {
maxDepth: L,
includeFiles: !d,
followSymlinks: false,
exts: undefined,
match: undefined,
skip: a ? undefined : [/(^|\/)\./],
});
結果は長くなるので省略
.gitignore
を読み込もう
Deno.run()
でgit status --ignored -s
を動かして無視されているファイルを探すことができる
なおこの中に.git
は含まれていないので手動で追加する
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");
console.log(ignoredList);
通常は.gitignore
を読み込んで対象ファイルと.git
を読み飛ばし、u
オプション(命名はripgrepを参考にした)を追加するとそれらを見るものとする
(略)
const {
a,
d,
L,
u,
_: [dir = "."],
} = parse(Deno.args);
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");
skip.concat(...ignoredList.map((str) => new RegExp(str)));
}
console.log(dir);
await tree(resolve(Deno.cwd(), String(dir)), "", {
maxDepth: L,
includeFiles: !d,
followSymlinks: false,
exts: undefined,
match: undefined,
skip,
});
実行時にはallow-run
オプションが必要
❯ deno run --allow-read --allow-run tree.ts -a -u
h
オプションでヘルプを追加 完璧だ
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);
}
published
Cliffy使いたいけどscrap長くなってきたので一旦closeする
別ページで勉強しよう