// Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved. // Use of this source code is governed by an MIT license // that can be found in the LICENSE file. module os pub struct Result { pub: exit_code int output string // stderr string // TODO } pub const ( args = []string{} max_path_len = 4096 wd_at_startup = getwd() ) const ( f_ok = 0 x_ok = 1 w_ok = 2 r_ok = 4 ) [deprecated] pub fn cp_r(osource_path string, odest_path string, overwrite bool) ? { eprintln('warning: `os.cp_r` has been deprecated, use `os.cp_all` instead') return cp_all(osource_path, odest_path, overwrite) } // 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) ? { source_path := real_path(src) dest_path := real_path(dst) if !exists(source_path) { return error("Source path doesn\'t exist") } // single file copy 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 { rm(adjusted_path) } else { return error('Destination file path already exist') } } cp(source_path, adjusted_path) ? return } if !is_dir(dest_path) { return error('Destination path is not a valid directory') } files := ls(source_path) ? for file in files { sp := join_path(source_path, file) dp := join_path(dest_path, file) if is_dir(sp) { mkdir(dp) ? } cp_all(sp, dp, overwrite) or { rmdir(dp) return error(err) } } } // mv_by_cp first copies the source file, and if it is copied successfully, deletes the source file. // may be used when you are not sure that the source and target are on the same mount/partition. pub fn mv_by_cp(source string, target string) ? { cp(source, target) ? rm(source) ? } // read_lines reads the file in `path` into an array of lines. pub fn read_lines(path string) ?[]string { buf := read_file(path) ? return buf.split_into_lines() } // read_ulines reads the file in `path` into an array of ustring lines. fn read_ulines(path string) ?[]ustring { lines := read_lines(path) ? // mut ulines := new_array(0, lines.len, sizeof(ustring)) mut ulines := []ustring{} for myline in lines { // ulines[i] = ustr ulines << myline.ustring() } return ulines } // 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 { 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 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' } // ///////////////////////////// 5 { return 'SIGTRAP' } 7 { return 'SIGBUS' } else {} } } return 'unknown' } [deprecated] pub fn file_exists(_path string) bool { eprintln('warning: `os.file_exists` has been deprecated, use `os.exists` instead') return exists(_path) } [deprecated] pub fn rmdir_recursive(path string) { eprintln('warning: `os.rmdir_recursive` has been deprecated, use `os.rmdir_all` instead') rmdir_all(path) } // rmdir_all recursively removes the specified directory. pub fn rmdir_all(path string) ? { mut ret_err := '' items := ls(path) ? for item in items { if is_dir(join_path(path, item)) { rmdir_all(join_path(path, item)) } rm(join_path(path, item)) or { ret_err = err } } rmdir(path) or { ret_err = err } if ret_err.len > 0 { return error(ret_err) } } // is_dir_empty will return a `bool` whether or not `path` is empty. pub fn is_dir_empty(path string) bool { items := ls(path) or { return true } return items.len == 0 } // file_ext will return the part after the last occurence of `.` in `path`. // The `.` is included. pub fn file_ext(path string) string { pos := path.last_index('.') or { return '' } return path[pos..] } // 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. pub fn dir(path string) string { if path == '' { return '.' } pos := path.last_index(path_separator) or { return '.' } 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) { path2 := path[..path.len - 1] pos := path2.last_index(path_separator) or { return path2.clone() } return path2[pos + 1..] } pos := path.last_index(path_separator) or { return path.clone() } return path[pos + 1..] } // file_name will return all characters found after the last occurence of `path_separator`. // file extension is included. pub fn file_name(path string) string { return path.all_after_last(path_separator) } // input returns a one-line string from stdin, after printing a prompt. pub fn input(prompt string) string { print(prompt) flush() return get_line() } // get_line returns a one-line string from stdin pub fn get_line() string { str := get_raw_line() $if windows { return str.trim_right('\r\n') } $else { return str.trim_right('\n') } } // get_lines returns an array of strings read from from stdin. // reading is stopped when an empty line is read. pub fn get_lines() []string { mut line := '' mut inputstr := []string{} for { line = get_line() if line.len <= 0 { break } line = line.trim_space() inputstr << line } return inputstr } // get_lines_joined returns a string of the values read from from stdin. // reading is stopped when an empty line is read. pub fn get_lines_joined() string { mut line := '' mut inputstr := '' for { line = get_line() if line.len <= 0 { break } line = line.trim_space() inputstr += line } return inputstr } // user_os returns current user operating system name. pub fn user_os() string { $if linux { return 'linux' } $if macos { return 'macos' } $if windows { return 'windows' } $if freebsd { return 'freebsd' } $if openbsd { return 'openbsd' } $if netbsd { return 'netbsd' } $if dragonfly { return 'dragonfly' } $if android { return 'android' } $if solaris { return 'solaris' } $if haiku { return 'haiku' } return 'unknown' } // home_dir returns path to the user's home directory. pub fn home_dir() string { $if windows { return getenv('USERPROFILE') } $else { // println('home_dir() call') // res:= os.getenv('HOME') // println('res="$res"') return getenv('HOME') } } // write_file writes `text` data to a file in `path`. pub fn write_file(path string, text string) ? { mut f := create(path) ? f.write(text.bytes()) f.close() } // write_file_array writes the data in `buffer` to a file in `path`. pub fn write_file_array(path string, buffer array) ? { mut f := create(path) ? f.write_bytes_at(buffer.data, (buffer.len * buffer.element_size), 0) f.close() } // 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 // all cases, but it should be better, than just using os.args[0] directly. fn executable_fallback() string { if args.len == 0 { // we are early in the bootstrap, os.args has not been initialized yet :-| return '' } mut exepath := args[0] $if windows { if !exepath.contains('.exe') { exepath += '.exe' } } if !is_abs_path(exepath) { if exepath.contains(path_separator) { exepath = join_path(wd_at_startup, exepath) } else { // no choice but to try to walk the PATH folders :-| ... foundpath := find_abs_path_of_executable(exepath) or { '' } if foundpath.len > 0 { exepath = foundpath } } } exepath = real_path(exepath) 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 { if is_abs_path(exepath) { return real_path(exepath) } mut res := '' paths := getenv('PATH').split(path_delimiter) for p in paths { found_abs_path := join_path(p, exepath) if exists(found_abs_path) && is_executable(found_abs_path) { res = found_abs_path break } } if res.len > 0 { return real_path(res) } return error('failed to find executable') } // exists_in_system_path returns `true` if `prog` exists in the system's PATH pub fn exists_in_system_path(prog string) bool { find_abs_path_of_executable(prog) or { return false } return true } [deprecated] pub fn dir_exists(path string) bool { eprintln('warning: `os.dir_exists` has been deprecated, use `os.is_dir` instead') return is_dir(path) } // 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) } // is_abs_path returns `true` if `path` is absolute. pub fn is_abs_path(path string) bool { $if windows { return path[0] == `/` || // incase we're in MingGW bash (path[0].is_letter() && path[1] == `:`) } return path[0] == `/` } // join_path returns a path as string from input string parameter(s). pub fn join_path(base string, dirs ...string) string { mut result := []string{} result << base.trim_right('\\/') for d in dirs { result << d } return result.join(path_separator) } // walk_ext returns a recursive list of all files in `path` ending with `ext`. pub fn walk_ext(path string, ext string) []string { if !is_dir(path) { return [] } mut files := ls(path) or { return [] } mut res := []string{} separator := if path.ends_with(path_separator) { '' } else { path_separator } for file in files { if file.starts_with('.') { continue } p := path + separator + file if is_dir(p) && !is_link(p) { res << walk_ext(p, ext) } else if file.ends_with(ext) { res << p } } return res } // walk recursively traverses the given directory `path`. // When a file is encountred it will call the callback function with current file as argument. pub fn walk(path string, f fn (string)) { if !is_dir(path) { return } mut files := ls(path) or { return } for file in files { p := path + path_separator + file if is_dir(p) && !is_link(p) { walk(p, f) } else if exists(p) { f(p) } } return } // log will print "os.log: "+`s` ... pub fn log(s string) { println('os.log: ' + s) } [deprecated] pub fn flush_stdout() { eprintln('warning: `os.flush_stdout` has been deprecated, use `os.flush` instead') flush() } // mkdir_all will create a valid full path of all directories given in `path`. pub fn mkdir_all(path string) ? { 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 { p += subdir + path_separator if exists(p) && is_dir(p) { continue } mkdir(p) or { return error('folder: $p, error: $err') } } } // 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 { xdg_cache_home := getenv('XDG_CACHE_HOME') if xdg_cache_home != '' { return xdg_cache_home } } cdir := join_path(home_dir(), '.cache') if !is_dir(cdir) && !is_link(cdir) { mkdir(cdir) or { panic(err) } } return cdir } // temp_dir returns the path to a folder, that is suitable for storing temporary files. pub fn temp_dir() string { 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 path = getenv('TEMP') if path == '' { path = getenv('TMP') } if path == '' { path = 'C:/tmp' } } } $if android { // TODO test+use '/data/local/tmp' on Android before using cache_dir() if path == '' { path = cache_dir() } } if path == '' { path = '/tmp' } return path } fn default_vmodules_path() string { return join_path(home_dir(), '.vmodules') } // 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] } 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 { mut path := getenv('VMODULES') if path == '' { path = default_vmodules_path() } list := path.split(path_delimiter).map(it.trim_right(path_separator)) return list } // 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 { mut base_path := real_path(dir(executable())) vresource := getenv('V_RESOURCE_PATH') if vresource.len != 0 { base_path = vresource } return real_path(join_path(base_path, path)) } pub struct Uname { pub mut: sysname string nodename string release string version string machine string }