v/vlib/os/os.v

1398 lines
35 KiB
V
Raw Normal View History

2020-01-23 21:04:46 +01:00
// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved.
2019-06-23 04:21:30 +02:00
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
2019-06-22 20:20:28 +02:00
module os
2019-10-24 11:36:57 +02:00
pub const (
2020-12-15 08:33:31 +01:00
args = []string{}
2020-05-22 17:36:09 +02:00
max_path_len = 4096
2019-06-22 20:20:28 +02:00
)
2020-08-27 16:57:01 +02:00
// read_bytes returns all bytes read from file in `path`.
2019-11-19 07:53:52 +01:00
pub fn read_bytes(path string) ?[]byte {
2020-12-15 08:33:31 +01:00
mut fp := vfopen(path, 'rb') ?
cseek := C.fseek(fp, 0, C.SEEK_END)
if cseek != 0 {
return error('fseek failed')
}
2019-11-19 07:53:52 +01:00
fsize := C.ftell(fp)
if fsize < 0 {
return error('ftell failed')
}
2019-11-19 07:53:52 +01:00
C.rewind(fp)
mut res := []byte{len: fsize}
2019-12-18 06:22:52 +01:00
nr_read_elements := C.fread(res.data, fsize, 1, fp)
if nr_read_elements == 0 && fsize > 0 {
return error('fread failed')
}
2019-11-19 07:53:52 +01:00
C.fclose(fp)
2019-12-18 06:22:52 +01:00
return res[0..nr_read_elements * fsize]
2019-11-19 07:53:52 +01:00
}
2019-06-22 20:20:28 +02:00
// read_file reads the file in `path` and returns the contents.
2019-08-22 22:19:31 +02:00
pub fn read_file(path string) ?string {
2019-07-24 12:16:45 +02:00
mode := 'rb'
2020-12-15 08:33:31 +01:00
mut fp := vfopen(path, mode) ?
defer {
C.fclose(fp)
}
cseek := C.fseek(fp, 0, C.SEEK_END)
if cseek != 0 {
return error('fseek failed')
}
fsize := C.ftell(fp)
if fsize < 0 {
return error('ftell failed')
}
2019-07-21 12:22:41 +02:00
// C.fseek(fp, 0, SEEK_SET) // same as `C.rewind(fp)` below
C.rewind(fp)
unsafe {
mut str := malloc(fsize + 1)
nelements := C.fread(str, fsize, 1, fp)
if nelements == 0 && fsize > 0 {
free(str)
return error('fread failed')
}
str[fsize] = 0
return str.vstring_with_len(fsize)
}
}
2020-12-15 08:33:31 +01:00
// ***************************** OS ops ************************
// file_size returns the size of the file located in `path`.
2019-06-22 20:20:28 +02:00
pub fn file_size(path string) int {
mut s := C.stat{}
unsafe {
$if windows {
$if tinyc {
C.stat(charptr(path.str), &s)
} $else {
C._wstat(path.to_wide(), voidptr(&s))
}
2020-06-19 12:54:56 +02:00
} $else {
C.stat(charptr(path.str), &s)
2020-06-19 12:54:56 +02:00
}
2019-07-24 12:16:45 +02:00
}
2019-06-27 17:48:49 +02:00
return s.st_size
2019-06-22 20:20:28 +02:00
}
2020-08-27 16:57:01 +02:00
// mv moves files or folders from `src` to `dst`.
pub fn mv(src string, dst string) {
mut rdst := dst
if is_dir(rdst) {
2020-12-15 08:33:31 +01:00
rdst = join_path(rdst.trim_right(path_separator), file_name(src.trim_right(path_separator)))
}
2019-07-24 12:16:45 +02:00
$if windows {
w_src := src.replace('/', '\\')
w_dst := rdst.replace('/', '\\')
C._wrename(w_src.to_wide(), w_dst.to_wide())
2019-07-24 12:16:45 +02:00
} $else {
C.rename(charptr(src.str), charptr(rdst.str))
2019-07-24 12:16:45 +02:00
}
}
2019-06-25 20:41:46 +02:00
2020-08-27 16:57:01 +02:00
// cp copies files or folders from `src` to `dst`.
pub fn cp(src string, dst string) ? {
2019-10-31 22:57:16 +01:00
$if windows {
2020-08-27 16:57:01 +02:00
w_src := src.replace('/', '\\')
w_dst := dst.replace('/', '\\')
if C.CopyFile(w_src.to_wide(), w_dst.to_wide(), false) == 0 {
result := C.GetLastError()
2020-08-27 16:57:01 +02:00
return error_with_code('failed to copy $src to $dst', int(result))
2019-11-02 20:37:29 +01:00
}
} $else {
2020-08-27 16:57:01 +02:00
fp_from := C.open(charptr(src.str), C.O_RDONLY)
2020-06-01 21:11:40 +02:00
if fp_from < 0 { // Check if file opened
2020-08-27 16:57:01 +02:00
return error_with_code('cp: failed to open $src', int(fp_from))
2020-06-01 21:11:40 +02:00
}
2020-08-27 16:57:01 +02:00
fp_to := C.open(charptr(dst.str), C.O_WRONLY | C.O_CREAT | C.O_TRUNC, C.S_IWUSR | C.S_IRUSR)
2020-06-01 21:11:40 +02:00
if fp_to < 0 { // Check if file opened (permissions problems ...)
C.close(fp_from)
2020-12-15 08:33:31 +01:00
return error_with_code('cp (permission): failed to write to $dst (fp_to: $fp_to)',
int(fp_to))
2020-06-01 21:11:40 +02:00
}
mut buf := [1024]byte{}
2020-06-01 21:11:40 +02:00
mut count := 0
for {
// FIXME: use sizeof, bug: 'os__buf' undeclared
2020-12-15 08:33:31 +01:00
// count = C.read(fp_from, buf, sizeof(buf))
2020-06-01 21:11:40 +02:00
count = C.read(fp_from, buf, 1024)
if count == 0 {
break
}
if C.write(fp_to, buf, count) < 0 {
2020-08-27 16:57:01 +02:00
return error_with_code('cp: failed to write to $dst', int(-1))
2020-06-01 21:11:40 +02:00
}
}
from_attr := C.stat{}
2020-12-15 08:33:31 +01:00
unsafe {C.stat(charptr(src.str), &from_attr)}
2020-08-27 16:57:01 +02:00
if C.chmod(charptr(dst.str), from_attr.st_mode) < 0 {
return error_with_code('failed to set permissions for $dst', int(-1))
2020-06-01 21:11:40 +02:00
}
C.close(fp_to)
C.close(fp_from)
2019-11-02 20:37:29 +01:00
}
2019-10-31 22:57:16 +01:00
}
2020-03-02 19:30:04 +01:00
[deprecated]
pub fn cp_r(osource_path string, odest_path string, overwrite bool) ? {
2020-06-23 16:25:24 +02:00
eprintln('warning: `os.cp_r` has been deprecated, use `os.cp_all` instead')
return cp_all(osource_path, odest_path, overwrite)
2020-03-02 19:30:04 +01:00
}
2020-08-27 16:57:01 +02:00
// cp_all will recursively copy `src` to `dst`,
// optionally overwriting files or dirs in `dst`.
pub fn cp_all(src string, dst string, overwrite bool) ? {
2020-12-15 08:33:31 +01:00
source_path := real_path(src)
dest_path := real_path(dst)
if !exists(source_path) {
2019-12-19 22:29:37 +01:00
return error("Source path doesn\'t exist")
2019-11-06 21:05:35 +01:00
}
2019-12-19 22:29:37 +01:00
// single file copy
2020-12-15 08:33:31 +01:00
if !is_dir(source_path) {
adjusted_path := if is_dir(dest_path) { join_path(dest_path, file_name(source_path)) } else { dest_path }
if exists(adjusted_path) {
if overwrite {
2020-12-15 08:33:31 +01:00
rm(adjusted_path)
} else {
2019-11-19 00:25:55 +01:00
return error('Destination file path already exist')
}
2019-11-06 21:05:35 +01:00
}
2020-12-15 08:33:31 +01:00
cp(source_path, adjusted_path) ?
2020-06-29 15:06:26 +02:00
return
2019-11-06 21:05:35 +01:00
}
2020-12-15 08:33:31 +01:00
if !is_dir(dest_path) {
2019-11-06 21:05:35 +01:00
return error('Destination path is not a valid directory')
}
2020-12-15 08:33:31 +01:00
files := ls(source_path) ?
2019-11-06 21:05:35 +01:00
for file in files {
2020-12-15 08:33:31 +01:00
sp := join_path(source_path, file)
dp := join_path(dest_path, file)
if is_dir(sp) {
mkdir(dp) ?
2019-11-06 21:05:35 +01:00
}
2020-03-02 19:30:04 +01:00
cp_all(sp, dp, overwrite) or {
2020-12-15 08:33:31 +01:00
rmdir(dp)
return error(err)
2019-11-06 21:05:35 +01:00
}
}
}
// mv_by_cp first copies the source file, and if it is copied successfully, deletes the source file.
2020-08-27 16:57:01 +02:00
// may be used when you are not sure that the source and target are on the same mount/partition.
2020-06-25 12:06:47 +02:00
pub fn mv_by_cp(source string, target string) ? {
2020-12-15 08:33:31 +01:00
cp(source, target) ?
rm(source) ?
}
// vfopen returns an opened C file, given its path and open mode.
// NB: os.vfopen is useful for compatibility with C libraries, that expect `FILE *`.
// If you write pure V code, os.create or os.open are more convenient.
pub fn vfopen(path string, mode string) ?&C.FILE {
if path.len == 0 {
return error('vfopen called with ""')
}
2020-08-28 14:24:00 +02:00
mut fp := voidptr(0)
$if windows {
2020-08-28 14:24:00 +02:00
fp = C._wfopen(path.to_wide(), mode.to_wide())
} $else {
2020-08-28 14:24:00 +02:00
fp = C.fopen(charptr(path.str), charptr(mode.str))
}
if isnil(fp) {
return error('failed to open file "$path"')
} else {
return fp
}
2019-11-29 17:14:26 +01:00
}
2020-08-27 16:57:01 +02:00
// fileno returns the file descriptor of an opened C file.
pub fn fileno(cfile voidptr) int {
$if windows {
return C._fileno(cfile)
} $else {
mut cfile_casted := &C.FILE(0) // FILE* cfile_casted = 0;
cfile_casted = cfile
// Required on FreeBSD/OpenBSD/NetBSD as stdio.h defines fileno(..) with a macro
// that performs a field access on its argument without casting from void*.
return C.fileno(cfile_casted)
}
}
// read_lines reads the file in `path` into an array of lines.
2019-11-19 00:25:55 +01:00
pub fn read_lines(path string) ?[]string {
2020-12-15 08:33:31 +01:00
buf := read_file(path) ?
2020-01-31 15:59:23 +01:00
return buf.split_into_lines()
2019-06-22 20:20:28 +02:00
}
2020-08-27 16:57:01 +02:00
// read_ulines reads the file in `path` into an array of ustring lines.
2019-11-19 00:25:55 +01:00
fn read_ulines(path string) ?[]ustring {
2020-12-15 08:33:31 +01:00
lines := read_lines(path) ?
2019-06-22 20:20:28 +02:00
// mut ulines := new_array(0, lines.len, sizeof(ustring))
mut ulines := []ustring{}
2019-06-22 20:20:28 +02:00
for myline in lines {
// ulines[i] = ustr
ulines << myline.ustring()
}
return ulines
}
2020-08-27 16:57:01 +02:00
// open_append opens `path` file for appending.
pub fn open_append(path string) ?File {
mut file := File{}
2019-07-24 12:16:45 +02:00
$if windows {
wpath := path.replace('/', '\\').to_wide()
mode := 'ab'
2019-12-19 22:29:37 +01:00
file = File{
2019-07-24 12:16:45 +02:00
cfile: C._wfopen(wpath, mode.to_wide())
}
} $else {
2019-08-22 22:19:31 +02:00
cpath := path.str
2019-12-19 22:29:37 +01:00
file = File{
2019-12-01 08:33:26 +01:00
cfile: C.fopen(charptr(cpath), 'ab')
2019-07-24 12:16:45 +02:00
}
2019-06-22 20:20:28 +02:00
}
if isnil(file.cfile) {
2019-07-24 12:16:45 +02:00
return error('failed to create(append) file "$path"')
2019-06-22 20:20:28 +02:00
}
2020-08-01 23:07:37 +02:00
file.is_opened = true
2019-08-22 22:19:31 +02:00
return file
2019-06-22 20:20:28 +02:00
}
2020-08-27 16:57:01 +02:00
// open_file can be used to open or create a file with custom flags and permissions and returns a `File` object.
2020-01-21 16:58:47 +01:00
pub fn open_file(path string, mode string, options ...int) ?File {
mut flags := 0
for m in mode {
match m {
2020-05-22 17:36:09 +02:00
`r` { flags |= o_rdonly }
`w` { flags |= o_create | o_trunc }
`b` { flags |= o_binary }
2020-05-22 17:36:09 +02:00
`a` { flags |= o_create | o_append }
`s` { flags |= o_sync }
`n` { flags |= o_nonblock }
`c` { flags |= o_noctty }
`+` { flags |= o_rdwr }
2020-01-21 16:58:47 +01:00
else {}
}
}
mut permission := 0o666
2020-01-21 16:58:47 +01:00
if options.len > 0 {
permission = options[0]
}
$if windows {
if permission < 0o600 {
2020-01-21 16:58:47 +01:00
permission = 0x0100
2020-12-15 08:33:31 +01:00
} else {
2020-01-21 16:58:47 +01:00
permission = 0x0100 | 0x0080
}
}
mut p := path
$if windows {
p = path.replace('/', '\\')
}
fd := C.open(charptr(p.str), flags, permission)
if fd == -1 {
return error(posix_get_error_msg(C.errno))
}
cfile := C.fdopen(fd, charptr(mode.str))
if isnil(cfile) {
return error('Failed to open or create file "$path"')
}
return File{
cfile: cfile
fd: fd
2020-08-01 23:07:37 +02:00
is_opened: true
2020-01-21 16:58:47 +01:00
}
}
2020-08-27 16:57:01 +02:00
// vpopen system starts the specified command, waits for it to complete, and returns its code.
2019-12-19 22:29:37 +01:00
fn vpopen(path string) voidptr {
// *C.FILE {
2019-06-22 20:20:28 +02:00
$if windows {
2019-07-24 12:16:45 +02:00
mode := 'rb'
wpath := path.to_wide()
return C._wpopen(wpath, mode.to_wide())
2019-12-19 22:29:37 +01:00
} $else {
2019-07-24 12:16:45 +02:00
cpath := path.str
2020-07-14 00:16:31 +02:00
return C.popen(charptr(cpath), 'r')
2019-06-22 20:20:28 +02:00
}
}
2020-12-15 08:33:31 +01:00
fn posix_wait4_to_exit_status(waitret int) (int, bool) {
$if windows {
2020-12-15 08:33:31 +01:00
return waitret, false
2019-12-19 22:29:37 +01:00
} $else {
mut ret := 0
mut is_signaled := true
// (see man system, man 2 waitpid: C macro WEXITSTATUS section)
2019-12-19 22:29:37 +01:00
if C.WIFEXITED(waitret) {
ret = C.WEXITSTATUS(waitret)
is_signaled = false
2020-12-15 08:33:31 +01:00
} else if C.WIFSIGNALED(waitret) {
2019-12-19 22:29:37 +01:00
ret = C.WTERMSIG(waitret)
is_signaled = true
}
2020-12-15 08:33:31 +01:00
return ret, is_signaled
}
}
2020-01-21 16:58:47 +01:00
// posix_get_error_msg return error code representation in string.
pub fn posix_get_error_msg(code int) string {
ptr_text := C.strerror(code) // voidptr?
if ptr_text == 0 {
return ''
}
return tos3(ptr_text)
}
2020-08-27 16:57:01 +02:00
// vpclose will close a file pointer opened with `vpopen`.
fn vpclose(f voidptr) int {
$if windows {
2019-12-07 20:42:13 +01:00
return C._pclose(f)
2019-12-19 22:29:37 +01:00
} $else {
2020-12-15 08:33:31 +01:00
ret, _ := posix_wait4_to_exit_status(C.pclose(f))
return ret
}
}
2019-10-24 20:58:28 +02:00
pub struct Result {
2019-08-22 22:19:31 +02:00
pub:
exit_code int
2019-12-19 22:29:37 +01:00
output string
// stderr string // TODO
2019-08-22 22:19:31 +02:00
}
2020-08-27 16:57:01 +02:00
// system works like `exec`, but only returns a return code.
pub fn system(cmd string) int {
2019-12-19 22:29:37 +01:00
// if cmd.contains(';') || cmd.contains('&&') || cmd.contains('||') || cmd.contains('\n') {
// TODO remove panic
// panic(';, &&, || and \\n are not allowed in shell commands')
// }
2019-12-07 13:51:00 +01:00
mut ret := 0
$if windows {
// overcome bug in system & _wsystem (cmd) when first char is quote `"`
wcmd := if cmd.len > 1 && cmd[0] == `"` && cmd[1] != `"` { '"$cmd"' } else { cmd }
unsafe {
ret = C._wsystem(wcmd.to_wide())
}
} $else {
$if ios {
unsafe {
2020-12-15 08:33:31 +01:00
arg := [c'/bin/sh', c'-c', byteptr(cmd.str), 0]
pid := 0
ret = C.posix_spawn(&pid, '/bin/sh', 0, 0, arg.data, 0)
status := 0
ret = C.waitpid(pid, &status, 0)
if C.WIFEXITED(status) {
ret = C.WEXITSTATUS(status)
}
}
} $else {
unsafe {
ret = C.system(charptr(cmd.str))
}
}
}
if ret == -1 {
print_c_errno()
}
$if !windows {
2020-12-15 08:33:31 +01:00
pret, is_signaled := posix_wait4_to_exit_status(ret)
if is_signaled {
2019-12-19 22:29:37 +01:00
println('Terminated by signal ${ret:2d} (' + sigint_to_signal_name(pret) + ')')
}
ret = pret
}
return ret
2019-06-22 20:20:28 +02:00
}
2020-08-27 16:57:01 +02:00
// sigint_to_signal_name will translate `si` signal integer code to it's string code representation.
pub fn sigint_to_signal_name(si int) string {
// POSIX signals:
match si {
2020-12-15 08:33:31 +01:00
1 { return 'SIGHUP' }
2 { return 'SIGINT' }
3 { return 'SIGQUIT' }
4 { return 'SIGILL' }
6 { return 'SIGABRT' }
8 { return 'SIGFPE' }
9 { return 'SIGKILL' }
11 { return 'SIGSEGV' }
13 { return 'SIGPIPE' }
14 { return 'SIGALRM' }
15 { return 'SIGTERM' }
else {}
}
$if linux {
// From `man 7 signal` on linux:
match si {
// TODO dependent on platform
// works only on x86/ARM/most others
2020-12-15 08:33:31 +01:00
10 /* , 30, 16 */ { return 'SIGUSR1' }
12 /* , 31, 17 */ { return 'SIGUSR2' }
17 /* , 20, 18 */ { return 'SIGCHLD' }
18 /* , 19, 25 */ { return 'SIGCONT' }
19 /* , 17, 23 */ { return 'SIGSTOP' }
20 /* , 18, 24 */ { return 'SIGTSTP' }
21 /* , 26 */ { return 'SIGTTIN' }
22 /* , 27 */ { return 'SIGTTOU' }
2019-12-19 22:29:37 +01:00
// /////////////////////////////
2020-12-15 08:33:31 +01:00
5 { return 'SIGTRAP' }
7 { return 'SIGBUS' }
else {}
2020-01-21 16:58:47 +01:00
}
}
return 'unknown'
}
const (
2020-05-22 17:36:09 +02:00
f_ok = 0
x_ok = 1
w_ok = 2
r_ok = 4
)
2020-08-27 16:57:01 +02:00
// exists returns true if `path` (file or directory) exists.
pub fn exists(path string) bool {
$if windows {
p := path.replace('/', '\\')
2020-05-22 17:36:09 +02:00
return C._waccess(p.to_wide(), f_ok) != -1
2019-07-24 12:16:45 +02:00
} $else {
2020-07-14 00:16:31 +02:00
return C.access(charptr(path.str), f_ok) != -1
}
}
2020-08-27 16:57:01 +02:00
// is_executable returns `true` if `path` is executable.
pub fn is_executable(path string) bool {
2020-12-15 08:33:31 +01:00
$if windows {
// NB: https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/access-waccess?view=vs-2019
// i.e. there is no X bit there, the modes can be:
// 00 Existence only
// 02 Write-only
// 04 Read-only
// 06 Read and write
p := real_path(path)
return (exists(p) && p.ends_with('.exe'))
}
$if solaris {
statbuf := C.stat{}
unsafe {
if C.stat(charptr(path.str), &statbuf) != 0 {
return false
}
2020-08-01 23:07:37 +02:00
}
2020-12-15 08:33:31 +01:00
return (int(statbuf.st_mode) & (s_ixusr | s_ixgrp | s_ixoth)) != 0
2020-08-01 23:07:37 +02:00
}
2020-12-15 08:33:31 +01:00
return C.access(charptr(path.str), x_ok) != -1
}
2020-08-27 16:57:01 +02:00
// is_writable returns `true` if `path` is writable.
pub fn is_writable(path string) bool {
2020-12-15 08:33:31 +01:00
$if windows {
p := path.replace('/', '\\')
return C._waccess(p.to_wide(), w_ok) != -1
} $else {
return C.access(charptr(path.str), w_ok) != -1
}
}
2020-08-27 16:57:01 +02:00
// is_readable returns `true` if `path` is readable.
pub fn is_readable(path string) bool {
2020-12-15 08:33:31 +01:00
$if windows {
p := path.replace('/', '\\')
return C._waccess(p.to_wide(), r_ok) != -1
} $else {
return C.access(charptr(path.str), r_ok) != -1
}
2019-06-22 20:20:28 +02:00
}
[deprecated]
pub fn file_exists(_path string) bool {
2020-06-23 16:25:24 +02:00
eprintln('warning: `os.file_exists` has been deprecated, use `os.exists` instead')
return exists(_path)
}
// rm removes file in `path`.
2020-06-24 14:01:19 +02:00
pub fn rm(path string) ? {
2019-07-24 12:16:45 +02:00
$if windows {
2020-06-24 14:01:19 +02:00
rc := C._wremove(path.to_wide())
if rc == -1 {
2020-12-15 08:33:31 +01:00
// TODO: proper error as soon as it's supported on windows
2020-06-24 14:01:19 +02:00
return error('Failed to remove "$path"')
}
2019-12-19 22:29:37 +01:00
} $else {
2020-07-14 00:16:31 +02:00
rc := C.remove(charptr(path.str))
2020-06-24 14:01:19 +02:00
if rc == -1 {
return error(posix_get_error_msg(C.errno))
}
2019-07-24 12:16:45 +02:00
}
// C.unlink(path.cstr())
2019-06-22 20:20:28 +02:00
}
2020-08-27 16:57:01 +02:00
// rmdir removes a specified directory.
pub fn rmdir(path string) ? {
$if windows {
rc := C.RemoveDirectory(path.to_wide())
2020-06-26 16:24:36 +02:00
if rc == 0 {
// https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-removedirectorya - 0 is failure
return error('Failed to remove "$path"')
}
} $else {
2020-07-14 00:16:31 +02:00
rc := C.rmdir(charptr(path.str))
if rc == -1 {
return error(posix_get_error_msg(C.errno))
}
}
2019-06-22 20:20:28 +02:00
}
2020-03-03 15:02:50 +01:00
[deprecated]
pub fn rmdir_recursive(path string) {
2020-06-23 16:25:24 +02:00
eprintln('warning: `os.rmdir_recursive` has been deprecated, use `os.rmdir_all` instead')
rmdir_all(path)
2020-03-03 15:02:50 +01:00
}
2020-08-27 16:57:01 +02:00
// rmdir_all recursively removes the specified directory.
pub fn rmdir_all(path string) ? {
mut ret_err := ''
2020-12-15 08:33:31 +01:00
items := ls(path) ?
for item in items {
2020-12-15 08:33:31 +01:00
if is_dir(join_path(path, item)) {
rmdir_all(join_path(path, item))
}
2020-12-15 08:33:31 +01:00
rm(join_path(path, item)) or { ret_err = err }
}
2020-12-15 08:33:31 +01:00
rmdir(path) or { ret_err = err }
if ret_err.len > 0 {
return error(ret_err)
}
}
2020-08-27 16:57:01 +02:00
// is_dir_empty will return a `bool` whether or not `path` is empty.
pub fn is_dir_empty(path string) bool {
2020-12-15 08:33:31 +01:00
items := ls(path) or { return true }
return items.len == 0
}
2020-08-27 16:57:01 +02:00
// print_c_errno will print the current value of `C.errno`.
2019-06-22 20:20:28 +02:00
fn print_c_errno() {
e := C.errno
se := tos_clone(byteptr(C.strerror(C.errno)))
println('errno=$e err=$se')
2019-06-22 20:20:28 +02:00
}
2020-08-27 16:57:01 +02:00
// file_ext will return the part after the last occurence of `.` in `path`.
// The `.` is included.
2020-03-26 14:18:08 +01:00
pub fn file_ext(path string) string {
2020-12-15 08:33:31 +01:00
pos := path.last_index('.') or { return '' }
2020-03-07 22:26:26 +01:00
return path[pos..]
2019-06-26 11:28:06 +02:00
}
// dir returns all but the last element of path, typically the path's directory.
// After dropping the final element, trailing slashes are removed.
// If the path is empty, dir returns ".". If the path consists entirely of separators,
// dir returns a single separator.
// The returned path does not end in a separator unless it is the root directory.
2019-07-16 01:57:03 +02:00
pub fn dir(path string) string {
if path == '' {
return '.'
}
2020-12-15 08:33:31 +01:00
pos := path.last_index(path_separator) or { return '.' }
2020-03-07 22:26:26 +01:00
return path[..pos]
}
// base returns the last element of path.
// Trailing path separators are removed before extracting the last element.
// If the path is empty, base returns ".". If the path consists entirely of separators, base returns a
// single separator.
pub fn base(path string) string {
if path == '' {
return '.'
}
if path == path_separator {
return path_separator
}
if path.ends_with(path_separator) {
2020-12-15 08:33:31 +01:00
path2 := path[..path.len - 1]
pos := path2.last_index(path_separator) or { return path2.clone() }
return path2[pos + 1..]
2020-03-07 22:26:26 +01:00
}
2020-12-15 08:33:31 +01:00
pos := path.last_index(path_separator) or { return path.clone() }
return path[pos + 1..]
2019-06-22 20:20:28 +02:00
}
2020-08-27 16:57:01 +02:00
// file_name will return all characters found after the last occurence of `path_separator`.
// file extension is included.
2020-03-19 15:49:07 +01:00
pub fn file_name(path string) string {
return path.all_after_last(path_separator)
2019-06-22 20:20:28 +02:00
}
2020-08-27 16:57:01 +02:00
// input returns a one-line string from stdin, after printing a prompt.
2020-04-25 21:03:51 +02:00
pub fn input(prompt string) string {
print(prompt)
flush()
return get_line()
}
2019-08-22 22:19:31 +02:00
// get_line returns a one-line string from stdin
2019-06-22 20:20:28 +02:00
pub fn get_line() string {
2019-12-19 22:29:37 +01:00
str := get_raw_line()
2019-09-15 18:07:40 +02:00
$if windows {
return str.trim_right('\r\n')
2019-12-19 22:29:37 +01:00
} $else {
2019-09-15 18:07:40 +02:00
return str.trim_right('\n')
}
2019-06-22 20:20:28 +02:00
}
2020-08-27 16:57:01 +02:00
// get_raw_line returns a one-line string from stdin along with '\n' if there is any.
pub fn get_raw_line() string {
2019-12-19 22:29:37 +01:00
$if windows {
2020-02-22 12:41:24 +01:00
unsafe {
max_line_chars := 256
buf := malloc(max_line_chars * 2)
2020-05-22 17:36:09 +02:00
h_input := C.GetStdHandle(std_input_handle)
2020-02-22 12:41:24 +01:00
mut bytes_read := 0
if is_atty(0) > 0 {
2020-12-15 08:33:31 +01:00
C.ReadConsole(h_input, buf, max_line_chars * 2, C.LPDWORD(&bytes_read),
0)
2020-02-22 12:41:24 +01:00
return string_from_wide2(&u16(buf), bytes_read)
}
2020-02-22 12:41:24 +01:00
mut offset := 0
for {
pos := buf + offset
res := C.ReadFile(h_input, pos, 1, C.LPDWORD(&bytes_read), 0)
2020-02-22 12:41:24 +01:00
if !res || bytes_read == 0 {
2020-12-15 08:33:31 +01:00
break
2020-02-22 12:41:24 +01:00
}
if *pos == `\n` || *pos == `\r` {
offset++
break
}
offset++
}
return buf.vstring_with_len(offset)
2019-12-19 22:29:37 +01:00
}
} $else {
max := size_t(0)
mut buf := charptr(0)
2020-04-03 10:52:48 +02:00
nr_chars := C.getline(&buf, &max, C.stdin)
2020-12-15 08:33:31 +01:00
// defer { unsafe{ free(buf) } }
if nr_chars == 0 || nr_chars == -1 {
2019-12-19 22:29:37 +01:00
return ''
}
return tos3(buf)
2020-12-15 08:33:31 +01:00
// res := tos_clone(buf)
// return res
2019-12-19 22:29:37 +01:00
}
}
2020-08-27 16:57:01 +02:00
// get_raw_stdin will get the raw input from stdin.
pub fn get_raw_stdin() []byte {
$if windows {
unsafe {
block_bytes := 512
mut buf := malloc(block_bytes)
h_input := C.GetStdHandle(std_input_handle)
mut bytes_read := 0
mut offset := 0
for {
pos := buf + offset
res := C.ReadFile(h_input, pos, block_bytes, C.LPDWORD(&bytes_read), 0)
2020-05-31 12:57:26 +02:00
offset += bytes_read
if !res {
break
}
2020-12-15 08:33:31 +01:00
buf = v_realloc(buf, offset + block_bytes + (block_bytes - bytes_read))
}
2020-05-31 12:57:26 +02:00
C.CloseHandle(h_input)
2020-12-15 08:33:31 +01:00
return array{
element_size: 1
data: voidptr(buf)
len: offset
cap: offset
}
}
} $else {
panic('get_raw_stdin not implemented on this platform...')
}
}
2020-08-27 16:57:01 +02:00
// get_lines returns an array of strings read from from stdin.
// reading is stopped when an empty line is read.
2019-07-24 18:14:13 +02:00
pub fn get_lines() []string {
2019-12-19 22:29:37 +01:00
mut line := ''
mut inputstr := []string{}
2019-12-19 22:29:37 +01:00
for {
line = get_line()
if line.len <= 0 {
2019-12-19 22:29:37 +01:00
break
}
line = line.trim_space()
inputstr << line
}
return inputstr
2019-07-24 18:14:13 +02:00
}
2020-08-27 16:57:01 +02:00
// get_lines_joined returns a string of the values read from from stdin.
// reading is stopped when an empty line is read.
2019-07-24 18:14:13 +02:00
pub fn get_lines_joined() string {
2019-10-26 19:21:07 +02:00
mut line := ''
mut inputstr := ''
for {
line = get_line()
if line.len <= 0 {
break
}
line = line.trim_space()
inputstr += line
}
return inputstr
2019-07-24 18:14:13 +02:00
}
2019-10-26 19:21:07 +02:00
// user_os returns current user operating system name.
2019-06-22 20:20:28 +02:00
pub fn user_os() string {
$if linux {
return 'linux'
}
2019-12-03 14:29:24 +01:00
$if macos {
return 'macos'
2019-06-22 20:20:28 +02:00
}
$if windows {
return 'windows'
}
2019-07-15 17:24:40 +02:00
$if freebsd {
2019-08-22 22:19:31 +02:00
return 'freebsd'
}
2019-07-21 12:22:41 +02:00
$if openbsd {
2019-08-22 22:19:31 +02:00
return 'openbsd'
}
2019-07-21 12:22:41 +02:00
$if netbsd {
2019-08-22 22:19:31 +02:00
return 'netbsd'
}
2019-07-21 12:22:41 +02:00
$if dragonfly {
2019-08-22 22:19:31 +02:00
return 'dragonfly'
}
2019-12-19 22:29:37 +01:00
$if android {
return 'android'
}
2019-09-26 23:30:41 +02:00
$if solaris {
return 'solaris'
}
2019-12-03 09:26:47 +01:00
$if haiku {
return 'haiku'
}
2019-06-22 20:20:28 +02:00
return 'unknown'
}
// home_dir returns path to user's home directory.
2019-06-22 20:20:28 +02:00
pub fn home_dir() string {
$if windows {
2020-12-15 08:33:31 +01:00
return getenv('USERPROFILE')
} $else {
2020-12-15 08:33:31 +01:00
// println('home_dir() call')
// res:= os.getenv('HOME')
// println('res="$res"')
return getenv('HOME')
2019-06-22 20:20:28 +02:00
}
}
2019-08-22 22:19:31 +02:00
// write_file writes `text` data to a file in `path`.
pub fn write_file(path string, text string) ? {
2020-12-15 08:33:31 +01:00
mut f := create(path) ?
f.write(text.bytes())
2019-06-22 20:20:28 +02:00
f.close()
}
// write_file_array writes the data in `buffer` to a file in `path`.
pub fn write_file_array(path string, buffer array) ? {
2020-12-15 08:33:31 +01:00
mut f := create(path) ?
f.write_bytes_at(buffer.data, (buffer.len * buffer.element_size), 0)
f.close()
}
2020-08-27 16:57:01 +02:00
// read_file_array reads an array of `T` values from file `path`.
pub fn read_file_array<T>(path string) []T {
a := T{}
tsize := int(sizeof(a))
// prepare for reading, get current file size
2020-08-28 14:24:00 +02:00
mut fp := vfopen(path, 'rb') or { return array{} }
C.fseek(fp, 0, C.SEEK_END)
fsize := C.ftell(fp)
C.rewind(fp)
// read the actual data from the file
len := fsize / tsize
buf := malloc(fsize)
C.fread(buf, fsize, 1, fp)
C.fclose(fp)
2020-12-15 08:33:31 +01:00
return array{
element_size: tsize
data: buf
len: len
cap: len
}
}
2019-12-03 11:08:57 +01:00
pub fn on_segfault(f voidptr) {
$if windows {
return
}
2019-12-03 14:29:24 +01:00
$if macos {
2020-12-15 08:33:31 +01:00
C.printf('TODO')
2020-03-22 14:47:43 +01:00
/*
mut sa := C.sigaction{}
2020-03-22 14:47:43 +01:00
C.memset(&sa, 0, sizeof(C.sigaction_size))
2019-06-27 17:48:49 +02:00
C.sigemptyset(&sa.sa_mask)
sa.sa_sigaction = f
2019-08-22 22:19:31 +02:00
sa.sa_flags = C.SA_SIGINFO
C.sigaction(C.SIGSEGV, &sa, 0)
2020-03-22 14:47:43 +01:00
*/
}
}
2019-11-22 17:00:56 +01:00
// executable returns the path name of the executable that started the current
// process.
2019-07-15 23:22:29 +02:00
pub fn executable() string {
$if linux {
2020-05-22 17:36:09 +02:00
mut result := vcalloc(max_path_len)
2020-07-14 00:16:31 +02:00
count := C.readlink('/proc/self/exe', charptr(result), max_path_len)
if count < 0 {
2019-11-28 09:46:52 +01:00
eprintln('os.executable() failed at reading /proc/self/exe to get exe path')
2020-03-13 22:19:02 +01:00
return executable_fallback()
}
2020-12-15 08:33:31 +01:00
return unsafe {result.vstring()}
}
$if windows {
max := 512
size := max * 2 // max_path_len * sizeof(wchar_t)
mut result := &u16(vcalloc(size))
2019-12-19 22:29:37 +01:00
len := C.GetModuleFileName(0, result, max)
// determine if the file is a windows symlink
attrs := C.GetFileAttributesW(result)
2020-12-15 08:33:31 +01:00
is_set := attrs & 0x400 // FILE_ATTRIBUTE_REPARSE_POINT
if is_set != 0 { // it's a windows symlink
// gets handle with GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0
file := C.CreateFile(result, 0x80000000, 1, 0, 3, 0x80, 0)
2020-12-12 23:06:02 +01:00
if file != voidptr(-1) {
final_path := &u16(vcalloc(size))
// https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfinalpathnamebyhandlew
final_len := C.GetFinalPathNameByHandleW(file, final_path, size, 0)
if final_len < size {
ret := string_from_wide2(final_path, final_len)
// remove '\\?\' from beginning (see link above)
return ret[4..]
2020-12-15 08:33:31 +01:00
} else {
eprintln('os.executable() saw that the executable file path was too long')
}
}
C.CloseHandle(file)
}
2019-07-24 12:16:45 +02:00
return string_from_wide2(result, len)
}
2019-12-03 14:29:24 +01:00
$if macos {
2020-05-22 17:36:09 +02:00
mut result := vcalloc(max_path_len)
2019-08-22 22:19:31 +02:00
pid := C.getpid()
2020-05-22 17:36:09 +02:00
ret := proc_pidpath(pid, result, max_path_len)
2019-12-19 22:29:37 +01:00
if ret <= 0 {
2019-11-28 09:46:52 +01:00
eprintln('os.executable() failed at calling proc_pidpath with pid: $pid . proc_pidpath returned $ret ')
2020-03-13 22:19:02 +01:00
return executable_fallback()
2019-08-22 22:19:31 +02:00
}
2020-12-15 08:33:31 +01:00
return unsafe {result.vstring()}
}
2019-07-16 16:19:52 +02:00
$if freebsd {
2020-05-22 17:36:09 +02:00
mut result := vcalloc(max_path_len)
2020-12-15 08:33:31 +01:00
mib := [1 /* CTL_KERN */, 14 /* KERN_PROC */, 12 /* KERN_PROC_PATHNAME */, -1]
2020-05-22 17:36:09 +02:00
size := max_path_len
2020-12-15 08:33:31 +01:00
unsafe {C.sysctl(mib.data, 4, result, &size, 0, 0)}
return unsafe {result.vstring()}
2019-08-22 22:19:31 +02:00
}
2020-03-13 22:19:02 +01:00
// "Sadly there is no way to get the full path of the executed file in OpenBSD."
2020-12-15 08:33:31 +01:00
$if openbsd {
}
$if solaris {
}
$if haiku {
}
$if netbsd {
2020-05-22 17:36:09 +02:00
mut result := vcalloc(max_path_len)
2020-07-14 00:16:31 +02:00
count := C.readlink('/proc/curproc/exe', charptr(result), max_path_len)
if count < 0 {
2019-11-28 09:46:52 +01:00
eprintln('os.executable() failed at reading /proc/curproc/exe to get exe path')
2020-03-13 22:19:02 +01:00
return executable_fallback()
}
2020-12-15 08:33:31 +01:00
return unsafe {result.vstring_with_len(count)}
2019-08-22 22:19:31 +02:00
}
$if dragonfly {
2020-05-22 17:36:09 +02:00
mut result := vcalloc(max_path_len)
2020-07-14 00:16:31 +02:00
count := C.readlink('/proc/curproc/file', charptr(result), max_path_len)
if count < 0 {
2019-11-28 09:46:52 +01:00
eprintln('os.executable() failed at reading /proc/curproc/file to get exe path')
2020-03-13 22:19:02 +01:00
return executable_fallback()
}
2020-12-15 08:33:31 +01:00
return unsafe {result.vstring_with_len(count)}
2019-08-22 22:19:31 +02:00
}
2020-03-13 22:19:02 +01:00
return executable_fallback()
}
2020-08-27 16:57:01 +02:00
// executable_fallback is used when there is not a more platform specific and accurate implementation.
// It relies on path manipulation of os.args[0] and os.wd_at_startup, so it may not work properly in
2020-03-13 22:19:02 +01:00
// all cases, but it should be better, than just using os.args[0] directly.
fn executable_fallback() string {
2020-12-15 08:33:31 +01:00
if args.len == 0 {
// we are early in the bootstrap, os.args has not been initialized yet :-|
return ''
}
2020-12-15 08:33:31 +01:00
mut exepath := args[0]
$if windows {
if !exepath.contains('.exe') {
exepath += '.exe'
}
}
2020-12-15 08:33:31 +01:00
if !is_abs_path(exepath) {
if exepath.contains(path_separator) {
exepath = join_path(wd_at_startup, exepath)
} else {
2020-03-13 22:19:02 +01:00
// no choice but to try to walk the PATH folders :-| ...
2020-12-15 08:33:31 +01:00
foundpath := find_abs_path_of_executable(exepath) or { '' }
2020-03-13 22:19:02 +01:00
if foundpath.len > 0 {
exepath = foundpath
}
}
}
2020-12-15 08:33:31 +01:00
exepath = real_path(exepath)
2020-03-13 22:19:02 +01:00
return exepath
}
// find_exe_path walks the environment PATH, just like most shell do, it returns
// the absolute path of the executable if found
pub fn find_abs_path_of_executable(exepath string) ?string {
2020-12-15 08:33:31 +01:00
if is_abs_path(exepath) {
return real_path(exepath)
2020-03-13 22:19:02 +01:00
}
mut res := ''
2020-12-15 08:33:31 +01:00
paths := getenv('PATH').split(path_delimiter)
2020-03-13 22:19:02 +01:00
for p in paths {
2020-12-15 08:33:31 +01:00
found_abs_path := join_path(p, exepath)
if exists(found_abs_path) && is_executable(found_abs_path) {
2020-03-13 22:19:02 +01:00
res = found_abs_path
break
}
}
2020-12-15 08:33:31 +01:00
if res.len > 0 {
return real_path(res)
2020-03-13 22:19:02 +01:00
}
return error('failed to find executable')
}
2020-08-27 16:57:01 +02:00
// exists_in_system_path returns `true` if `prog` exists in the system's PATH
pub fn exists_in_system_path(prog string) bool {
2020-12-15 08:33:31 +01:00
find_abs_path_of_executable(prog) or { return false }
return true
}
[deprecated]
pub fn dir_exists(path string) bool {
2020-06-23 16:25:24 +02:00
eprintln('warning: `os.dir_exists` has been deprecated, use `os.is_dir` instead')
return is_dir(path)
}
2020-08-27 16:57:01 +02:00
// is_dir returns a `bool` indicating whether the given `path` is a directory.
pub fn is_dir(path string) bool {
$if windows {
2020-05-16 16:12:23 +02:00
w_path := path.replace('/', '\\')
attr := C.GetFileAttributesW(w_path.to_wide())
2019-12-05 09:25:55 +01:00
if attr == u32(C.INVALID_FILE_ATTRIBUTES) {
return false
}
2019-12-04 21:53:11 +01:00
if int(attr) & C.FILE_ATTRIBUTE_DIRECTORY != 0 {
return true
}
return false
2019-12-19 22:29:37 +01:00
} $else {
statbuf := C.stat{}
if unsafe {C.stat(charptr(path.str), &statbuf)} != 0 {
return false
}
2019-07-31 10:32:00 +02:00
// ref: https://code.woboq.org/gcc/include/sys/stat.h.html
2020-12-15 08:33:31 +01:00
val := int(statbuf.st_mode) & s_ifmt
2020-05-22 17:36:09 +02:00
return val == s_ifdir
2019-08-22 22:19:31 +02:00
}
}
2020-09-04 22:27:52 +02:00
// is_file returns a `bool` indicating whether the given `path` is a file.
pub fn is_file(path string) bool {
return exists(path) && !is_dir(path)
}
2020-08-27 16:57:01 +02:00
// is_link returns a boolean indicating whether `path` is a link.
2019-12-04 21:53:11 +01:00
pub fn is_link(path string) bool {
2019-12-04 22:14:23 +01:00
$if windows {
return false // TODO
} $else {
statbuf := C.stat{}
2020-07-14 00:16:31 +02:00
if C.lstat(charptr(path.str), &statbuf) != 0 {
2019-12-04 22:14:23 +01:00
return false
}
2020-05-22 17:36:09 +02:00
return int(statbuf.st_mode) & s_ifmt == s_iflnk
2019-12-04 21:53:11 +01:00
}
}
2020-08-27 16:57:01 +02:00
// chdir changes the current working directory to the new directory in `path`.
2019-06-30 16:11:55 +02:00
pub fn chdir(path string) {
$if windows {
2019-07-24 12:16:45 +02:00
C._wchdir(path.to_wide())
2019-12-19 22:29:37 +01:00
} $else {
2020-07-14 00:16:31 +02:00
C.chdir(charptr(path.str))
2019-08-22 22:19:31 +02:00
}
}
2020-08-27 16:57:01 +02:00
// getwd returns the absolute path of the current directory.
2019-11-29 17:14:26 +01:00
pub fn getwd() string {
$if windows {
2020-05-22 17:36:09 +02:00
max := 512 // max_path_len * sizeof(wchar_t)
buf := &u16(vcalloc(max * 2))
if C._wgetcwd(buf, max) == 0 {
return ''
}
2019-07-24 12:16:45 +02:00
return string_from_wide(buf)
2019-12-19 22:29:37 +01:00
} $else {
buf := vcalloc(512)
2020-07-14 00:16:31 +02:00
if C.getcwd(charptr(buf), 512) == 0 {
return ''
}
2020-12-15 08:33:31 +01:00
return unsafe {buf.vstring()}
2019-07-24 12:16:45 +02:00
}
}
2020-08-27 16:57:01 +02:00
// real_path returns the full absolute path for fpath, with all relative ../../, symlinks and so on resolved.
// See http://pubs.opengroup.org/onlinepubs/9699919799/functions/realpath.html
// Also https://insanecoding.blogspot.com/2007/11/pathmax-simply-isnt.html
2019-12-19 22:29:37 +01:00
// and https://insanecoding.blogspot.com/2007/11/implementing-realpath-in-c.html
// NB: this particular rabbit hole is *deep* ...
2020-03-20 16:41:18 +01:00
pub fn real_path(fpath string) string {
2020-05-22 17:36:09 +02:00
mut fullpath := vcalloc(max_path_len)
2019-12-01 08:33:26 +01:00
mut ret := charptr(0)
$if windows {
2020-12-12 23:06:02 +01:00
ret = charptr(C._fullpath(charptr(fullpath), charptr(fpath.str), max_path_len))
2019-11-23 18:56:22 +01:00
if ret == 0 {
return fpath
2019-11-29 17:14:26 +01:00
}
2019-11-28 09:46:52 +01:00
} $else {
2020-07-14 00:16:31 +02:00
ret = charptr(C.realpath(charptr(fpath.str), charptr(fullpath)))
2019-11-14 23:07:38 +01:00
if ret == 0 {
return fpath
2019-11-29 17:14:26 +01:00
}
}
2020-12-15 08:33:31 +01:00
res := unsafe {fullpath.vstring()}
return normalize_drive_letter(res)
}
fn normalize_drive_letter(path string) string {
// normalize_drive_letter is needed, because a path like c:\nv\.bin (note the small `c`)
// in %PATH is NOT recognized by cmd.exe (and probably other programs too)...
// Capital drive letters do work fine.
$if !windows {
return path
}
2020-12-15 08:33:31 +01:00
if path.len > 2 &&
path[0] >= `a` && path[0] <= `z` && path[1] == `:` && path[2] == path_separator[0] {
unsafe {
x := &path.str[0]
(*x) = *x - 32
}
}
return path
}
2020-08-27 16:57:01 +02:00
// is_abs_path returns `true` if `path` is absolute.
2020-03-10 16:09:37 +01:00
pub fn is_abs_path(path string) bool {
2020-03-07 22:26:26 +01:00
$if windows {
2020-12-15 08:33:31 +01:00
return path[0] == `/` || // incase we're in MingGW bash
2020-03-07 22:26:26 +01:00
(path[0].is_letter() && path[1] == `:`)
}
return path[0] == `/`
}
2020-08-27 16:57:01 +02:00
// join_path returns a path as string from input string parameter(s).
2020-03-09 02:23:34 +01:00
pub fn join_path(base string, dirs ...string) string {
mut result := []string{}
2020-03-07 22:26:26 +01:00
result << base.trim_right('\\/')
for d in dirs {
result << d
}
return result.join(path_separator)
}
2020-08-27 16:57:01 +02:00
// walk_ext returns a recursive list of all files in `path` ending with `ext`.
pub fn walk_ext(path string, ext string) []string {
2020-12-15 08:33:31 +01:00
if !is_dir(path) {
2020-02-21 20:14:01 +01:00
return []
2019-12-19 22:29:37 +01:00
}
2020-12-15 08:33:31 +01:00
mut files := ls(path) or { return [] }
mut res := []string{}
2020-12-15 08:33:31 +01:00
separator := if path.ends_with(path_separator) { '' } else { path_separator }
for file in files {
2019-08-16 14:05:11 +02:00
if file.starts_with('.') {
2019-08-22 22:19:31 +02:00
continue
}
p := path + separator + file
2020-12-15 08:33:31 +01:00
if is_dir(p) && !is_link(p) {
2019-08-22 22:19:31 +02:00
res << walk_ext(p, ext)
2020-12-15 08:33:31 +01:00
} else if file.ends_with(ext) {
2019-08-22 22:19:31 +02:00
res << p
}
}
return res
}
2020-08-27 16:57:01 +02:00
// walk recursively traverses the given directory `path`.
2019-10-24 14:17:09 +02:00
// When a file is encountred it will call the callback function with current file as argument.
2020-12-15 08:33:31 +01:00
pub fn walk(path string, f fn (string)) {
if !is_dir(path) {
2020-02-21 20:14:01 +01:00
return
2019-12-19 22:29:37 +01:00
}
2020-12-15 08:33:31 +01:00
mut files := ls(path) or { return }
2019-10-24 14:17:09 +02:00
for file in files {
2020-12-15 08:33:31 +01:00
p := path + path_separator + file
if is_dir(p) && !is_link(p) {
2020-02-11 10:26:46 +01:00
walk(p, f)
2020-12-15 08:33:31 +01:00
} else if exists(p) {
2020-02-11 10:26:46 +01:00
f(p)
2019-10-24 14:17:09 +02:00
}
}
return
}
2020-12-15 08:33:31 +01:00
// signal will assign `handler` callback to be called when `signum` signal is recieved.
2019-07-02 22:13:23 +02:00
pub fn signal(signum int, handler voidptr) {
2020-12-15 08:33:31 +01:00
unsafe {C.signal(signum, handler)}
2019-07-02 22:13:23 +02:00
}
2020-08-27 16:57:01 +02:00
// fork will fork the current system process and return the pid of the fork.
2019-07-14 04:18:54 +02:00
pub fn fork() int {
2019-11-16 00:30:50 +01:00
mut pid := -1
$if !windows {
2019-11-16 00:30:50 +01:00
pid = C.fork()
}
2019-11-16 00:30:50 +01:00
$if windows {
panic('os.fork not supported in windows') // TODO
}
return pid
2019-07-14 04:18:54 +02:00
}
2020-08-27 16:57:01 +02:00
// wait blocks the calling process until one of its child processes exits or a signal is received.
// After child process terminates, parent continues its execution after wait system call instruction.
2019-07-14 04:18:54 +02:00
pub fn wait() int {
2019-11-16 00:30:50 +01:00
mut pid := -1
$if !windows {
pid = C.wait(0)
}
2020-02-10 18:48:01 +01:00
$if windows {
2019-11-16 00:30:50 +01:00
panic('os.wait not supported in windows') // TODO
}
2019-11-16 00:30:50 +01:00
return pid
2019-07-14 04:18:54 +02:00
}
2020-08-27 16:57:01 +02:00
// file_last_mod_unix returns the "last modified" time stamp of file in `path`.
2019-07-07 21:46:21 +02:00
pub fn file_last_mod_unix(path string) int {
attr := C.stat{}
2019-12-19 22:29:37 +01:00
// # struct stat attr;
2020-12-15 08:33:31 +01:00
unsafe {C.stat(charptr(path.str), &attr)}
2019-12-19 22:29:37 +01:00
// # stat(path.str, &attr);
2019-08-22 22:19:31 +02:00
return attr.st_mtime
2019-12-19 22:29:37 +01:00
// # return attr.st_mtime ;
2019-07-07 21:46:21 +02:00
}
2019-08-22 22:19:31 +02:00
2020-08-27 16:57:01 +02:00
// log will print "os.log: "+`s` ...
pub fn log(s string) {
println('os.log: ' + s)
}
2020-03-03 00:00:30 +01:00
[deprecated]
2019-07-24 16:15:21 +02:00
pub fn flush_stdout() {
2020-06-23 16:25:24 +02:00
eprintln('warning: `os.flush_stdout` has been deprecated, use `os.flush` instead')
flush()
2020-03-03 00:00:30 +01:00
}
2020-08-27 16:57:01 +02:00
// flush will flush the stdout buffer.
2020-03-03 00:00:30 +01:00
pub fn flush() {
2020-04-03 10:52:48 +02:00
C.fflush(C.stdout)
2019-08-22 22:19:31 +02:00
}
2019-07-24 16:15:21 +02:00
2020-08-27 16:57:01 +02:00
// mkdir_all will create a valid full path of all directories given in `path`.
pub fn mkdir_all(path string) ? {
2020-12-15 08:33:31 +01:00
mut p := if path.starts_with(path_separator) { path_separator } else { '' }
path_parts := path.trim_left(path_separator).split(path_separator)
for subdir in path_parts {
2020-12-15 08:33:31 +01:00
p += subdir + path_separator
if exists(p) && is_dir(p) {
continue
}
2020-12-15 08:33:31 +01:00
mkdir(p) or { return error('folder: $p, error: $err') }
}
}
2019-10-15 17:08:46 +02:00
2020-03-08 15:57:47 +01:00
// cache_dir returns the path to a *writable* user specific folder, suitable for writing non-essential data.
pub fn cache_dir() string {
// See: https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
// There is a single base directory relative to which user-specific non-essential
// (cached) data should be written. This directory is defined by the environment
// variable $XDG_CACHE_HOME.
// $XDG_CACHE_HOME defines the base directory relative to which user specific
// non-essential data files should be stored. If $XDG_CACHE_HOME is either not set
// or empty, a default equal to $HOME/.cache should be used.
$if !windows {
2020-12-15 08:33:31 +01:00
xdg_cache_home := getenv('XDG_CACHE_HOME')
if xdg_cache_home != '' {
return xdg_cache_home
}
}
2020-12-15 08:33:31 +01:00
cdir := join_path(home_dir(), '.cache')
if !is_dir(cdir) && !is_link(cdir) {
mkdir(cdir) or { panic(err) }
}
return cdir
}
2020-08-27 16:57:01 +02:00
// temp_dir returns the path to a folder, that is suitable for storing temporary files.
2020-03-10 15:02:09 +01:00
pub fn temp_dir() string {
2020-12-15 08:33:31 +01:00
mut path := getenv('TMPDIR')
$if windows {
if path == '' {
// TODO see Qt's implementation?
// https://doc.qt.io/qt-5/qdir.html#tempPath
// https://github.com/qt/qtbase/blob/e164d61ca8263fc4b46fdd916e1ea77c7dd2b735/src/corelib/io/qfilesystemengine_win.cpp#L1275
2020-12-15 08:33:31 +01:00
path = getenv('TEMP')
2019-12-19 22:29:37 +01:00
if path == '' {
2020-12-15 08:33:31 +01:00
path = getenv('TMP')
2019-12-19 22:29:37 +01:00
}
if path == '' {
path = 'C:/tmp'
}
}
}
$if android {
// TODO test+use '/data/local/tmp' on Android before using cache_dir()
if path == '' {
2020-12-15 08:33:31 +01:00
path = cache_dir()
}
}
2019-12-30 05:25:26 +01:00
if path == '' {
path = '/tmp'
}
return path
}
2019-11-24 04:27:02 +01:00
fn default_vmodules_path() string {
2020-12-15 08:33:31 +01:00
return join_path(home_dir(), '.vmodules')
}
2020-12-15 08:33:31 +01:00
// vmodules_dir returns the path to a folder, where v stores its global modules.
pub fn vmodules_dir() string {
paths := vmodules_paths()
if paths.len > 0 {
return paths[0]
}
2020-12-15 08:33:31 +01:00
return default_vmodules_path()
}
// vmodules_paths returns a list of paths, where v looks up for modules.
// You can customize it through setting the environment variable VMODULES
pub fn vmodules_paths() []string {
2020-12-15 08:33:31 +01:00
mut path := getenv('VMODULES')
if path == '' {
2020-12-15 08:33:31 +01:00
path = default_vmodules_path()
}
2020-12-15 08:33:31 +01:00
list := path.split(path_delimiter).map(it.trim_right(path_separator))
return list
}
2020-08-27 16:57:01 +02:00
// chmod change file access attributes of `path` to `mode`.
// Octals like `0o600` can be used.
2019-11-24 04:27:02 +01:00
pub fn chmod(path string, mode int) {
2020-07-14 00:16:31 +02:00
C.chmod(charptr(path.str), mode)
2019-11-29 17:14:26 +01:00
}
pub const (
2019-12-19 22:29:37 +01:00
wd_at_startup = getwd()
)
2020-08-27 16:57:01 +02:00
// resource_abs_path returns an absolute path, for the given `path`.
// (the path is expected to be relative to the executable program)
// See https://discordapp.com/channels/592103645835821068/592294828432424960/630806741373943808
// It gives a convenient way to access program resources like images, fonts, sounds and so on,
// *no matter* how the program was started, and what is the current working directory.
pub fn resource_abs_path(path string) string {
2020-12-15 08:33:31 +01:00
mut base_path := real_path(dir(executable()))
vresource := getenv('V_RESOURCE_PATH')
if vresource.len != 0 {
base_path = vresource
}
2020-12-15 08:33:31 +01:00
return real_path(join_path(base_path, path))
}
2020-08-27 16:57:01 +02:00
// open tries to open a file for reading and returns back a read-only `File` object.
pub fn open(path string) ?File {
2020-12-15 08:33:31 +01:00
/*
$if linux {
$if !android {
fd := C.syscall(sys_open, path.str, 511)
if fd == -1 {
return error('failed to open file "$path"')
}
return File{
fd: fd
2020-08-01 23:07:37 +02:00
is_opened: true
}
}
}
2020-12-15 08:33:31 +01:00
*/
cfile := vfopen(path, 'rb') ?
fd := fileno(cfile)
2020-12-15 08:33:31 +01:00
return File{
cfile: cfile
fd: fd
2020-08-01 23:07:37 +02:00
is_opened: true
}
}
2020-08-27 16:57:01 +02:00
// create creates or opens a file at a specified location and returns a write-only `File` object.
pub fn create(path string) ?File {
2020-12-15 08:33:31 +01:00
/*
// NB: android/termux/bionic is also a kind of linux,
// but linux syscalls there sometimes fail,
// while the libc version should work.
$if linux {
$if !android {
//$if macos {
// fd = C.syscall(398, path.str, 0x601, 0x1b6)
//}
//$if linux {
fd = C.syscall(sys_creat, path.str, 511)
//}
if fd == -1 {
return error('failed to create file "$path"')
}
file = File{
fd: fd
2020-08-01 23:07:37 +02:00
is_opened: true
}
return file
}
}
2020-12-15 08:33:31 +01:00
*/
cfile := vfopen(path, 'wb') ?
fd := fileno(cfile)
2020-12-15 08:33:31 +01:00
return File{
cfile: cfile
fd: fd
2020-08-01 23:07:37 +02:00
is_opened: true
}
}
2020-06-14 15:46:30 +02:00
pub struct Uname {
pub mut:
sysname string
nodename string
release string
version string
machine string
}