Skip to content

Commit

Permalink
Merge pull request #37 from joshi-monster/file-info-functions
Browse files Browse the repository at this point in the history
File info functions
  • Loading branch information
bcpeinhardt authored Aug 28, 2024
2 parents 838ef4f + d3f67c9 commit 28e8b6a
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 23 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# Changelog

## Unreleased
- Add `FileInfo` and `file_info_type` to get the file type from a `FileInfo` without checking the file system again
- Add `file_info_permissions` and `file_info_permissions_octal` to get the currently set permissions of a file or directory.
- Improve performance of `get_files`
- Add `link_info` function to get `FileInfo` values without following symlinks

## v2.0.1 - 27 June 2024
Expand Down
97 changes: 85 additions & 12 deletions src/simplifile.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,55 @@ pub type FileInfo {
)
}

/// Extract the file permissions from a given FileInfo value in their octal representation.
///
/// ## Example
/// ```gleam
/// use info <- result.try(simplifile.file_info("src/app.gleam"))
/// simplifile.file_info_permissions_octal(info)
/// // --> 0o644
/// ```
pub fn file_info_permissions_octal(from file_info: FileInfo) -> Int {
int.bitwise_and(file_info.mode, 0o777)
}

/// Extract the `FilePermissions` from a given FileInfo value.
pub fn file_info_permissions(from file_info: FileInfo) -> FilePermissions {
octal_to_file_permissions(file_info_permissions_octal(file_info))
}

/// An enumeration of different types of files.
pub type FileType {
/// A regular file
File
/// A directory
Directory
/// A symbolic link
Symlink
/// Another special file type present on some systems, lika a socket or device
Other
}

/// Extract the file type from a given FileInfo value.
///
/// ## Example
/// ```gleam
/// use info <- result.try(simplifile.file_info("src/app.gleam"))
/// simplifile.file_info_type(info)
/// // --> simplifile.File
/// ```
pub fn file_info_type(from file_info: FileInfo) -> FileType {
// S_IFMT and related constants;
// see https://www.man7.org/linux/man-pages/man7/inode.7.html
// see https://github.com/nodejs/node/blob/main/typings/internalBinding/constants.d.ts#L147
case int.bitwise_and(file_info.mode, 0o170000) {
0o100000 -> File
0o40000 -> Directory
0o120000 -> Symlink
_ -> Other
}
}

/// Get information about a file at a given path
///
/// When the given `filepath` points to a symlink, this function will follow
Expand Down Expand Up @@ -526,19 +575,15 @@ pub fn get_files(in directory: String) -> Result(List(String), FileError) {
use contents <- result.try(read_directory(directory))
use acc, content <- list.try_fold(over: contents, from: [])
let path = filepath.join(directory, content)
use info <- result.try(file_info(path))

case is_file(path) {
Error(e) -> Error(e)
Ok(True) -> Ok([path, ..acc])
Ok(False) ->
case is_directory(path) {
Error(e) -> Error(e)
Ok(False) -> Ok(acc)
Ok(True) -> {
use nested_files <- result.try(get_files(path))
Ok(list.append(acc, nested_files))
}
}
case file_info_type(info) {
File -> Ok([path, ..acc])
Directory -> {
use nested_files <- result.try(get_files(path))
Ok(list.append(acc, nested_files))
}
_ -> Ok(acc)
}
}

Expand All @@ -557,6 +602,21 @@ fn permission_to_integer(permission: Permission) -> Int {
}
}

fn integer_to_permissions(integer: Int) -> Set(Permission) {
case int.bitwise_and(integer, 7) {
7 -> set.from_list([Read, Write, Execute])
6 -> set.from_list([Read, Write])
5 -> set.from_list([Read, Execute])
3 -> set.from_list([Write, Execute])
4 -> set.from_list([Read])
2 -> set.from_list([Write])
1 -> set.from_list([Execute])
0 -> set.new()
// since we bitwise_and, these are all possible values.
_ -> panic
}
}

/// Represents a set of file permissions for a given file
pub type FilePermissions {
FilePermissions(
Expand All @@ -581,6 +641,19 @@ pub fn file_permissions_to_octal(permissions: FilePermissions) -> Int {
+ make_permission_digit(permissions.other)
}

fn octal_to_file_permissions(octal: Int) -> FilePermissions {
FilePermissions(
user: octal
|> int.bitwise_shift_right(6)
|> integer_to_permissions,
group: octal
|> int.bitwise_shift_right(3)
|> integer_to_permissions,
other: octal
|> integer_to_permissions,
)
}

/// Sets the permissions for a given file
///
/// # Example
Expand Down
74 changes: 63 additions & 11 deletions test/simplifile_test.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@ import gleam/set
import gleeunit
import gleeunit/should
import simplifile.{
Eacces, Eagain, Ebadf, Ebadmsg, Ebusy, Edeadlk, Edeadlock, Edquot, Eexist,
Efault, Efbig, Eftype, Einval, Eio, Eisdir, Eloop, Emfile, Emlink, Emultihop,
Enametoolong, Enfile, Enobufs, Enodev, Enoent, Enolck, Enolink, Enomem, Enospc,
Enosr, Enostr, Enosys, Enotblk, Enotdir, Enotsup, Enxio, Eopnotsupp, Eoverflow,
Eperm, Epipe, Erange, Erofs, Espipe, Esrch, Estale, Etxtbsy, Exdev, Execute,
FilePermissions, NotUtf8, Read, Unknown, Write, append, append_bits,
copy_directory, copy_file, create_directory, create_directory_all, create_file,
create_symlink, delete, delete_all, file_info, file_permissions_to_octal,
get_files, is_directory, is_file, is_symlink, link_info, read, read_bits,
read_directory, rename_directory, rename_file, set_permissions,
set_permissions_octal, write, write_bits,
Directory, Eacces, Eagain, Ebadf, Ebadmsg, Ebusy, Edeadlk, Edeadlock, Edquot,
Eexist, Efault, Efbig, Eftype, Einval, Eio, Eisdir, Eloop, Emfile, Emlink,
Emultihop, Enametoolong, Enfile, Enobufs, Enodev, Enoent, Enolck, Enolink,
Enomem, Enospc, Enosr, Enostr, Enosys, Enotblk, Enotdir, Enotsup, Enxio,
Eopnotsupp, Eoverflow, Eperm, Epipe, Erange, Erofs, Espipe, Esrch, Estale,
Etxtbsy, Exdev, Execute, File, FilePermissions, NotUtf8, Read, Unknown, Write,
append, append_bits, copy_directory, copy_file, create_directory,
create_directory_all, create_file, create_symlink, delete, delete_all,
file_info, file_info_permissions, file_info_permissions_octal, file_info_type,
file_permissions_to_octal, get_files, is_directory, is_file, is_symlink,
link_info, read, read_bits, read_directory, rename_directory, rename_file,
set_permissions, set_permissions_octal, write, write_bits,
}

pub fn main() {
Expand Down Expand Up @@ -422,6 +423,36 @@ pub fn permissions_octal_test() {
let assert Ok(Nil) = delete("./tmp/permissions")
}

pub fn file_info_get_permissions_test() {
let filepath = "./tmp/permissions/test.sh"
let assert Ok(Nil) = create_directory("./tmp/permissions")
let assert Ok(Nil) = write("echo \"Hello from a file\"", to: filepath)

// This is the equivalent of `chmod 777 ./tmp/permissions/test.sh`
let all = set.from_list([Read, Write, Execute])
let all = FilePermissions(user: all, group: all, other: all)
let assert Ok(Nil) = set_permissions(filepath, all)
let assert Ok(info) = file_info(filepath)

file_info_permissions(info)
|> should.equal(all)

file_info_permissions_octal(info)
|> should.equal(0o777)

let assert Ok(Nil) = set_permissions_octal(filepath, 0o625)
let assert Ok(info) = file_info(filepath)

file_info_permissions(info)
|> should.equal(FilePermissions(
user: set.from_list([Read, Write]),
group: set.from_list([Write]),
other: set.from_list([Read, Execute]),
))

let assert Ok(Nil) = delete("./tmp/permissions")
}

pub fn get_files_with_slash_test() {
let assert Ok(files) = get_files(in: "./test/")
files
Expand Down Expand Up @@ -473,6 +504,27 @@ pub fn file_info_test() {
let assert Ok(_info) = file_info("./test.sh")
}

pub fn file_info_type_test() {
let filepath = "./tmp/file_info_type_test.txt"
let assert Ok(_) = write(to: filepath, contents: "")
let assert Ok(info) = file_info(filepath)
file_info_type(info)
|> should.equal(File)

let assert Ok(info) = file_info("./tmp")
file_info_type(info)
|> should.equal(Directory)

let linkpath = "./tmp/file_info_type_symlink"
let assert Ok(_) = create_symlink("file_info_type_test.txt", linkpath)
let assert Ok(info) = file_info(linkpath)
file_info_type(info)
|> should.equal(File)
// let assert Ok(info) = link_info(linkpath)
// file_info_type(info)
// |> should.equal(Symlink)
}

pub fn link_info_test() {
let target_path = "./tmp/the_target"
let symlink_path = "./tmp/the_symlink"
Expand Down

0 comments on commit 28e8b6a

Please sign in to comment.