Working with the File System in Deno


Working with the File System in Deno

In this article, we’ll build on our introduction to Deno by creating a command-line tool that can search for text within files and folders. We’ll use a range of API methods that Deno provides to read and write to the file system.

In our last installment, we used Deno to build a command-line tool to make requests to a third-party API. In this article, we’re going to leave the network to one side and build a tool that lets you search the file system for text in files and folders within your current directory — similar to tools like grep.

Note: we’re not building a tool that will be as optimized and efficient as grep, nor are we aiming to replace it! The aim of building a tool like this is to get familiar with Deno’s file system APIs.

Installing Deno

We’re going to assume that you’ve got Deno up and running on your machine locally. You can check the Deno website or the previous article for more detailed installation instructions and also to get information on how to add Deno support to your editor of choice.

At the time of writing, the latest stable version of Deno is 1.10.2, so that’s what I’m using in this article.

Setting Up Our New Command with Yargs

As in the previous article, we’ll use Yargs to build the interface that our users can use to execute our tool. Let’s create index.ts and populate it with the following:

import yargs from "https://deno.land/x/yargs@v17.0.1-deno/deno.ts";

interface Yargs<ArgvReturnType> {
  describe: (param: string, description: string) => Yargs<ArgvReturnType>;
  demandOption: (required: string[]) => Yargs<ArgvReturnType>;
  argv: ArgvReturnType;
}

interface UserArguments {
  text: string;
}

const userArguments: UserArguments =
  (yargs(Deno.args) as unknown as Yargs<UserArguments>)
    .describe("text", "the text to search for within the current directory")
    .demandOption(["text"])
    .argv;

console.log(userArguments);

There’s a fair bit going on here that’s worth pointing out:

  • We install Yargs by pointing to its path on the Deno repository. I explicitly use a precise version number to make sure we always get that version, so that we don’t end up using whatever happens to be the latest version when the script runs.
  • At the time of writing, the Deno + TypeScript experience for Yargs isn’t great, so I’ve created my own interface and used that to provide some type safety.
  • UserArguments contains all the inputs we’ll ask the user for. For now, we’re only going to ask for text, but in future we could expand this to provide a list of files to search for, rather than assuming the current directory.

We can run this with deno run index.ts and see our Yargs output:

$ 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

Now it’s time to get implementing!

Listing Files

Before we can start searching for text in a given file, we need to generate a list of directories and files to search within. Deno provides Deno.readdir, which is part of the “built-ins” library, meaning you don’t have to import it. It’s available for you on the global namespace.

Deno.readdir is asynchronous and returns a list of files and folders in the current directory. It returns these items as an AsyncIterator, which means we have to use the for await ... of loop to get at the results:

for await (const fileOrFolder of Deno.readDir(Deno.cwd())) {
  console.log(fileOrFolder);
}

This code will read from the current working directory (which Deno.cwd() gives us) and log each result. However, if you try to run the script now, you’ll get an error:

$ deno run index.ts --text='foo'
error: Uncaught PermissionDenied: Requires read access to <CWD>, run again with the --allow-read flag
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

Remember that Deno requires all scripts to be explicitly given permissions to read from the file system. In our case, the --allow-read flag will enable our code to run:

~/$ 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 }

In this case, I’m running the script in the directory where I’m building our 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, as we need to find all the files within the directory, not just the top level ones. Additionally, we can add some common ignores. I don’t think anyone will want the script to search the entire .git folder!

In the code below, we’ve created the getFilesList function, which takes a directory and returns all files in that directory. If it encounters a directory, it will recursively call itself to find any nested files, and return the result:

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(
        `${directory}/${fileOrFolder.name}`,
      );
      foundFiles.push(...nestedFiles);
    } else {
      // We found a file, so store it.
      foundFiles.push(`${directory}/${fileOrFolder.name}`);
    }
  }
  return foundFiles;
}

We can then use this like so:

const files = await getFilesList(Deno.cwd());
console.log(files);

We also get some output that looks good:

$ 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"
]

Using the path Module

We’re could now combine file paths with template strings like so:

`${directory}/${fileOrFolder.name}`,

But it would be nicer to do this using Deno’s path module. This module is one of the modules that Deno provides as part of its standard library (much like Node does with its path module), and if you’ve used Node’s path module the code will look very similar. At the time of writing, the latest version of the std library Deno provides is 0.97.0, and we import the path module from the mod.ts file:

import * as path from "https://deno.land/std@0.97.0/path/mod.ts";

mod.ts is always the entrypoint when importing Deno’s standard modules. The documentation for this module lives on the Deno site and lists path.join, which will take multiple paths and join them into one path. Let’s import and use that function rather than manually combining them:

// import added to the top of our script
import yargs from "https://deno.land/x/yargs@v17.0.1-deno/deno.ts";
import * as path from "https://deno.land/std@0.97.0/path/mod.ts";

// update our usages of the function:
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;
}

When using the standard library, it’s vital that you remember to pin to a specific version. Without doing so, your code will always load the latest version, even if that contains changes that will break your code. The Deno docs on the standard library go into this further, and I recommend giving that page a read.

Continue reading
Working with the File System in Deno
on SitePoint.

This article was republished from its original source.
Call Us: 1(800)730-2416

Pixeldust is a 20-year-old web development agency specializing in Drupal and WordPress and working with clients all over the country. With our best in class capabilities, we work with small businesses and fortune 500 companies alike. Give us a call at 1(800)730-2416 and let’s talk about your project.

FREE Drupal SEO Audit

Test your site below to see which issues need to be fixed. We will fix them and optimize your Drupal site 100% for Google and Bing. (Allow 30-60 seconds to gather data.)

Powered by

Working with the File System in Deno

On-Site Drupal SEO Master Setup

We make sure your site is 100% optimized (and stays that way) for the best SEO results.

With Pixeldust On-site (or On-page) SEO we make changes to your site’s structure and performance to make it easier for search engines to see and understand your site’s content. Search engines use algorithms to rank sites by degrees of relevance. Our on-site optimization ensures your site is configured to provide information in a way that meets Google and Bing standards for optimal indexing.

This service includes:

  • Pathauto install and configuration for SEO-friendly URLs.
  • Meta Tags install and configuration with dynamic tokens for meta titles and descriptions for all content types.
  • Install and fix all issues on the SEO checklist module.
  • Install and configure XML sitemap module and submit sitemaps.
  • Install and configure Google Analytics Module.
  • Install and configure Yoast.
  • Install and configure the Advanced Aggregation module to improve performance by minifying and merging CSS and JS.
  • Install and configure Schema.org Metatag.
  • Configure robots.txt.
  • Google Search Console setup snd configuration.
  • Find & Fix H1 tags.
  • Find and fix duplicate/missing meta descriptions.
  • Find and fix duplicate title tags.
  • Improve title, meta tags, and site descriptions.
  • Optimize images for better search engine optimization. Automate where possible.
  • Find and fix the missing alt and title tag for all images. Automate where possible.
  • The project takes 1 week to complete.