Skip to content

Commit

Permalink
feat: implement rmSync (#276)
Browse files Browse the repository at this point in the history
Refs: #249
  • Loading branch information
fredbonin committed Mar 16, 2024
1 parent 8e7f95d commit 74b2bb0
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 24 deletions.
1 change: 1 addition & 0 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ Available globally
[readdirSync](https://nodejs.org/api/fs.html#fsreaddirsyncpath-options)
[readFileSync](https://nodejs.org/api/fs.html#fsreadfilesyncpath-options)
[rmdirSync](https://nodejs.org/api/fs.html#fsrmdirsyncpath-options)
[rmSync](https://nodejs.org/api/fs.html#fsrmsyncpath-options)

## fs/promises

Expand Down
4 changes: 2 additions & 2 deletions src/fs/access.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ use super::{CONSTANT_F_OK, CONSTANT_R_OK, CONSTANT_W_OK, CONSTANT_X_OK};
pub async fn access(ctx: Ctx<'_>, path: String, mode: Opt<u32>) -> Result<()> {
let metadata = fs::metadata(&path)
.await
.or_throw_msg(&ctx, &format!("Can't access file \"{}\"", &path))?;
.or_throw_msg(&ctx, &format!("No such file or directory \"{}\"", &path))?;

verify_metadata(&ctx, mode, metadata)
}

pub fn access_sync(ctx: Ctx<'_>, path: String, mode: Opt<u32>) -> Result<()> {
let metadata = std::fs::metadata(path.clone())
.or_throw_msg(&ctx, &format!("Can't access file \"{}\"", &path))?;
.or_throw_msg(&ctx, &format!("No such file or directory \"{}\"", &path))?;

verify_metadata(&ctx, mode, metadata)
}
Expand Down
4 changes: 3 additions & 1 deletion src/fs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use self::rm::{rmdir, rmfile};
use self::stats::{stat_fn, Stat};
use self::write_file::write_file;
use crate::fs::mkdir::{mkdir, mkdir_sync, mkdtemp, mkdtemp_sync};
use crate::fs::rm::rmdir_sync;
use crate::fs::rm::{rmdir_sync, rmfile_sync};

pub const CONSTANT_F_OK: u32 = 0;
pub const CONSTANT_R_OK: u32 = 4;
Expand Down Expand Up @@ -78,6 +78,7 @@ impl ModuleDef for FsModule {
declare.declare("readdirSync")?;
declare.declare("readFileSync")?;
declare.declare("rmdirSync")?;
declare.declare("rmSync")?;

declare.declare("default")?;

Expand All @@ -99,6 +100,7 @@ impl ModuleDef for FsModule {
default.set("readdirSync", Func::from(read_dir_sync))?;
default.set("readFileSync", Func::from(read_file_sync))?;
default.set("rmdirSync", Func::from(rmdir_sync))?;
default.set("rmSync", Func::from(rmfile_sync))?;

Ok(())
})
Expand Down
49 changes: 39 additions & 10 deletions src/fs/rm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::utils::result::ResultExt;

#[allow(clippy::manual_async_fn)]
pub async fn rmdir<'js>(ctx: Ctx<'js>, path: String, options: Opt<Object<'js>>) -> Result<()> {
let recursive = get_params(options);
let recursive = get_params_rm_dir(options);

if recursive {
fs::remove_dir_all(&path).await
Expand All @@ -21,7 +21,7 @@ pub async fn rmdir<'js>(ctx: Ctx<'js>, path: String, options: Opt<Object<'js>>)

#[allow(clippy::manual_async_fn)]
pub fn rmdir_sync<'js>(ctx: Ctx<'js>, path: String, options: Opt<Object<'js>>) -> Result<()> {
let recursive = get_params(options);
let recursive = get_params_rm_dir(options);

if recursive {
std::fs::remove_dir_all(&path)
Expand All @@ -34,13 +34,7 @@ pub fn rmdir_sync<'js>(ctx: Ctx<'js>, path: String, options: Opt<Object<'js>>) -
}

pub async fn rmfile<'js>(ctx: Ctx<'js>, path: String, options: Opt<Object<'js>>) -> Result<()> {
let mut recursive = false;
let mut force = false;

if let Some(options) = options.0 {
recursive = options.get("recursive").unwrap_or_default();
force = options.get("force").unwrap_or_default();
}
let (recursive, force) = get_params_rm(options);

let res = async move {
let is_dir = fs::metadata(&path)
Expand Down Expand Up @@ -68,11 +62,46 @@ pub async fn rmfile<'js>(ctx: Ctx<'js>, path: String, options: Opt<Object<'js>>)
Ok(())
}

fn get_params(options: Opt<Object>) -> bool {
pub fn rmfile_sync<'js>(_ctx: Ctx<'js>, path: String, options: Opt<Object<'js>>) -> Result<()> {
let (recursive, force) = get_params_rm(options);

let res = (|| -> Result<()> {
let is_dir = std::fs::metadata(&path).map(|metadata| metadata.is_dir())?;

(if is_dir && recursive {
std::fs::remove_dir_all(&path)
} else if is_dir && !recursive {
std::fs::remove_dir(&path)
} else {
std::fs::remove_file(&path)
})?;

Ok(())
})();

if !force {
return res;
}

Ok(())
}

fn get_params_rm_dir(options: Opt<Object>) -> bool {
let mut recursive = false;

if let Some(options) = options.0 {
recursive = options.get("recursive").unwrap_or_default();
}
recursive
}

fn get_params_rm(options: Opt<Object>) -> (bool, bool) {
let mut recursive = false;
let mut force = false;

if let Some(options) = options.0 {
recursive = options.get("recursive").unwrap_or_default();
force = options.get("force").unwrap_or_default();
}
(recursive, force)
}
112 changes: 101 additions & 11 deletions tests/unit/fs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ describe("readfile", () => {
expect(typeof text).toEqual("string");
expect(text).toEqual("hello world!");
});
})
});

describe("readfileSync", () => {
it("should read a file synchronously", () => {
Expand All @@ -120,7 +120,9 @@ describe("readfileSync", () => {
});

it("should return a string when encoding is provided as option synchronously", () => {
const text = defaultFsImport.readFileSync("fixtures/hello.txt", { encoding: "utf-8" });
const text = defaultFsImport.readFileSync("fixtures/hello.txt", {
encoding: "utf-8",
});
expect(typeof text).toEqual("string");
expect(text).toEqual("hello world!");
});
Expand Down Expand Up @@ -159,7 +161,7 @@ describe("mkdtemp", () => {
// Clean up the temporary directory
await fs.rmdir(dirPath);
});
})
});

describe("mkdtempSync", () => {
it("should create a temporary directory with a given prefix synchronously", async () => {
Expand All @@ -172,11 +174,11 @@ describe("mkdtempSync", () => {
.stat(dirPath)
.then(() => true)
.catch(() => false);
expect(dirExists).toBeTruthy()
expect(dirExists).toBeTruthy();

// Check that the directory has the correct prefix
const dirPrefix = path.basename(dirPath).slice(0, prefix.length);
expect(dirPrefix).toStrictEqual(prefix)
expect(dirPrefix).toStrictEqual(prefix);

// Clean up the temporary directory
await defaultFsImport.rmdirSync(dirPath);
Expand All @@ -188,9 +190,7 @@ describe("mkdir", () => {
const dirPath = await fs.mkdtemp(path.join(os.tmpdir(), "test/test-"));

//non recursive should reject
expect(fs.mkdir(dirPath)).rejects.toThrow(
"EEXIST: file already exists, mkdir"
);
await expect(fs.mkdir(dirPath)).rejects.toThrow(/dir/);

await fs.mkdir(dirPath, { recursive: true });

Expand All @@ -208,7 +208,9 @@ describe("mkdir", () => {

describe("mkdirSync", () => {
it("should create a directory with the given path synchronously", async () => {
const dirPath = defaultFsImport.mkdtempSync(path.join(os.tmpdir(), "test/test-"));
const dirPath = defaultFsImport.mkdtempSync(
path.join(os.tmpdir(), "test/test-")
);

//non recursive should reject
expect(() => defaultFsImport.mkdirSync(dirPath)).toThrow(/[fF]ile.*exists/);
Expand Down Expand Up @@ -242,6 +244,94 @@ describe("writeFile", () => {
});
});

describe("rm", () => {
it("should delete file and directory", async () => {
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "test-"));
const filePath = path.join(tmpDir, "test");
const fileContents = "hello";
await fs.writeFile(filePath, fileContents);

const contents = (await fs.readFile(filePath)).toString();
expect(fileContents).toEqual(contents);

// Should delete file
await fs.rm(filePath, { recursive: true });
await expect(fs.access(filePath)).rejects.toThrow(
/[Nn]o such file or directory/
);

// Check dir still exists and then delete it
await fs.access(tmpDir);
await fs.rm(tmpDir, { recursive: true });
await expect(fs.access(filePath)).rejects.toThrow(
/[Nn]o such file or directory/
);
});
it("should throw an error if file does not exists", async () => {
const tmpDir = defaultFsImport.mkdtempSync(path.join(os.tmpdir(), "test-"));
const filePath = path.join(tmpDir, "test");

await expect(fs.rm(filePath, {})).rejects.toThrow(
/[Nn]o such file or directory/
);
});
it("should not throw an error if file does not exists and force is used", async () => {
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "test-"));
const filePath = path.join(tmpDir, "test");

await expect(fs.access(filePath)).rejects.toThrow(
/[Nn]o such file or directory/
);

// Should not throw an exception since it does not exists
await fs.rm(filePath, { force: true, recursive: true });
});
});
describe("rmSync", () => {
it("should delete file and directory with rm synchronously", async () => {
const tmpDir = defaultFsImport.mkdtempSync(path.join(os.tmpdir(), "test-"));
const filePath = path.join(tmpDir, "test");
const fileContents = "hello";
await fs.writeFile(filePath, fileContents);

const contents = defaultFsImport.readFileSync(filePath).toString();

expect(fileContents).toEqual(contents);

// Should delete file
defaultFsImport.rmSync(filePath, { recursive: true });
expect(() => defaultFsImport.accessSync(filePath)).toThrow(
/[Nn]o such file or directory/
);

// Check dir still exists and then delete it
defaultFsImport.accessSync(tmpDir);
defaultFsImport.rmSync(tmpDir, { recursive: true });
expect(() => defaultFsImport.accessSync(tmpDir)).toThrow(
/[Nn]o such file or directory/
);
});
it("should throw an error if file does not exists with rm synchronously", async () => {
const tmpDir = defaultFsImport.mkdtempSync(path.join(os.tmpdir(), "test-"));
const filePath = path.join(tmpDir, "test");

expect(() => defaultFsImport.rmSync(filePath, {})).toThrow(
/[Nn]o such file or directory/
);
});
it("should not throw an error if file does not exists and force is used with rm synchronously", async () => {
const tmpDir = defaultFsImport.mkdtempSync(path.join(os.tmpdir(), "test-"));
const filePath = path.join(tmpDir, "test");

expect(() => defaultFsImport.accessSync(filePath)).toThrow(
/[Nn]o such file or directory/
);

// Should not throw an exception since it does not exists
defaultFsImport.rmSync(filePath, { force: true, recursive: true });
});
});

describe("access", () => {
it("should access a file", async () => {
const filePath = "fixtures/hello.txt";
Expand All @@ -250,12 +340,12 @@ describe("access", () => {

it("should throw if not proper permissions", async () => {
const filePath = "fixtures/hello.txt";
assert.rejects(fs.access(filePath, fs.constants.X_OK));
await expect(fs.access(filePath, fs.constants.X_OK)).rejects.toThrow(/[pP]ermission denied/);
});

it("should throw if not exists", async () => {
const filePath = "fixtures/nothing";
assert.rejects(fs.access(filePath));
await expect(fs.access(filePath)).rejects.toThrow(/[nN]o such file or directory/);
});

it("should access a file using default import", async () => {
Expand Down

0 comments on commit 74b2bb0

Please sign in to comment.