Skip to content

Commit a7180ae

Browse files
committed
add file_info function
1 parent b9f6e00 commit a7180ae

6 files changed

+127
-24
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
## Unreleased
44

5+
## v1.5.0 - 29 February 2024
6+
- add `file_info` function.
7+
58
## v1.4.2 - 18 February 2024
69
- Update msg in deprecated tag for `is_directory` to correctly point to `verify_is_directory`
710

gleam.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name = "simplifile"
2-
version = "1.4.2"
2+
version = "1.5.0"
33
description = "Basic file operations that work on all targets"
44

55
licences = ["Apache-2.0"]

src/simplifile.gleam

+49
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,55 @@ pub type FileError {
114114
Unknown
115115
}
116116

117+
/// Represents the intersection of information available
118+
/// from erlang's `file:read_file_info` and node's `fs.stat`
119+
pub type FileInfo {
120+
FileInfo(
121+
/// File size in bytes.
122+
size: Int,
123+
/// File mode that indicates the file type and its permissions.
124+
/// For example, in Unix and Linux, a mode value of 33188 indicates
125+
/// a regular file and the permissions associated with it
126+
/// (read and write for the owner, and read-only for others, in
127+
/// this case).
128+
mode: Int,
129+
/// Number of hard links that exist for the file.
130+
nlinks: Int,
131+
/// Inode number, which is a unique identifier for the file in the filesystem.
132+
inode: Int,
133+
/// User ID of the file's owner.
134+
user_id: Int,
135+
/// Group ID of the file's group.
136+
group_id: Int,
137+
/// Device ID of the file's major device.
138+
/// TODO: We can actually get a major device and minor device from both
139+
/// node and erlang. The `fs.stat` in node returns a `dev` and `rdev`,
140+
/// so we can use some bitwise operations to get the minor out of `rdev`.
141+
/// Someone who's not me should totally make a PR for that.
142+
dev: Int,
143+
/// The last access time in seconds since the UNIX epoch (00:00:00 UTC on 1 January 1970).
144+
atime_seconds: Int,
145+
/// The last modification time in seconds since the UNIX epoch (00:00:00 UTC on 1 January 1970).
146+
mtime_seconds: Int,
147+
/// The last change time in seconds since the UNIX epoch (00:00:00 UTC on 1 January 1970).
148+
ctime_seconds: Int,
149+
)
150+
}
151+
152+
@target(erlang)
153+
@external(erlang, "simplifile_erl", "file_info")
154+
fn do_file_info(a: String) -> Result(FileInfo, FileError)
155+
156+
@target(javascript)
157+
@external(javascript, "./simplifile_js.mjs", "fileInfo")
158+
fn do_file_info(a: String) -> Result(FileInfo, String)
159+
160+
/// Get information about a file at a given path
161+
pub fn file_info(a: String) -> Result(FileInfo, FileError) {
162+
do_file_info(a)
163+
|> cast_error
164+
}
165+
117166
/// Read a files contents as a string
118167
/// ## Example
119168
/// ```gleam

src/simplifile_erl.erl

+40-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
-module(simplifile_erl).
88

99
%% API
10-
- export ( [ read_file / 1 , append_file / 2 , write_file / 2 , delete_file / 1 , delete_directory / 1 , recursive_delete / 1 , list_directory / 1 , make_directory / 1 , is_file / 1 , create_dir_all / 1 , rename_file / 2 , set_permissions / 2 , is_valid_directory / 1 , is_valid_file/1 ] ) .
10+
-export([read_file/1, append_file/2, write_file/2, delete_file/1, delete_directory/1,
11+
recursive_delete/1, list_directory/1, make_directory/1, is_file/1, create_dir_all/1,
12+
rename_file/2, set_permissions/2, is_valid_directory/1, is_valid_file/1, file_info/1]).
1113

1214
-include_lib("kernel/include/file.hrl").
1315

@@ -155,3 +157,40 @@ is_valid_file(Path) ->
155157
{error, Reason} ->
156158
posix_result({error, Reason})
157159
end.
160+
161+
file_info_result(Result) ->
162+
case Result of
163+
{ok,
164+
{file_info,
165+
Size,
166+
_Type,
167+
_Access,
168+
Atime,
169+
Mtime,
170+
Ctime,
171+
Mode,
172+
Links,
173+
MajorDevice,
174+
_MinorDevice,
175+
Inode,
176+
Uid,
177+
Gid}} ->
178+
{ok,
179+
{file_info,
180+
Size,
181+
Mode,
182+
Links,
183+
Inode,
184+
Uid,
185+
Gid,
186+
MajorDevice,
187+
Atime,
188+
Mtime,
189+
Ctime}};
190+
{error, Reason} when ?is_posix_error(Reason) ->
191+
Result
192+
end.
193+
194+
file_info(Filename) ->
195+
file_info_result(file:read_file_info(Filename, [{time, posix}])).
196+

src/simplifile_js.mjs

+25
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,31 @@ export function currentDirectory() {
190190
return gleamResult(() => process.cwd())
191191
}
192192

193+
/**
194+
*
195+
* @param {string} filepath
196+
* @returns {Ok | GError}
197+
*/
198+
export function fileInfo(filepath) {
199+
return gleamResult(() => new FileInfo(filepath))
200+
}
201+
202+
class FileInfo {
203+
constructor(filepath) {
204+
const stat = fs.statSync(path.normalize(filepath))
205+
this.size = stat.size
206+
this.mode = stat.mode
207+
this.nlinks = stat.nlink
208+
this.inode = stat.ino
209+
this.userId = stat.uid
210+
this.groupId = stat.gid
211+
this.dev = stat.dev
212+
this.atime = Math.floor(stat.atimeMs / 1000)
213+
this.mtime = Math.floor(stat.mtimeMs / 1000)
214+
this.ctime = Math.floor(stat.ctimeMs / 1000)
215+
}
216+
}
217+
193218
/**
194219
* Perform some operation and return a Gleam `Result(a, String)`
195220
* where `a` is the type returned by the operation and the `String`

test/simplifile_test.gleam

+9-22
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ import gleeunit/should
33
import simplifile.{
44
Eacces, Enoent, Execute, FilePermissions, NotUtf8, Read, Write, append,
55
append_bits, copy_directory, copy_file, create_directory, create_directory_all,
6-
create_file, current_directory, delete, delete_all, file_permissions_to_octal,
7-
get_files, read, read_bits, read_directory, rename_directory, rename_file,
8-
set_permissions, set_permissions_octal, verify_is_directory, verify_is_file,
9-
write, write_bits,
6+
create_file, current_directory, delete, delete_all, file_info,
7+
file_permissions_to_octal, get_files, read, read_bits, read_directory,
8+
rename_directory, rename_file, set_permissions, set_permissions_octal,
9+
verify_is_directory, verify_is_file, write, write_bits,
1010
}
1111
import gleam/list
1212
import gleam/int
@@ -383,22 +383,9 @@ pub fn no_read_permissions_test() {
383383
let assert Ok(Nil) = set_permissions_octal(parent_dir, 0o777)
384384
let assert Ok(Nil) = delete(parent_dir)
385385
}
386-
// pub fn is_file_and_is_dir_test() {
387-
// let existing_file = "./gleam.toml"
388-
// let existing_dir = "./test"
389-
// let non_existing_file = "./i_dont_exist"
390-
// let file_permission_issue = "/etc"
391386

392-
// let assert True = is_file(existing_file)
393-
// let assert True = is_directory(existing_dir)
394-
395-
// let assert False = is_directory(existing_file)
396-
// let assert False = is_file(existing_dir)
397-
398-
// let assert False = is_file(non_existing_file)
399-
// let assert False = is_directory(non_existing_file)
400-
401-
// // let assert False = is_file(file_permission_issue)
402-
// // This fails on javascript, because the permission error throws
403-
// // let assert False = is_directory(file_permission_issue)
404-
// }
387+
pub fn file_info_test() {
388+
let assert Ok(_info) = file_info("./test.sh")
389+
}
390+
/// I visually inspected this info to make sure it matched on all targets.
391+
/// TODO: Add a better test setup for validating file info functionality.

0 commit comments

Comments
 (0)