mirror of
https://github.com/denoland/deno.git
synced 2024-11-25 15:29:32 -05:00
BREAKING: Include limited metadata in 'DirEntry' objects (#4941)
This change is to prevent needed a separate stat syscall for each file when using readdir. For consistency, this PR also modifies std's `WalkEntry` interface to extend `DirEntry` with an additional `path` field.
This commit is contained in:
parent
721a4ad59d
commit
3e6ea62841
14 changed files with 193 additions and 206 deletions
5
cli/js/lib.deno.ns.d.ts
vendored
5
cli/js/lib.deno.ns.d.ts
vendored
|
@ -1386,8 +1386,11 @@ declare namespace Deno {
|
|||
* Requires `allow-read` permission. */
|
||||
export function realpath(path: string): Promise<string>;
|
||||
|
||||
export interface DirEntry extends FileInfo {
|
||||
export interface DirEntry {
|
||||
name: string;
|
||||
isFile: boolean;
|
||||
isDirectory: boolean;
|
||||
isSymlink: boolean;
|
||||
}
|
||||
|
||||
/** Synchronously reads the directory given by `path` and returns an iterable
|
||||
|
|
|
@ -1,21 +1,19 @@
|
|||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||
import { sendSync, sendAsync } from "../dispatch_json.ts";
|
||||
import { FileInfo, StatResponse, parseFileInfo } from "./stat.ts";
|
||||
|
||||
export interface DirEntry extends FileInfo {
|
||||
export interface DirEntry {
|
||||
name: string;
|
||||
isFile: boolean;
|
||||
isDirectory: boolean;
|
||||
isSymlink: boolean;
|
||||
}
|
||||
|
||||
interface ReadDirResponse {
|
||||
entries: StatResponse[];
|
||||
entries: DirEntry[];
|
||||
}
|
||||
|
||||
function res(response: ReadDirResponse): DirEntry[] {
|
||||
return response.entries.map(
|
||||
(statRes: StatResponse): DirEntry => {
|
||||
return { ...parseFileInfo(statRes), name: statRes.name! };
|
||||
}
|
||||
);
|
||||
return response.entries;
|
||||
}
|
||||
|
||||
export function readdirSync(path: string): Iterable<DirEntry> {
|
||||
|
|
|
@ -29,8 +29,6 @@ export interface StatResponse {
|
|||
mtime: number | null;
|
||||
atime: number | null;
|
||||
birthtime: number | null;
|
||||
// Null for stat(), but exists for readdir().
|
||||
name: string | null;
|
||||
// Unix only members
|
||||
dev: number;
|
||||
ino: number;
|
||||
|
|
|
@ -4,19 +4,14 @@ import { unitTest, assert, assertEquals } from "./test_util.ts";
|
|||
function assertSameContent(files: Deno.DirEntry[]): void {
|
||||
let counter = 0;
|
||||
|
||||
for (const file of files) {
|
||||
if (file.name === "subdir") {
|
||||
assert(file.isDirectory);
|
||||
counter++;
|
||||
}
|
||||
|
||||
if (file.name === "002_hello.ts") {
|
||||
assertEquals(file.mode!, Deno.statSync(`cli/tests/${file.name}`).mode!);
|
||||
for (const entry of files) {
|
||||
if (entry.name === "subdir") {
|
||||
assert(entry.isDirectory);
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals(counter, 2);
|
||||
assertEquals(counter, 1);
|
||||
}
|
||||
|
||||
unitTest({ perms: { read: true } }, function readdirSyncSuccess(): void {
|
||||
|
|
|
@ -460,10 +460,7 @@ fn to_msec(maybe_time: Result<SystemTime, io::Error>) -> serde_json::Value {
|
|||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn get_stat_json(
|
||||
metadata: std::fs::Metadata,
|
||||
maybe_name: Option<String>,
|
||||
) -> JsonResult {
|
||||
fn get_stat_json(metadata: std::fs::Metadata) -> JsonResult {
|
||||
// Unix stat member (number types only). 0 if not on unix.
|
||||
macro_rules! usm {
|
||||
($member: ident) => {{
|
||||
|
@ -480,7 +477,7 @@ fn get_stat_json(
|
|||
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
let mut json_val = json!({
|
||||
let json_val = json!({
|
||||
"isFile": metadata.is_file(),
|
||||
"isDirectory": metadata.is_dir(),
|
||||
"isSymlink": metadata.file_type().is_symlink(),
|
||||
|
@ -502,14 +499,6 @@ fn get_stat_json(
|
|||
"blksize": usm!(blksize),
|
||||
"blocks": usm!(blocks),
|
||||
});
|
||||
|
||||
// "name" is an optional field by our design.
|
||||
if let Some(name) = maybe_name {
|
||||
if let serde_json::Value::Object(ref mut m) = json_val {
|
||||
m.insert("name".to_owned(), json!(name));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(json_val)
|
||||
}
|
||||
|
||||
|
@ -540,7 +529,7 @@ fn op_stat(
|
|||
} else {
|
||||
std::fs::metadata(&path)?
|
||||
};
|
||||
get_stat_json(metadata, None)
|
||||
get_stat_json(metadata)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -599,10 +588,15 @@ fn op_read_dir(
|
|||
let entries: Vec<_> = std::fs::read_dir(path)?
|
||||
.filter_map(|entry| {
|
||||
let entry = entry.unwrap();
|
||||
let metadata = entry.metadata().unwrap();
|
||||
let file_type = entry.file_type().unwrap();
|
||||
// Not all filenames can be encoded as UTF-8. Skip those for now.
|
||||
if let Ok(filename) = into_string(entry.file_name()) {
|
||||
Some(get_stat_json(metadata, Some(filename)).unwrap())
|
||||
if let Ok(name) = into_string(entry.file_name()) {
|
||||
Some(json!({
|
||||
"name": name,
|
||||
"isFile": file_type.is_file(),
|
||||
"isDirectory": file_type.is_dir(),
|
||||
"isSymlink": file_type.is_symlink()
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
await copyDir(srcPath, destPath, options);
|
||||
} else if (file.isFile) {
|
||||
await copyFile(srcPath, destPath, options);
|
||||
} else if (file.isSymlink) {
|
||||
if (entry.isSymlink) {
|
||||
await copySymLink(srcPath, destPath, options);
|
||||
} else if (entry.isDirectory) {
|
||||
await copyDir(srcPath, destPath, options);
|
||||
} else if (entry.isFile) {
|
||||
await copyFile(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) {
|
||||
copyDirSync(srcPath, destPath, options);
|
||||
} else if (file.isFile) {
|
||||
copyFileSync(srcPath, destPath, options);
|
||||
} else if (file.isSymlink) {
|
||||
if (entry.isSymlink) {
|
||||
copySymlinkSync(srcPath, destPath, options);
|
||||
} else if (entry.isDirectory) {
|
||||
copyDirSync(srcPath, destPath, options);
|
||||
} else if (entry.isFile) {
|
||||
copyFileSync(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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<WalkEntry> {
|
||||
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<string, FileInfo> = new Map();
|
||||
const nextMatchMap: Map<string, WalkEntry> = 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<WalkEntry> {
|
||||
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<string, FileInfo> = new Map();
|
||||
const nextMatchMap: Map<string, WalkEntry> = 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;
|
||||
|
|
|
@ -19,12 +19,12 @@ async function expandGlobArray(
|
|||
options: ExpandGlobOptions
|
||||
): Promise<string[]> {
|
||||
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);
|
||||
|
|
|
@ -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<WalkEntry> {
|
||||
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,
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -140,22 +140,22 @@ async function serveDir(
|
|||
): Promise<Response> {
|
||||
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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in a new issue