1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-01-08 15:19:40 -05:00

Implement deno.stat() and deno.lstat()

This commit is contained in:
Sajjad Hashemian 2018-09-12 00:08:53 +04:30 committed by Ryan Dahl
parent 806385543c
commit 7c50c11f40
9 changed files with 284 additions and 185 deletions

View file

@ -198,6 +198,7 @@ run_node("gen_declarations") {
"js/mkdir.ts",
"js/os.ts",
"js/read_file.ts",
"js/stat.ts",
"js/text_encoding.ts",
"js/timers.ts",
"js/tsconfig.generated.json",
@ -238,6 +239,7 @@ run_node("bundle") {
"js/os.ts",
"js/plugins.d.ts",
"js/read_file.ts",
"js/stat.ts",
"js/text_encoding.ts",
"js/timers.ts",
"js/types.ts",

View file

@ -4,14 +4,12 @@
export {
env,
exit,
FileInfo,
makeTempDirSync,
renameSync,
statSync,
lstatSync
} from "./os";
export { mkdirSync, mkdir } from "./mkdir";
export { readFileSync, readFile } from "./read_file";
export { FileInfo, statSync, lstatSync, stat, lstat } from "./stat";
export { writeFileSync, writeFile } from "./write_file";
export { ErrorKind, DenoError } from "./errors";
export { libdeno } from "./libdeno";

109
js/os.ts
View file

@ -165,115 +165,6 @@ export function env(): { [index: string]: string } {
return createEnv(res);
}
/**
* A FileInfo describes a file and is returned by `stat`, `lstat`,
* `statSync`, `lstatSync`.
*/
export class FileInfo {
private readonly _isFile: boolean;
private readonly _isSymlink: boolean;
/** The size of the file, in bytes. */
len: number;
/**
* The last modification time of the file. This corresponds to the `mtime`
* field from `stat` on Unix and `ftLastWriteTime` on Windows. This may not
* be available on all platforms.
*/
modified: number | null;
/**
* The last access time of the file. This corresponds to the `atime`
* field from `stat` on Unix and `ftLastAccessTime` on Windows. This may not
* be available on all platforms.
*/
accessed: number | null;
/**
* The last access time of the file. This corresponds to the `birthtime`
* field from `stat` on Unix and `ftCreationTime` on Windows. This may not
* be available on all platforms.
*/
created: number | null;
/* @internal */
constructor(private _msg: fbs.StatSyncRes) {
const modified = this._msg.modified().toFloat64();
const accessed = this._msg.accessed().toFloat64();
const created = this._msg.created().toFloat64();
this._isFile = this._msg.isFile();
this._isSymlink = this._msg.isSymlink();
this.len = this._msg.len().toFloat64();
this.modified = modified ? modified : null;
this.accessed = accessed ? accessed : null;
this.created = created ? created : null;
}
/**
* Returns whether this is info for a regular file. This result is mutually
* exclusive to `FileInfo.isDirectory` and `FileInfo.isSymlink`.
*/
isFile() {
return this._isFile;
}
/**
* Returns whether this is info for a regular directory. This result is
* mutually exclusive to `FileInfo.isFile` and `FileInfo.isSymlink`.
*/
isDirectory() {
return !this._isFile && !this._isSymlink;
}
/**
* Returns whether this is info for a symlink. This result is
* mutually exclusive to `FileInfo.isFile` and `FileInfo.isDirectory`.
*/
isSymlink() {
return this._isSymlink;
}
}
/**
* Queries the file system for information on the path provided.
* If the given path is a symlink information about the symlink will
* be returned.
* @returns FileInfo
*/
export function lstatSync(filename: string): FileInfo {
return statSyncInner(filename, true);
}
/**
* Queries the file system for information on the path provided.
* `statSync` Will always follow symlinks.
* @returns FileInfo
*/
export function statSync(filename: string): FileInfo {
return statSyncInner(filename, false);
}
function statSyncInner(filename: string, lstat: boolean): FileInfo {
/* Ideally we could write
const res = sendSync({
command: fbs.Command.STAT_FILE_SYNC,
StatFilename: filename,
StatLStat: lstat,
});
return new FileInfo(res);
*/
const builder = new flatbuffers.Builder();
const filename_ = builder.createString(filename);
fbs.StatSync.startStatSync(builder);
fbs.StatSync.addFilename(builder, filename_);
fbs.StatSync.addLstat(builder, lstat);
const msg = fbs.StatSync.endStatSync(builder);
const baseRes = sendSync(builder, fbs.Any.StatSync, msg);
assert(baseRes != null);
assert(fbs.Any.StatSyncRes === baseRes!.msgType());
const res = new fbs.StatSyncRes();
assert(baseRes!.msg(res) != null);
return new FileInfo(res);
}
/**
* Renames (moves) oldpath to newpath.
* import { renameSync } from "deno";

View file

@ -23,68 +23,6 @@ test(async function envFailure() {
assert(caughtError);
});
// TODO Add tests for modified, accessed, and created fields once there is a way
// to create temp files.
test(async function statSyncSuccess() {
const packageInfo = deno.statSync("package.json");
assert(packageInfo.isFile());
assert(!packageInfo.isSymlink());
const testingInfo = deno.statSync("testing");
assert(testingInfo.isDirectory());
assert(!testingInfo.isSymlink());
const srcInfo = deno.statSync("src");
assert(srcInfo.isDirectory());
assert(!srcInfo.isSymlink());
});
test(async function statSyncNotFound() {
let caughtError = false;
let badInfo;
try {
badInfo = deno.statSync("bad_file_name");
} catch (err) {
caughtError = true;
assertEqual(err.kind, deno.ErrorKind.NotFound);
assertEqual(err.name, "NotFound");
}
assert(caughtError);
assertEqual(badInfo, undefined);
});
test(async function lstatSyncSuccess() {
const packageInfo = deno.lstatSync("package.json");
assert(packageInfo.isFile());
assert(!packageInfo.isSymlink());
const testingInfo = deno.lstatSync("testing");
assert(!testingInfo.isDirectory());
assert(testingInfo.isSymlink());
const srcInfo = deno.lstatSync("src");
assert(srcInfo.isDirectory());
assert(!srcInfo.isSymlink());
});
test(async function lstatSyncNotFound() {
let caughtError = false;
let badInfo;
try {
badInfo = deno.lstatSync("bad_file_name");
} catch (err) {
caughtError = true;
assertEqual(err.kind, deno.ErrorKind.NotFound);
assertEqual(err.name, "NotFound");
}
assert(caughtError);
assertEqual(badInfo, undefined);
});
testPerm({ write: true }, function makeTempDirSync() {
const dir1 = deno.makeTempDirSync({ prefix: "hello", suffix: "world" });
const dir2 = deno.makeTempDirSync({ prefix: "hello", suffix: "world" });

143
js/stat.ts Normal file
View file

@ -0,0 +1,143 @@
// Copyright 2018 the Deno authors. All rights reserved. MIT license.
import * as fbs from "gen/msg_generated";
import { flatbuffers } from "flatbuffers";
import * as dispatch from "./dispatch";
import { assert } from "./util";
/**
* A FileInfo describes a file and is returned by `stat`, `lstat`,
* `statSync`, `lstatSync`.
*/
export class FileInfo {
private readonly _isFile: boolean;
private readonly _isSymlink: boolean;
/** The size of the file, in bytes. */
len: number;
/**
* The last modification time of the file. This corresponds to the `mtime`
* field from `stat` on Unix and `ftLastWriteTime` on Windows. This may not
* be available on all platforms.
*/
modified: number | null;
/**
* The last access time of the file. This corresponds to the `atime`
* field from `stat` on Unix and `ftLastAccessTime` on Windows. This may not
* be available on all platforms.
*/
accessed: number | null;
/**
* The last access time of the file. This corresponds to the `birthtime`
* field from `stat` on Unix and `ftCreationTime` on Windows. This may not
* be available on all platforms.
*/
created: number | null;
/* @internal */
constructor(private _msg: fbs.StatRes) {
const modified = this._msg.modified().toFloat64();
const accessed = this._msg.accessed().toFloat64();
const created = this._msg.created().toFloat64();
this._isFile = this._msg.isFile();
this._isSymlink = this._msg.isSymlink();
this.len = this._msg.len().toFloat64();
this.modified = modified ? modified : null;
this.accessed = accessed ? accessed : null;
this.created = created ? created : null;
}
/**
* Returns whether this is info for a regular file. This result is mutually
* exclusive to `FileInfo.isDirectory` and `FileInfo.isSymlink`.
*/
isFile() {
return this._isFile;
}
/**
* Returns whether this is info for a regular directory. This result is
* mutually exclusive to `FileInfo.isFile` and `FileInfo.isSymlink`.
*/
isDirectory() {
return !this._isFile && !this._isSymlink;
}
/**
* Returns whether this is info for a symlink. This result is
* mutually exclusive to `FileInfo.isFile` and `FileInfo.isDirectory`.
*/
isSymlink() {
return this._isSymlink;
}
}
/**
* Queries the file system for information on the path provided.
* If the given path is a symlink information about the symlink will
* be returned.
*
* import { lstat } from "deno";
* const fileInfo = await deno.lstat("hello.txt");
* assert(fileInfo.isFile());
*/
export async function lstat(filename: string): Promise<FileInfo> {
return res(await dispatch.sendAsync(...req(filename, true)));
}
/**
* Queries the file system for information on the path provided synchronously.
* If the given path is a symlink information about the symlink will
* be returned.
*
* import { lstatSync } from "deno";
* const fileInfo = deno.lstatSync("hello.txt");
* assert(fileInfo.isFile());
*/
export function lstatSync(filename: string): FileInfo {
return res(dispatch.sendSync(...req(filename, true)));
}
/**
* Queries the file system for information on the path provided.
* `stat` Will always follow symlinks.
*
* import { stat } from "deno";
* const fileInfo = await deno.stat("hello.txt");
* assert(fileInfo.isFile());
*/
export async function stat(filename: string): Promise<FileInfo> {
return res(await dispatch.sendAsync(...req(filename, false)));
}
/**
* Queries the file system for information on the path provided synchronously.
* `statSync` Will always follow symlinks.
*
* import { statSync } from "deno";
* const fileInfo = deno.statSync("hello.txt");
* assert(fileInfo.isFile());
*/
export function statSync(filename: string): FileInfo {
return res(dispatch.sendSync(...req(filename, false)));
}
function req(
filename: string,
lstat: boolean
): [flatbuffers.Builder, fbs.Any, flatbuffers.Offset] {
const builder = new flatbuffers.Builder();
const filename_ = builder.createString(filename);
fbs.Stat.startStat(builder);
fbs.Stat.addFilename(builder, filename_);
fbs.Stat.addLstat(builder, lstat);
const msg = fbs.Stat.endStat(builder);
return [builder, fbs.Any.Stat, msg];
}
function res(baseRes: null | fbs.Base): FileInfo {
assert(baseRes != null);
assert(fbs.Any.StatRes === baseRes!.msgType());
const res = new fbs.StatRes();
assert(baseRes!.msg(res) != null);
return new FileInfo(res);
}

126
js/stat_test.ts Normal file
View file

@ -0,0 +1,126 @@
// Copyright 2018 the Deno authors. All rights reserved. MIT license.
import { test, assert, assertEqual } from "./test_util.ts";
import * as deno from "deno";
// TODO Add tests for modified, accessed, and created fields once there is a way
// to create temp files.
test(async function statSyncSuccess() {
const packageInfo = deno.statSync("package.json");
assert(packageInfo.isFile());
assert(!packageInfo.isSymlink());
const testingInfo = deno.statSync("testing");
assert(testingInfo.isDirectory());
assert(!testingInfo.isSymlink());
const srcInfo = deno.statSync("src");
assert(srcInfo.isDirectory());
assert(!srcInfo.isSymlink());
});
test(async function statSyncNotFound() {
let caughtError = false;
let badInfo;
try {
badInfo = deno.statSync("bad_file_name");
} catch (err) {
caughtError = true;
assertEqual(err.kind, deno.ErrorKind.NotFound);
assertEqual(err.name, "NotFound");
}
assert(caughtError);
assertEqual(badInfo, undefined);
});
test(async function lstatSyncSuccess() {
const packageInfo = deno.lstatSync("package.json");
assert(packageInfo.isFile());
assert(!packageInfo.isSymlink());
const testingInfo = deno.lstatSync("testing");
assert(!testingInfo.isDirectory());
assert(testingInfo.isSymlink());
const srcInfo = deno.lstatSync("src");
assert(srcInfo.isDirectory());
assert(!srcInfo.isSymlink());
});
test(async function lstatSyncNotFound() {
let caughtError = false;
let badInfo;
try {
badInfo = deno.lstatSync("bad_file_name");
} catch (err) {
caughtError = true;
assertEqual(err.kind, deno.ErrorKind.NotFound);
assertEqual(err.name, "NotFound");
}
assert(caughtError);
assertEqual(badInfo, undefined);
});
test(async function statSuccess() {
const packageInfo = await deno.stat("package.json");
assert(packageInfo.isFile());
assert(!packageInfo.isSymlink());
const testingInfo = await deno.stat("testing");
assert(testingInfo.isDirectory());
assert(!testingInfo.isSymlink());
const srcInfo = await deno.stat("src");
assert(srcInfo.isDirectory());
assert(!srcInfo.isSymlink());
});
test(async function statNotFound() {
let caughtError = false;
let badInfo;
try {
badInfo = await deno.stat("bad_file_name");
} catch (err) {
caughtError = true;
assertEqual(err.kind, deno.ErrorKind.NotFound);
assertEqual(err.name, "NotFound");
}
assert(caughtError);
assertEqual(badInfo, undefined);
});
test(async function lstatSuccess() {
const packageInfo = await deno.lstat("package.json");
assert(packageInfo.isFile());
assert(!packageInfo.isSymlink());
const testingInfo = await deno.lstat("testing");
assert(!testingInfo.isDirectory());
assert(testingInfo.isSymlink());
const srcInfo = await deno.lstat("src");
assert(srcInfo.isDirectory());
assert(!srcInfo.isSymlink());
});
test(async function lstatNotFound() {
let caughtError = false;
let badInfo;
try {
badInfo = await deno.lstat("bad_file_name");
} catch (err) {
caughtError = true;
assertEqual(err.kind, deno.ErrorKind.NotFound);
assertEqual(err.name, "NotFound");
}
assert(caughtError);
assertEqual(badInfo, undefined);
});

View file

@ -8,3 +8,4 @@ import "./os_test.ts";
import "./read_file_test.ts";
import "./write_file_test.ts";
import "./mkdir_test.ts";
import "./stat_test.ts";

View file

@ -53,7 +53,7 @@ pub extern "C" fn msg_from_js(d: *const DenoC, buf: deno_buf) {
msg::Any::ReadFile => handle_read_file,
msg::Any::RenameSync => handle_rename_sync,
msg::Any::SetEnv => handle_set_env,
msg::Any::StatSync => handle_stat_sync,
msg::Any::Stat => handle_stat,
msg::Any::WriteFile => handle_write_file,
msg::Any::Exit => handle_exit,
_ => panic!(format!(
@ -476,15 +476,15 @@ macro_rules! to_seconds {
}};
}
fn handle_stat_sync(_d: *const DenoC, base: &msg::Base) -> Box<Op> {
let msg = base.msg_as_stat_sync().unwrap();
fn handle_stat(_d: *const DenoC, base: &msg::Base) -> Box<Op> {
let msg = base.msg_as_stat().unwrap();
let cmd_id = base.cmd_id();
let filename = String::from(msg.filename().unwrap());
let lstat = msg.lstat();
Box::new(futures::future::result(|| -> OpResult {
let builder = &mut FlatBufferBuilder::new();
debug!("handle_stat_sync {} {}", filename, lstat);
debug!("handle_stat {} {}", filename, lstat);
let path = Path::new(&filename);
let metadata = if lstat {
fs::symlink_metadata(path)?
@ -492,9 +492,9 @@ fn handle_stat_sync(_d: *const DenoC, base: &msg::Base) -> Box<Op> {
fs::metadata(path)?
};
let msg = msg::StatSyncRes::create(
let msg = msg::StatRes::create(
builder,
&msg::StatSyncResArgs {
&msg::StatResArgs {
is_file: metadata.is_file(),
is_symlink: metadata.file_type().is_symlink(),
len: metadata.len(),
@ -510,7 +510,7 @@ fn handle_stat_sync(_d: *const DenoC, base: &msg::Base) -> Box<Op> {
builder,
msg::BaseArgs {
msg: Some(msg.as_union_value()),
msg_type: msg::Any::StatSyncRes,
msg_type: msg::Any::StatRes,
..Default::default()
},
))

View file

@ -19,8 +19,8 @@ union Any {
ReadFileRes,
WriteFile,
RenameSync,
StatSync,
StatSyncRes,
Stat,
StatRes,
SetEnv,
}
@ -193,12 +193,12 @@ table RenameSync {
newpath: string;
}
table StatSync {
table Stat {
filename: string;
lstat: bool;
}
table StatSyncRes {
table StatRes {
is_file: bool;
is_symlink: bool;
len: ulong;