diff --git a/fs/README.md b/fs/README.md index 289069694e7e..8d1be54bf719 100644 --- a/fs/README.md +++ b/fs/README.md @@ -156,8 +156,8 @@ for (const fileInfo of walkSync(".")) { // Async async function printFilesNames() { - for await (const fileInfo of walk()) { - console.log(fileInfo.filename); + for await (const entry of walk()) { + console.log(entry.path); } } diff --git a/fs/copy.ts b/fs/copy.ts index 27b3c2a3cd37..d442e46ae06f 100644 --- a/fs/copy.ts +++ b/fs/copy.ts @@ -157,15 +157,15 @@ async function copyDir( await Deno.utime(dest, srcStatInfo.atime, srcStatInfo.mtime); } - for await (const file of Deno.readdir(src)) { - const srcPath = path.join(src, file.name); + for await (const entry of Deno.readdir(src)) { + const srcPath = path.join(src, entry.name); const destPath = path.join(dest, path.basename(srcPath as string)); - if (file.isDirectory) { + if (entry.isSymlink) { + await copySymLink(srcPath, destPath, options); + } else if (entry.isDirectory) { await copyDir(srcPath, destPath, options); - } else if (file.isFile) { + } else if (entry.isFile) { await copyFile(srcPath, destPath, options); - } else if (file.isSymlink) { - await copySymLink(srcPath, destPath, options); } } } @@ -185,16 +185,16 @@ function copyDirSync(src: string, dest: string, options: CopyOptions): void { Deno.utimeSync(dest, srcStatInfo.atime, srcStatInfo.mtime); } - for (const file of Deno.readdirSync(src)) { - assert(file.name != null, "file.name must be set"); - const srcPath = path.join(src, file.name); + for (const entry of Deno.readdirSync(src)) { + assert(entry.name != null, "file.name must be set"); + const srcPath = path.join(src, entry.name); const destPath = path.join(dest, path.basename(srcPath as string)); - if (file.isDirectory) { + if (entry.isSymlink) { + copySymlinkSync(srcPath, destPath, options); + } else if (entry.isDirectory) { copyDirSync(srcPath, destPath, options); - } else if (file.isFile) { + } else if (entry.isFile) { copyFileSync(srcPath, destPath, options); - } else if (file.isSymlink) { - copySymlinkSync(srcPath, destPath, options); } } } @@ -229,12 +229,12 @@ export async function copy( ); } - if (srcStat.isDirectory) { + if (srcStat.isSymlink) { + await copySymLink(src, dest, options); + } else if (srcStat.isDirectory) { await copyDir(src, dest, options); } else if (srcStat.isFile) { await copyFile(src, dest, options); - } else if (srcStat.isSymlink) { - await copySymLink(src, dest, options); } } @@ -268,11 +268,11 @@ export function copySync( ); } - if (srcStat.isDirectory) { + if (srcStat.isSymlink) { + copySymlinkSync(src, dest, options); + } else if (srcStat.isDirectory) { copyDirSync(src, dest, options); } else if (srcStat.isFile) { copyFileSync(src, dest, options); - } else if (srcStat.isSymlink) { - copySymlinkSync(src, dest, options); } } diff --git a/fs/expand_glob.ts b/fs/expand_glob.ts index 386f61ad58bf..803c67cdfb35 100644 --- a/fs/expand_glob.ts +++ b/fs/expand_glob.ts @@ -8,9 +8,15 @@ import { joinGlobs, normalize, } from "../path/mod.ts"; -import { WalkEntry, walk, walkSync } from "./walk.ts"; +import { + WalkEntry, + createWalkEntry, + createWalkEntrySync, + walk, + walkSync, +} from "./walk.ts"; import { assert } from "../testing/asserts.ts"; -const { cwd, stat, statSync } = Deno; +const { cwd } = Deno; type FileInfo = Deno.FileInfo; export interface ExpandGlobOptions extends GlobOptions { @@ -48,6 +54,12 @@ function throwUnlessNotFound(error: Error): void { } } +function comparePath(a: WalkEntry, b: WalkEntry): number { + if (a.path < b.path) return -1; + if (a.path > b.path) return 1; + return 0; +} + /** * Expand the glob string from the specified `root` directory and yield each * result as a `WalkEntry` object. @@ -73,8 +85,8 @@ export async function* expandGlob( const excludePatterns = exclude .map(resolveFromRoot) .map((s: string): RegExp => globToRegExp(s, globOptions)); - const shouldInclude = (filename: string): boolean => - !excludePatterns.some((p: RegExp): boolean => !!filename.match(p)); + const shouldInclude = (path: string): boolean => + !excludePatterns.some((p: RegExp): boolean => !!path.match(p)); const { segments, hasTrailingSep, winRoot } = split(resolveFromRoot(glob)); let fixedRoot = winRoot != undefined ? winRoot : "/"; @@ -86,7 +98,7 @@ export async function* expandGlob( let fixedRootInfo: WalkEntry; try { - fixedRootInfo = { filename: fixedRoot, info: await stat(fixedRoot) }; + fixedRootInfo = await createWalkEntry(fixedRoot); } catch (error) { return throwUnlessNotFound(error); } @@ -95,29 +107,29 @@ export async function* expandGlob( walkInfo: WalkEntry, globSegment: string ): AsyncIterableIterator { - if (!walkInfo.info.isDirectory) { + if (!walkInfo.isDirectory) { return; } else if (globSegment == "..") { - const parentPath = joinGlobs([walkInfo.filename, ".."], globOptions); + const parentPath = joinGlobs([walkInfo.path, ".."], globOptions); try { if (shouldInclude(parentPath)) { - return yield { filename: parentPath, info: await stat(parentPath) }; + return yield await createWalkEntry(parentPath); } } catch (error) { throwUnlessNotFound(error); } return; } else if (globSegment == "**") { - return yield* walk(walkInfo.filename, { + return yield* walk(walkInfo.path, { includeFiles: false, skip: excludePatterns, }); } - yield* walk(walkInfo.filename, { + yield* walk(walkInfo.path, { maxDepth: 1, match: [ globToRegExp( - joinGlobs([walkInfo.filename, globSegment], globOptions), + joinGlobs([walkInfo.path, globSegment], globOptions), globOptions ), ], @@ -129,27 +141,22 @@ export async function* expandGlob( for (const segment of segments) { // Advancing the list of current matches may introduce duplicates, so we // pass everything through this Map. - const nextMatchMap: Map = new Map(); + const nextMatchMap: Map = new Map(); for (const currentMatch of currentMatches) { for await (const nextMatch of advanceMatch(currentMatch, segment)) { - nextMatchMap.set(nextMatch.filename, nextMatch.info); + nextMatchMap.set(nextMatch.path, nextMatch); } } - currentMatches = [...nextMatchMap].sort().map( - ([filename, info]): WalkEntry => ({ - filename, - info, - }) - ); + currentMatches = [...nextMatchMap.values()].sort(comparePath); } if (hasTrailingSep) { currentMatches = currentMatches.filter( - ({ info }): boolean => info.isDirectory + (entry: WalkEntry): boolean => entry.isDirectory ); } if (!includeDirs) { currentMatches = currentMatches.filter( - ({ info }): boolean => !info.isDirectory + (entry: WalkEntry): boolean => !entry.isDirectory ); } yield* currentMatches; @@ -177,8 +184,8 @@ export function* expandGlobSync( const excludePatterns = exclude .map(resolveFromRoot) .map((s: string): RegExp => globToRegExp(s, globOptions)); - const shouldInclude = (filename: string): boolean => - !excludePatterns.some((p: RegExp): boolean => !!filename.match(p)); + const shouldInclude = (path: string): boolean => + !excludePatterns.some((p: RegExp): boolean => !!path.match(p)); const { segments, hasTrailingSep, winRoot } = split(resolveFromRoot(glob)); let fixedRoot = winRoot != undefined ? winRoot : "/"; @@ -190,7 +197,7 @@ export function* expandGlobSync( let fixedRootInfo: WalkEntry; try { - fixedRootInfo = { filename: fixedRoot, info: statSync(fixedRoot) }; + fixedRootInfo = createWalkEntrySync(fixedRoot); } catch (error) { return throwUnlessNotFound(error); } @@ -199,29 +206,29 @@ export function* expandGlobSync( walkInfo: WalkEntry, globSegment: string ): IterableIterator { - if (!walkInfo.info.isDirectory) { + if (!walkInfo.isDirectory) { return; } else if (globSegment == "..") { - const parentPath = joinGlobs([walkInfo.filename, ".."], globOptions); + const parentPath = joinGlobs([walkInfo.path, ".."], globOptions); try { if (shouldInclude(parentPath)) { - return yield { filename: parentPath, info: statSync(parentPath) }; + return yield createWalkEntrySync(parentPath); } } catch (error) { throwUnlessNotFound(error); } return; } else if (globSegment == "**") { - return yield* walkSync(walkInfo.filename, { + return yield* walkSync(walkInfo.path, { includeFiles: false, skip: excludePatterns, }); } - yield* walkSync(walkInfo.filename, { + yield* walkSync(walkInfo.path, { maxDepth: 1, match: [ globToRegExp( - joinGlobs([walkInfo.filename, globSegment], globOptions), + joinGlobs([walkInfo.path, globSegment], globOptions), globOptions ), ], @@ -233,27 +240,22 @@ export function* expandGlobSync( for (const segment of segments) { // Advancing the list of current matches may introduce duplicates, so we // pass everything through this Map. - const nextMatchMap: Map = new Map(); + const nextMatchMap: Map = new Map(); for (const currentMatch of currentMatches) { for (const nextMatch of advanceMatch(currentMatch, segment)) { - nextMatchMap.set(nextMatch.filename, nextMatch.info); + nextMatchMap.set(nextMatch.path, nextMatch); } } - currentMatches = [...nextMatchMap].sort().map( - ([filename, info]): WalkEntry => ({ - filename, - info, - }) - ); + currentMatches = [...nextMatchMap.values()].sort(comparePath); } if (hasTrailingSep) { currentMatches = currentMatches.filter( - ({ info }): boolean => info.isDirectory + (entry: WalkEntry): boolean => entry.isDirectory ); } if (!includeDirs) { currentMatches = currentMatches.filter( - ({ info }): boolean => !info.isDirectory + (entry: WalkEntry): boolean => !entry.isDirectory ); } yield* currentMatches; diff --git a/fs/expand_glob_test.ts b/fs/expand_glob_test.ts index a2e6b433391e..98abe0d732bc 100644 --- a/fs/expand_glob_test.ts +++ b/fs/expand_glob_test.ts @@ -19,12 +19,12 @@ async function expandGlobArray( options: ExpandGlobOptions ): Promise { const paths: string[] = []; - for await (const { filename } of expandGlob(globString, options)) { - paths.push(filename); + for await (const { path } of expandGlob(globString, options)) { + paths.push(path); } paths.sort(); const pathsSync = [...expandGlobSync(globString, options)].map( - ({ filename }): string => filename + ({ path }): string => path ); pathsSync.sort(); assertEquals(paths, pathsSync); diff --git a/fs/walk.ts b/fs/walk.ts index e4cf4674fe0a..a99d35a5098a 100644 --- a/fs/walk.ts +++ b/fs/walk.ts @@ -2,9 +2,35 @@ // https://golang.org/pkg/path/filepath/#Walk // Copyright 2009 The Go Authors. All rights reserved. BSD license. import { unimplemented, assert } from "../testing/asserts.ts"; -import { join } from "../path/mod.ts"; +import { basename, join, normalize } from "../path/mod.ts"; const { readdir, readdirSync, stat, statSync } = Deno; +export function createWalkEntrySync(path: string): WalkEntry { + path = normalize(path); + const name = basename(path); + const info = statSync(path); + return { + path, + name, + isFile: info.isFile, + isDirectory: info.isDirectory, + isSymlink: info.isSymlink, + }; +} + +export async function createWalkEntry(path: string): Promise { + path = normalize(path); + const name = basename(path); + const info = await stat(path); + return { + path, + name, + isFile: info.isFile, + isDirectory: info.isDirectory, + isSymlink: info.isSymlink, + }; +} + export interface WalkOptions { maxDepth?: number; includeFiles?: boolean; @@ -16,26 +42,25 @@ export interface WalkOptions { } function include( - filename: string, + path: string, exts?: string[], match?: RegExp[], skip?: RegExp[] ): boolean { - if (exts && !exts.some((ext): boolean => filename.endsWith(ext))) { + if (exts && !exts.some((ext): boolean => path.endsWith(ext))) { return false; } - if (match && !match.some((pattern): boolean => !!filename.match(pattern))) { + if (match && !match.some((pattern): boolean => !!path.match(pattern))) { return false; } - if (skip && skip.some((pattern): boolean => !!filename.match(pattern))) { + if (skip && skip.some((pattern): boolean => !!path.match(pattern))) { return false; } return true; } -export interface WalkEntry { - filename: string; - info: Deno.FileInfo; +export interface WalkEntry extends Deno.DirEntry { + path: string; } /** Walks the file tree rooted at root, yielding each file or directory in the @@ -52,8 +77,8 @@ export interface WalkEntry { * - match?: RegExp[]; * - skip?: RegExp[]; * - * for await (const { filename, info } of walk(".")) { - * console.log(filename); + * for await (const { name, info } of walk(".")) { + * console.log(name); * assert(info.isFile); * }; */ @@ -73,13 +98,13 @@ export async function* walk( return; } if (includeDirs && include(root, exts, match, skip)) { - yield { filename: root, info: await stat(root) }; + yield await createWalkEntry(root); } if (maxDepth < 1 || !include(root, undefined, undefined, skip)) { return; } - for await (const dirEntry of readdir(root)) { - if (dirEntry.isSymlink) { + for await (const entry of readdir(root)) { + if (entry.isSymlink) { if (followSymlinks) { // TODO(ry) Re-enable followSymlinks. unimplemented(); @@ -88,14 +113,15 @@ export async function* walk( } } - const filename = join(root, dirEntry.name); + assert(entry.name != null); + const path = join(root, entry.name); - if (dirEntry.isFile) { - if (includeFiles && include(filename, exts, match, skip)) { - yield { filename, info: dirEntry }; + if (entry.isFile) { + if (includeFiles && include(path, exts, match, skip)) { + yield { path, ...entry }; } } else { - yield* walk(filename, { + yield* walk(path, { maxDepth: maxDepth - 1, includeFiles, includeDirs, @@ -125,13 +151,13 @@ export function* walkSync( return; } if (includeDirs && include(root, exts, match, skip)) { - yield { filename: root, info: statSync(root) }; + yield createWalkEntrySync(root); } if (maxDepth < 1 || !include(root, undefined, undefined, skip)) { return; } - for (const dirEntry of readdirSync(root)) { - if (dirEntry.isSymlink) { + for (const entry of readdirSync(root)) { + if (entry.isSymlink) { if (followSymlinks) { unimplemented(); } else { @@ -139,15 +165,15 @@ export function* walkSync( } } - assert(dirEntry.name != null); - const filename = join(root, dirEntry.name); + assert(entry.name != null); + const path = join(root, entry.name); - if (dirEntry.isFile) { - if (includeFiles && include(filename, exts, match, skip)) { - yield { filename, info: dirEntry }; + if (entry.isFile) { + if (includeFiles && include(path, exts, match, skip)) { + yield { path, ...entry }; } } else { - yield* walkSync(filename, { + yield* walkSync(path, { maxDepth: maxDepth - 1, includeFiles, includeDirs, diff --git a/fs/walk_test.ts b/fs/walk_test.ts index ea9a33773bb2..8bd4577b954d 100644 --- a/fs/walk_test.ts +++ b/fs/walk_test.ts @@ -24,8 +24,8 @@ export function testWalk( Deno.test({ ignore, name: `[walk] ${name}`, fn }); } -function normalize({ filename }: WalkEntry): string { - return filename.replace(/\\/g, "/"); +function normalize({ path }: WalkEntry): string { + return path.replace(/\\/g, "/"); } export async function walkArray( diff --git a/http/file_server.ts b/http/file_server.ts index 90f8b8792530..c225dbef188b 100755 --- a/http/file_server.ts +++ b/http/file_server.ts @@ -140,22 +140,22 @@ async function serveDir( ): Promise { const dirUrl = `/${posix.relative(target, dirPath)}`; const listEntry: EntryInfo[] = []; - for await (const dirEntry of readdir(dirPath)) { - const filePath = posix.join(dirPath, dirEntry.name); - const fileUrl = posix.join(dirUrl, dirEntry.name); - if (dirEntry.name === "index.html" && dirEntry.isFile) { + for await (const entry of readdir(dirPath)) { + const filePath = posix.join(dirPath, entry.name); + const fileUrl = posix.join(dirUrl, entry.name); + if (entry.name === "index.html" && entry.isFile) { // in case index.html as dir... return serveFile(req, filePath); } // Yuck! - let mode = null; + let fileInfo = null; try { - mode = (await stat(filePath)).mode; + fileInfo = await stat(filePath); } catch (e) {} listEntry.push({ - mode: modeToString(dirEntry.isDirectory, mode), - size: dirEntry.isFile ? fileLenToString(dirEntry.size) : "", - name: dirEntry.name, + mode: modeToString(entry.isDirectory, fileInfo?.mode ?? null), + size: entry.isFile ? fileLenToString(fileInfo?.size ?? 0) : "", + name: entry.name, url: fileUrl, }); } @@ -331,8 +331,8 @@ function main(): void { let response: Response | undefined; try { - const info = await stat(fsPath); - if (info.isDirectory) { + const fileInfo = await stat(fsPath); + if (fileInfo.isDirectory) { response = await serveDir(req, fsPath); } else { response = await serveFile(req, fsPath); diff --git a/node/_fs/_fs_dirent.ts b/node/_fs/_fs_dirent.ts index 55fbad142814..3ea1def42a9c 100644 --- a/node/_fs/_fs_dirent.ts +++ b/node/_fs/_fs_dirent.ts @@ -4,11 +4,15 @@ export default class Dirent { constructor(private entry: Deno.DirEntry) {} isBlockDevice(): boolean { - return this.entry.blocks != null; + notImplemented("Deno does not yet support identification of block devices"); + return false; } isCharacterDevice(): boolean { - return this.entry.blocks == null; + notImplemented( + "Deno does not yet support identification of character devices" + ); + return false; } isDirectory(): boolean { diff --git a/node/_fs/_fs_dirent_test.ts b/node/_fs/_fs_dirent_test.ts index 5288cb3db178..43becedd1f1c 100644 --- a/node/_fs/_fs_dirent_test.ts +++ b/node/_fs/_fs_dirent_test.ts @@ -3,107 +3,74 @@ import { assert, assertEquals, assertThrows } from "../../testing/asserts.ts"; import Dirent from "./_fs_dirent.ts"; class DirEntryMock implements Deno.DirEntry { + name = ""; isFile = false; isDirectory = false; isSymlink = false; - size = -1; - mtime = new Date(-1); - atime = new Date(-1); - birthtime = new Date(-1); - name = ""; - dev = -1; - ino = -1; - mode = -1; - nlink = -1; - uid = -1; - gid = -1; - rdev = -1; - blksize = -1; - blocks: number | null = null; } -test({ - name: "Block devices are correctly identified", - fn() { - const fileInfo: DirEntryMock = new DirEntryMock(); - fileInfo.blocks = 5; - assert(new Dirent(fileInfo).isBlockDevice()); - assert(!new Dirent(fileInfo).isCharacterDevice()); - }, -}); - -test({ - name: "Character devices are correctly identified", - fn() { - const fileInfo: DirEntryMock = new DirEntryMock(); - fileInfo.blocks = null; - assert(new Dirent(fileInfo).isCharacterDevice()); - assert(!new Dirent(fileInfo).isBlockDevice()); - }, -}); - test({ name: "Directories are correctly identified", fn() { - const fileInfo: DirEntryMock = new DirEntryMock(); - fileInfo.isDirectory = true; - fileInfo.isFile = false; - fileInfo.isSymlink = false; - assert(new Dirent(fileInfo).isDirectory()); - assert(!new Dirent(fileInfo).isFile()); - assert(!new Dirent(fileInfo).isSymbolicLink()); + const entry: DirEntryMock = new DirEntryMock(); + entry.isDirectory = true; + entry.isFile = false; + entry.isSymlink = false; + assert(new Dirent(entry).isDirectory()); + assert(!new Dirent(entry).isFile()); + assert(!new Dirent(entry).isSymbolicLink()); }, }); test({ name: "Files are correctly identified", fn() { - const fileInfo: DirEntryMock = new DirEntryMock(); - fileInfo.isDirectory = false; - fileInfo.isFile = true; - fileInfo.isSymlink = false; - assert(!new Dirent(fileInfo).isDirectory()); - assert(new Dirent(fileInfo).isFile()); - assert(!new Dirent(fileInfo).isSymbolicLink()); + const entry: DirEntryMock = new DirEntryMock(); + entry.isDirectory = false; + entry.isFile = true; + entry.isSymlink = false; + assert(!new Dirent(entry).isDirectory()); + assert(new Dirent(entry).isFile()); + assert(!new Dirent(entry).isSymbolicLink()); }, }); test({ name: "Symlinks are correctly identified", fn() { - const fileInfo: DirEntryMock = new DirEntryMock(); - fileInfo.isDirectory = false; - fileInfo.isFile = false; - fileInfo.isSymlink = true; - assert(!new Dirent(fileInfo).isDirectory()); - assert(!new Dirent(fileInfo).isFile()); - assert(new Dirent(fileInfo).isSymbolicLink()); + const entry: DirEntryMock = new DirEntryMock(); + entry.isDirectory = false; + entry.isFile = false; + entry.isSymlink = true; + assert(!new Dirent(entry).isDirectory()); + assert(!new Dirent(entry).isFile()); + assert(new Dirent(entry).isSymbolicLink()); }, }); test({ name: "File name is correct", fn() { - const fileInfo: DirEntryMock = new DirEntryMock(); - fileInfo.name = "my_file"; - assertEquals(new Dirent(fileInfo).name, "my_file"); + const entry: DirEntryMock = new DirEntryMock(); + entry.name = "my_file"; + assertEquals(new Dirent(entry).name, "my_file"); }, }); test({ name: "Socket and FIFO pipes aren't yet available", fn() { - const fileInfo: DirEntryMock = new DirEntryMock(); + const entry: DirEntryMock = new DirEntryMock(); assertThrows( () => { - new Dirent(fileInfo).isFIFO(); + new Dirent(entry).isFIFO(); }, Error, "does not yet support" ); assertThrows( () => { - new Dirent(fileInfo).isSocket(); + new Dirent(entry).isSocket(); }, Error, "does not yet support"