Files
n8n-openai-adapter/lib/file.ex
T
2012-05-13 15:33:57 +02:00

322 lines
8.7 KiB
Elixir

defmodule File do
require Erlang.file, as: F
require Erlang.filename, as: FN
require Erlang.filelib, as: FL
defrecord Info, Record.extract(:file_info, from_lib: "kernel/include/file.hrl"), moduledoc: """
A record responsible to hold file information. Its fields are:
* `size` - Size of file in bytes.
* `type` - `:device`, `:directory`, `:regular`, `:other`. The type of the file.
* `access` - `:read`, `:write`, `:read_write`, `:none`. The current system access to
the file.
* `atime` - The last time the file was read.
* `mtime` - The last time the file was written.
* `ctime` - The interpretation of this time field depends on the operating
system. On Unix, it is the last time the file or the inode was
changed. In Windows, it is the create time.
* `mode` - The file permissions.
* `links` - The number of links to this file. This is always 1 for file
systems which have no concept of links.
* `major_device` - Identifies the file system where the file is located.
In windows, the number indicates a drive as follows:
0 means A:, 1 means B:, and so on.
* `minor_device` - Only valid for character devices on Unix. In all other
cases, this field is zero.
* `inode` - Gives the inode number. On non-Unix file systems, this field
will be zero.
* `uid` - Indicates the owner of the file.
* `gid` - Gives the group that the owner of the file belongs to. Will be
zero for non-Unix file systems.
The time type returned in `atime`, `mtime`, and `ctime` is dependent on the
time type set in options. `{:time, type}` where type can be `:local`,
`:universal`, or `:posix`. Default is `:local`.
"""
defexception Error, [reason: nil, action: "", path: nil] do
def message(exception) do
formatted = list_to_binary(F.format_error(exception.reason))
"could not #{exception.action} #{exception.path}: #{formatted}"
end
end
@doc """
Expands the path by returning its absolute name and expanding
any `.` and `..` characters.
## Examples
File.expand_path("/foo/bar/../bar") == "/foo/bar"
"""
def expand_path(path) do
normalize FN.absname(path)
end
@doc """
Expands the path to the relative location and expanding
any `.` and `..` characters. If the path is already an
absolute path, the relative location is ignored.
## Examples
File.expand_path("foo/bar/../bar", "/baz") == "/baz/foo/bar"
File.expand_path("/foo/bar/../bar", "/baz") == "/foo/bar"
"""
def expand_path(path, relative_to) do
normalize FN.absname(FN.absname(path, relative_to))
end
@doc """
Returns true if the path is a regular file.
## Examples
File.regular? __FILE__ #=> true
"""
def regular?(path) do
FL.is_regular(path)
end
@doc """
Returns true if the path is a directory.
"""
def dir?(path) do
FL.is_dir(path)
end
@doc """
Returns true if the given argument exists.
It can be regular file, directory, socket,
symbolic link, named pipe or device file.
## Examples
File.exists?("test/")
#=> true
File.exists?("missing.txt")
#=> false
File.exists?("/dev/null")
#=> true
"""
def exists?(path) do
match?({ :ok, _ }, F.read_file_info(path))
end
@doc """
Returns the last component of the path or the path
itself if it does not contain any directory separators.
## Examples
File.basename("foo")
#=> "foo"
File.basename("foo/bar")
#=> "bar"
File.basename("/")
#=> ""
"""
def basename(path) do
FN.basename(path)
end
@doc """
Returns the last component of `path` with the `extension`
stripped. This function should be used to remove a specific
extension which might, or might not, be there.
## Examples
File.basename("~/foo/bar.ex", ".ex")
#=> "bar"
File.basename("~/foo/bar.exs", ".ex")
#=> "bar.exs"
File.basename("~/foo/bar.old.ex", ".ex")
#=> "bar.old"
"""
def basename(path, extension) do
FN.basename(path, extension)
end
@doc """
Returns a string with one or more paths components joint by the path separator.
This function should be used to convert a list of strings in a path.
## Examples
File.join(["~", "foo"])
#=> "~/foo"
File.join(["foo"])
#=> "foo"
File.join(["/", "foo", "bar"])
#=> "/foo/bar"
"""
def join(paths) do
FN.join(paths)
end
@doc """
Join two paths.
## Examples
File.join("foo", "bar")
#=> "foo/bar"
"""
def join(left, right) do
FN.join(left, right)
end
@doc """
Returns `{:ok, binary}`, where `binary` is a binary data object that contains the contents
of `filename`, or `{:error, reason}` if an error occurs.
Typical error reasons:
* :enoent - The file does not exist.
* :eacces - Missing permission for reading the file,
or for searching one of the parent directories.
* :eisdir - The named file is a directory.
* :enotdir - A component of the file name is not a directory.
On some platforms, enoent is returned instead.
* :enomem - There is not enough memory for the contents of the file.
You can use `Erlang.file.format_error(reason)` to get a descriptive string of the error.
"""
def read(path) do
F.read_file(path)
end
@doc """
Returns binary with the contents of the given filename or raises
File.Error if an error occurs.
"""
def read!(path) do
case read(path) do
{ :ok, binary } ->
binary
{ :error, reason } ->
raise File.Error, reason: reason, action: "read file", path: to_binary(path)
end
end
@doc """
Returns a list with the path splitted by the path separator.
If an empty string is given, then it returns the root path.
## Examples
File.split("")
#=> ["/"]
File.split("foo")
#=> ["foo"]
File.split("/foo/bar")
#=> ["/", "foo", "bar"]
"""
def split(path) do
FN.split(path)
end
@doc """
Traverses files and directories according to the given glob expression.
The wildcard string looks like an ordinary filename, except that certain
"wildcard characters" are interpreted in a special way. The following
characters are special:
* `?` - Matches one character.
* `*` - Matches any number of characters up to the end of
the filename, the next dot, or the next slash.
* `**` - Two adjacent <c>*</c>'s used as a single pattern will
match all files and zero or more directories and subdirectories.
* `[char1,char2,...]` - Matches any of the characters listed. Two characters
separated by a hyphen will match a range of characters.
* `{item1,item2,...}` - Matches one of the alternatives.
Other characters represent themselves. Only filenames that have exactly
the same character in the same position will match. Note that matching
is case-sensitive; i.e. "a" will not match "A".
## Examples
Imagine you have a directory called `projects` with three Elixir projects
inside of it: `elixir`, `exdoc` and `dynamo`. You can find all `.beam` files
inside their ebin directories all projects as follows:
File.wildcard("projects/*/ebin/**/*.beam")
If you want to search for both `.beam` and `.app` files, you could do:
File.wildcard("projects/*/ebin/**/*.{beam,app}")
"""
def wildcard(glob) when is_binary(glob) do
paths = Erlang.elixir_glob.wildcard binary_to_list(glob)
Enum.map paths, list_to_binary(&1)
end
def wildcard(path) when is_list(path) do
Erlang.elixir_glob.wildcard(path)
end
@doc """
Returns information about a file. If the file exists, it
returns a `{ :ok, info }` tuple, where info is as a
`File.Info` record. Retuns `{ :error, reason }` with
the same reasons as `File.read` if a failure occurs.
"""
def read_info(path, opts // []) do
case :file.read_file_info(path, opts) do
{:ok, fileinfo} ->
{:ok, Info.new fileinfo}
error ->
error
end
end
@doc """
Same as `read_info` but returns the `File.Info` directly and
throws `File.Error` if an error is returned.
"""
def read_info!(path, opts // []) do
case read_info(path, opts) do
{:ok, info} ->
info
{:error, reason} ->
raise File.Error, reason: reason, action: "read file info", path: to_binary(path)
end
end
## Helpers
# Normalize the given path by removing "..".
defp normalize(path), do: normalize(split(path), [])
defp normalize([top|t], [_|acc]) when top == ".." or top == '..' do
normalize t, acc
end
defp normalize([top|t], acc) when top == "." or top == '.' do
normalize t, acc
end
defp normalize([h|t], acc) do
normalize t, [h|acc]
end
defp normalize([], acc) do
join List.reverse(acc)
end
end