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:
parent
806385543c
commit
7c50c11f40
9 changed files with 284 additions and 185 deletions
2
BUILD.gn
2
BUILD.gn
|
@ -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",
|
||||
|
|
|
@ -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
109
js/os.ts
|
@ -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";
|
||||
|
|
|
@ -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
143
js/stat.ts
Normal 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
126
js/stat_test.ts
Normal 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);
|
||||
});
|
||||
|
|
@ -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";
|
||||
|
|
|
@ -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()
|
||||
},
|
||||
))
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue