This article will dive into Deno and create a command line tool for searching for text in files and folders. We will use various API methods provided by Deno to read and write to the file system.
In the previous article, we built a command line tool using Deno to make requests to third-party APIs. In this article, we will temporarily ignore network operations and build a tool that allows you to search for text in files and folders within the current directory - tools like grep.
Note: The tools we build are not as optimized and efficient as grep, and we are not aiming to replace it! The purpose of building such a tool is to be familiar with Deno's file system API.
Deno.readDir
for listing files and Deno.readTextFile
for reading file contents, thus simplifying file system interaction without additional imports. path.join
to connect file paths. –allow-read
or –allow-write
to perform file system operations, which enhances security by controlling script functions. deno compile
to compile Deno scripts into separate executables, simplifying distribution and execution by encapsulating the necessary permissions. We assume that you have Deno running on your local machine. You can check out the Deno website or the previous article for more detailed installation instructions and information on how to add Deno support to your editor.
At the time of writing, the latest stable version of Deno is 1.10.2, which is the version I use in this article.
For reference, you can find the full code for this article on GitHub.
As in the previous post, we will use Yargs to build interfaces that users can use to execute our tools. Let's create index.ts and fill it with the following:
import yargs from "https://deno.land/x/yargs@v17.0.1-deno/deno.ts"; interface Yargs<argvreturntype></argvreturntype> { describe: (param: string, description: string) => Yargs<argvreturntype>; </argvreturntype> demandOption: (required: string[]) => Yargs<argvreturntype>; </argvreturntype> argv: ArgvReturnType; } interface UserArguments { text: string; } const userArguments: UserArguments = (yargs(Deno.args) as unknown as Yargs<userarguments>) </userarguments> .describe("text", "the text to search for within the current directory") .demandOption(["text"]) .argv; console.log(userArguments);
There are a lot to point out here:
We can run it with deno run index.ts and see our Yargs output:
import yargs from "https://deno.land/x/yargs@v17.0.1-deno/deno.ts"; interface Yargs<argvreturntype></argvreturntype> { describe: (param: string, description: string) => Yargs<argvreturntype>; </argvreturntype> demandOption: (required: string[]) => Yargs<argvreturntype>; </argvreturntype> argv: ArgvReturnType; } interface UserArguments { text: string; } const userArguments: UserArguments = (yargs(Deno.args) as unknown as Yargs<userarguments>) </userarguments> .describe("text", "the text to search for within the current directory") .demandOption(["text"]) .argv; console.log(userArguments);
It's time to start implementing it!
Before we start searching for text in a given file, we need to generate a directory and list of files to search for. Deno provides Deno.readdir, which is part of the "built-in" library, which means you don't have to import it. It is available in the global namespace.
Deno.readdir is asynchronous and returns a list of files and folders in the current directory. It returns these items as AsyncIterator, which means we have to use for await ... of loop to get the result:
$ deno run index.ts Check file:///home/jack/git/deno-file-search/index.ts Options: --help Show help [boolean] --version Show version number [boolean] --text the text to search for within the current directory [required] Missing required argument: text
This code will read and record each result from the current working directory (provided by Deno.cwd()). However, if you try to run the script now, you will receive an error:
for await (const fileOrFolder of Deno.readDir(Deno.cwd())) { console.log(fileOrFolder); }
Remember that Deno requires that all scripts explicitly obtain permissions to read from the file system. In our case, the --allow-read
flag will enable our code to run:
$ deno run index.ts --text='foo' error: Uncaught PermissionDenied: Requires read access to <cwd>, run again with the --allow-read flag </cwd>for await (const fileOrFolder of Deno.readDir(Deno.cwd())) { ^ at deno:core/core.js:86:46 at unwrapOpResult (deno:core/core.js:106:13) at Object.opSync (deno:core/core.js:120:12) at Object.cwd (deno:runtime/js/30_fs.js:57:17) at file:///home/jack/git/deno-file-search/index.ts:19:52
In this case, I run the script in the directory of the build tool, so it finds the TS source code, the .git repository, and the .vscode folder. Let's start writing some functions to recursively navigate this structure, because we need to find all the files in the directory, not just the top-level files. Additionally, we can add some common ignores. I don't think anyone would want the script to search the entire .git folder!
In the following code, we create the getFilesList function, which takes a directory and returns all files in that directory. If a directory is encountered, it will recursively call itself to find any nested files and return the result:
~/$ deno run --allow-read index.ts --text='foo' { name: ".git", isFile: false, isDirectory: true, isSymlink: false } { name: ".vscode", isFile: false, isDirectory: true, isSymlink: false } { name: "index.ts", isFile: true, isDirectory: false, isSymlink: false }
Then we can use it like this:
const IGNORED_DIRECTORIES = new Set([".git"]); async function getFilesList( directory: string, ): Promise<string[]> { const foundFiles: string[] = []; for await (const fileOrFolder of Deno.readDir(directory)) { if (fileOrFolder.isDirectory) { if (IGNORED_DIRECTORIES.has(fileOrFolder.name)) { // Skip this folder, it's in the ignore list. continue; } // If it's not ignored, recurse and search this folder for files. const nestedFiles = await getFilesList( path.join(directory, fileOrFolder.name), ); foundFiles.push(...nestedFiles); } else { // We found a file, so store it. foundFiles.push(path.join(directory, fileOrFolder.name)); } } return foundFiles; }
We also get some output that looks good:
const files = await getFilesList(Deno.cwd()); console.log(files);
We can now combine file paths using template strings as follows:
$ deno run --allow-read index.ts --text='foo' [ "/home/jack/git/deno-file-search/.vscode/settings.json", "/home/jack/git/deno-file-search/index.ts" ]
But this would be better with Deno's path module. This module is one of the modules Deno offers as part of its standard library (very similar to Node using its path module), and if you have used Node's path module, the code looks very similar. At the time of writing, the latest standard library version provided by Deno is 0.97.0, we import the path module from the mod.ts file:
import yargs from "https://deno.land/x/yargs@v17.0.1-deno/deno.ts"; interface Yargs<argvreturntype></argvreturntype> { describe: (param: string, description: string) => Yargs<argvreturntype>; </argvreturntype> demandOption: (required: string[]) => Yargs<argvreturntype>; </argvreturntype> argv: ArgvReturnType; } interface UserArguments { text: string; } const userArguments: UserArguments = (yargs(Deno.args) as unknown as Yargs<userarguments>) </userarguments> .describe("text", "the text to search for within the current directory") .demandOption(["text"]) .argv; console.log(userArguments);
mod.ts is always the entry point when importing Deno's standard modules. The documentation for this module is located on the Deno website and lists path.join, which will take multiple paths and join them into one path. Let's import and use the functions instead of combining them manually:
...(The rest of the code is omitted here because it is repeated with the original text and has been modified and optimized in the previous output.)
...(The rest of the code is omitted because it's repetitive from the original and has already been modified and optimized in the previous output.)
: : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :The above is the detailed content of Working with the File System in Deno. For more information, please follow other related articles on the PHP Chinese website!