6.031
6.031 — Software Construction
Fall 2021

Code Examples for Reading 16

Download

You can also download a ZIP file containing this code.

Seems to be necessary to have a triple-backtick block at the start of this page,
otherwise all the pre/code tags below aren't syntax highlighted.
So create a hidden one.

Words1.ts

import fs from 'fs';
import path from 'path';

/**
 * Words example: familiar TS implementation
 */

/**
 * Find names of all files in the filesystem subtree rooted at folder.
 * @param folder root of subtree, requires fs.lstatSync(folder).isDirectory() === true
 * @returns list of names of all ordinary files (not folders) that have folder as
 *         their ancestor
 */
function allFilesIn(folder:string):Array<string> {
    let files:Array<string> = [];
    for (let child of fs.readdirSync(folder)) {
        const fullNameOfChild = path.join(folder, child);
        if (fs.lstatSync(fullNameOfChild).isDirectory()) {
            files = files.concat(allFilesIn(fullNameOfChild));
        } else if (fs.lstatSync(fullNameOfChild).isFile()) {
            files.push(fullNameOfChild);
        }
    }
    return files;
}

/**
 * Filter a list of files to those that end with suffix.
 * @param filenames list of filenames
 * @param suffix string to test
 * @returns a new list consisting of only those files whose names end with suffix
 */
function onlyFilesWithSuffix(filenames:Array<string>, suffix:string):Array<string> {
    let result:Array<string> = [];
    for (let f of filenames) {
        if (f.endsWith(suffix)) {
            result.push(f);
        }
    }
    return result;
}

/**
 * Find all the non-word-character-separated words in files.
 * @param filenames list of files (all non-null)
 * @returns list of words from all the files
 * @throws IOException if an error occurs reading a file
 */
function getWords(filenames:Array<string>):Array<string> {
    let words:Array<string> = [];
    for (let f of filenames) {
        const data = fs.readFileSync(f, { encoding: "utf8", flag: "r" });
        const lines = data.split(/\r?\n/);
        for (let line of lines) {
            // split on \W (non-word characters, like spaces and punctuation)
            for (let word of line.split(/\W+/)) {
                // split can return empty strings, so omit them
                if (word.length > 0) {
                    words.push(word);
                }
            }
        }
    }
    return words;
}


let allFiles = allFilesIn(".");
let tsFiles = onlyFilesWithSuffix(allFiles, ".ts");
let words = getWords(tsFiles);
for (let s of words) { console.log(s); }

Words2.ts

import fs from 'fs';
import path from 'path';

/**
 * Words example: TS implementation with map/filter/reduce
 */

/**
 * Find all files in the filesystem subtree rooted at folder.
 * @param folder root of subtree, requires folder.isDirectory() == true
 * @returns stream of all ordinary files (not folders) that have folder as
 *         their ancestor
 */
function allFilesIn(folder:string):Array<string> {
    let children: Array<string> =
        fs.readdirSync(folder)
        .map(f => path.join(folder, f));
    let descendants: Array<string> = children
                                    .filter(f => fs.lstatSync(f).isDirectory())
                                    .flatMap(allFilesIn);
    return [
        ...descendants,
        ...children.filter(f => fs.lstatSync(f).isFile())
    ];
}

/**
 * Make a filename suffix testing predicate.
 * @param suffix string to test
 * @returns a predicate that returns true iff the filename ends with suffix.
 */
function endsWith(suffix:string): (filename:string) => boolean {
    return (filename: string) => filename.endsWith(suffix);
}

let filenames: Array<string> = allFilesIn(".")
                      .filter(s => s.endsWith(".ts"));
let fileContents: Array<Array<string>> = filenames.map(f => { 
    const data = fs.readFileSync(f, { encoding: "utf8", flag: "r" });
    return data.split(/\r?\n/);
});
let lines: Array<string> = fileContents.flatMap(x => x);
let words: Array<string> = lines.flatMap(line => line.split(/\W+/)
                                             .filter(s => s.length > 0));
words.forEach(s => console.log(s));