v / vlib / os
Raw file | 922 loc (860 sloc) | 22.93 KB | Latest commit hash 3aeb6179b
1// Copyright (c) 2019-2023 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4module os
5
6import strings
7
8pub const max_path_len = 4096
9
10pub const wd_at_startup = getwd()
11
12const f_ok = 0
13
14const x_ok = 1
15
16const w_ok = 2
17
18const r_ok = 4
19
20pub struct Result {
21pub:
22 exit_code int
23 output string
24 // stderr string // TODO
25}
26
27pub struct Command {
28mut:
29 f voidptr
30pub mut:
31 eof bool
32 exit_code int
33pub:
34 path string
35 redirect_stdout bool
36}
37
38[unsafe]
39pub fn (mut result Result) free() {
40 unsafe { result.output.free() }
41}
42
43// executable_fallback is used when there is not a more platform specific and accurate implementation.
44// It relies on path manipulation of os.args[0] and os.wd_at_startup, so it may not work properly in
45// all cases, but it should be better, than just using os.args[0] directly.
46fn executable_fallback() string {
47 if args.len == 0 {
48 // we are early in the bootstrap, os.args has not been initialized yet :-|
49 return ''
50 }
51 mut exepath := args[0]
52 $if windows {
53 if !exepath.contains('.exe') {
54 exepath += '.exe'
55 }
56 }
57 if !is_abs_path(exepath) {
58 other_seperator := if path_separator == '/' { '\\' } else { '/' }
59 rexepath := exepath.replace(other_seperator, path_separator)
60 if rexepath.contains(path_separator) {
61 exepath = join_path_single(os.wd_at_startup, exepath)
62 } else {
63 // no choice but to try to walk the PATH folders :-| ...
64 foundpath := find_abs_path_of_executable(exepath) or { '' }
65 if foundpath.len > 0 {
66 exepath = foundpath
67 }
68 }
69 }
70 exepath = real_path(exepath)
71 return exepath
72}
73
74// cp_all will recursively copy `src` to `dst`,
75// optionally overwriting files or dirs in `dst`.
76pub fn cp_all(src string, dst string, overwrite bool) ! {
77 source_path := real_path(src)
78 dest_path := real_path(dst)
79 if !exists(source_path) {
80 return error("Source path doesn't exist")
81 }
82 // single file copy
83 if !is_dir(source_path) {
84 fname := file_name(source_path)
85 adjusted_path := if is_dir(dest_path) {
86 join_path_single(dest_path, fname)
87 } else {
88 dest_path
89 }
90 if exists(adjusted_path) {
91 if overwrite {
92 rm(adjusted_path)!
93 } else {
94 return error('Destination file path already exist')
95 }
96 }
97 cp(source_path, adjusted_path)!
98 return
99 }
100 if !exists(dest_path) {
101 mkdir(dest_path)!
102 }
103 if !is_dir(dest_path) {
104 return error('Destination path is not a valid directory')
105 }
106 files := ls(source_path)!
107 for file in files {
108 sp := join_path_single(source_path, file)
109 dp := join_path_single(dest_path, file)
110 if is_dir(sp) {
111 if !exists(dp) {
112 mkdir(dp)!
113 }
114 }
115 cp_all(sp, dp, overwrite) or {
116 rmdir(dp) or { return err }
117 return err
118 }
119 }
120}
121
122// mv_by_cp first copies the source file, and if it is copied successfully, deletes the source file.
123// may be used when you are not sure that the source and target are on the same mount/partition.
124pub fn mv_by_cp(source string, target string) ! {
125 cp(source, target)!
126 rm(source)!
127}
128
129// mv moves files or folders from `src` to `dst`.
130pub fn mv(source string, target string) ! {
131 rename(source, target) or { mv_by_cp(source, target)! }
132}
133
134// read_lines reads the file in `path` into an array of lines.
135[manualfree]
136pub fn read_lines(path string) ![]string {
137 buf := read_file(path)!
138 res := buf.split_into_lines()
139 unsafe { buf.free() }
140 return res
141}
142
143// sigint_to_signal_name will translate `si` signal integer code to it's string code representation.
144pub fn sigint_to_signal_name(si int) string {
145 // POSIX signals:
146 match si {
147 1 { return 'SIGHUP' }
148 2 { return 'SIGINT' }
149 3 { return 'SIGQUIT' }
150 4 { return 'SIGILL' }
151 6 { return 'SIGABRT' }
152 8 { return 'SIGFPE' }
153 9 { return 'SIGKILL' }
154 11 { return 'SIGSEGV' }
155 13 { return 'SIGPIPE' }
156 14 { return 'SIGALRM' }
157 15 { return 'SIGTERM' }
158 else {}
159 }
160 $if linux {
161 // From `man 7 signal` on linux:
162 match si {
163 // TODO dependent on platform
164 // works only on x86/ARM/most others
165 10 { // , 30, 16
166 return 'SIGUSR1'
167 }
168 12 { // , 31, 17
169 return 'SIGUSR2'
170 }
171 17 { // , 20, 18
172 return 'SIGCHLD'
173 }
174 18 { // , 19, 25
175 return 'SIGCONT'
176 }
177 19 { // , 17, 23
178 return 'SIGSTOP'
179 }
180 20 { // , 18, 24
181 return 'SIGTSTP'
182 }
183 21 { // , 26
184 return 'SIGTTIN'
185 }
186 22 { // , 27
187 return 'SIGTTOU'
188 }
189 // /////////////////////////////
190 5 {
191 return 'SIGTRAP'
192 }
193 7 {
194 return 'SIGBUS'
195 }
196 else {}
197 }
198 }
199 return 'unknown'
200}
201
202// rmdir_all recursively removes the specified directory.
203pub fn rmdir_all(path string) ! {
204 mut ret_err := ''
205 items := ls(path)!
206 for item in items {
207 fullpath := join_path_single(path, item)
208 if is_dir(fullpath) && !is_link(fullpath) {
209 rmdir_all(fullpath) or { ret_err = err.msg() }
210 } else {
211 rm(fullpath) or { ret_err = err.msg() }
212 }
213 }
214 rmdir(path) or { ret_err = err.msg() }
215 if ret_err.len > 0 {
216 return error(ret_err)
217 }
218}
219
220// is_dir_empty will return a `bool` whether or not `path` is empty.
221// Note that it will return `true` if `path` does not exist.
222[manualfree]
223pub fn is_dir_empty(path string) bool {
224 items := ls(path) or { return true }
225 res := items.len == 0
226 unsafe { items.free() }
227 return res
228}
229
230// file_ext will return the part after the last occurence of `.` in `path`.
231// The `.` is included.
232// Examples:
233// ```v
234// assert os.file_ext('file.v') == '.v'
235// assert os.file_ext('.ignore_me') == ''
236// assert os.file_ext('.') == ''
237// ```
238pub fn file_ext(opath string) string {
239 if opath.len < 3 {
240 return empty_str
241 }
242 path := file_name(opath)
243 pos := path.last_index(dot_str) or { return empty_str }
244 if pos + 1 >= path.len || pos == 0 {
245 return empty_str
246 }
247 return path[pos..]
248}
249
250// dir returns all but the last element of path, typically the path's directory.
251// After dropping the final element, trailing slashes are removed.
252// If the path is empty, dir returns ".". If the path consists entirely of separators,
253// dir returns a single separator.
254// The returned path does not end in a separator unless it is the root directory.
255pub fn dir(opath string) string {
256 if opath == '' {
257 return '.'
258 }
259 other_seperator := if path_separator == '/' { '\\' } else { '/' }
260 path := opath.replace(other_seperator, path_separator)
261 pos := path.last_index(path_separator) or { return '.' }
262 if pos == 0 && path_separator == '/' {
263 return '/'
264 }
265 return path[..pos]
266}
267
268// base returns the last element of path.
269// Trailing path separators are removed before extracting the last element.
270// If the path is empty, base returns ".". If the path consists entirely of separators, base returns a
271// single separator.
272pub fn base(opath string) string {
273 if opath == '' {
274 return '.'
275 }
276 other_seperator := if path_separator == '/' { '\\' } else { '/' }
277 path := opath.replace(other_seperator, path_separator)
278 if path == path_separator {
279 return path_separator
280 }
281 if path.ends_with(path_separator) {
282 path2 := path[..path.len - 1]
283 pos := path2.last_index(path_separator) or { return path2.clone() }
284 return path2[pos + 1..]
285 }
286 pos := path.last_index(path_separator) or { return path.clone() }
287 return path[pos + 1..]
288}
289
290// file_name will return all characters found after the last occurence of `path_separator`.
291// file extension is included.
292pub fn file_name(opath string) string {
293 other_seperator := if path_separator == '/' { '\\' } else { '/' }
294 path := opath.replace(other_seperator, path_separator)
295 return path.all_after_last(path_separator)
296}
297
298// input_opt returns a one-line string from stdin, after printing a prompt.
299// Returns `none` in case of an error (end of input).
300pub fn input_opt(prompt string) ?string {
301 print(prompt)
302 flush()
303 res := get_raw_line()
304 if res.len > 0 {
305 return res.trim_right('\r\n')
306 }
307 return none
308}
309
310// input returns a one-line string from stdin, after printing a prompt.
311// Returns '<EOF>' in case of an error (end of input).
312pub fn input(prompt string) string {
313 res := input_opt(prompt) or { return '<EOF>' }
314 return res
315}
316
317// get_line returns a one-line string from stdin
318pub fn get_line() string {
319 str := get_raw_line()
320 $if windows {
321 return str.trim_right('\r\n')
322 }
323 return str.trim_right('\n')
324}
325
326// get_lines returns an array of strings read from from stdin.
327// reading is stopped when an empty line is read.
328pub fn get_lines() []string {
329 mut line := ''
330 mut inputstr := []string{}
331 for {
332 line = get_line()
333 if line.len <= 0 {
334 break
335 }
336 line = line.trim_space()
337 inputstr << line
338 }
339 return inputstr
340}
341
342// get_lines_joined returns a string of the values read from from stdin.
343// reading is stopped when an empty line is read.
344pub fn get_lines_joined() string {
345 mut line := ''
346 mut inputstr := ''
347 for {
348 line = get_line()
349 if line.len <= 0 {
350 break
351 }
352 line = line.trim_space()
353 inputstr += line
354 }
355 return inputstr
356}
357
358// get_raw_lines_joined reads *all* input lines from stdin.
359// It returns them as one large string. Note: unlike os.get_lines_joined,
360// empty lines (that contain only `\r\n` or `\n`), will be present in
361// the output.
362// Reading is stopped, only on EOF of stdin.
363pub fn get_raw_lines_joined() string {
364 mut line := ''
365 mut lines := []string{}
366 for {
367 line = get_raw_line()
368 if line.len <= 0 {
369 break
370 }
371 lines << line
372 }
373 res := lines.join('')
374 return res
375}
376
377// user_os returns current user operating system name.
378pub fn user_os() string {
379 $if linux {
380 return 'linux'
381 }
382 $if macos {
383 return 'macos'
384 }
385 $if windows {
386 return 'windows'
387 }
388 $if freebsd {
389 return 'freebsd'
390 }
391 $if openbsd {
392 return 'openbsd'
393 }
394 $if netbsd {
395 return 'netbsd'
396 }
397 $if dragonfly {
398 return 'dragonfly'
399 }
400 $if android {
401 return 'android'
402 }
403 $if termux {
404 return 'termux'
405 }
406 $if solaris {
407 return 'solaris'
408 }
409 $if qnx {
410 return 'qnx'
411 }
412 $if haiku {
413 return 'haiku'
414 }
415 $if serenity {
416 return 'serenity'
417 }
418 $if vinix {
419 return 'vinix'
420 }
421 if getenv('TERMUX_VERSION') != '' {
422 return 'termux'
423 }
424 return 'unknown'
425}
426
427// user_names returns an array of the name of every user on the system.
428pub fn user_names() ![]string {
429 $if windows {
430 result := execute('wmic useraccount get name')
431 if result.exit_code != 0 {
432 return error('Failed to get user names. Exited with code ${result.exit_code}: ${result.output}')
433 }
434 mut users := result.output.split_into_lines()
435 // windows command prints an empty line at the end of output
436 users.delete(users.len - 1)
437 return users
438 } $else {
439 lines := read_lines('/etc/passwd')!
440 mut users := []string{cap: lines.len}
441 for line in lines {
442 end_name := line.index(':') or { line.len }
443 users << line[0..end_name]
444 }
445 return users
446 }
447}
448
449// home_dir returns path to the user's home directory.
450pub fn home_dir() string {
451 $if windows {
452 return getenv('USERPROFILE')
453 } $else {
454 // println('home_dir() call')
455 // res:= os.getenv('HOME')
456 // println('res="$res"')
457 return getenv('HOME')
458 }
459}
460
461// expand_tilde_to_home expands the character `~` in `path` to the user's home directory.
462// See also `home_dir()`.
463pub fn expand_tilde_to_home(path string) string {
464 if path == '~' {
465 return home_dir().trim_right(path_separator)
466 }
467 if path.starts_with('~' + path_separator) {
468 return path.replace_once('~' + path_separator, home_dir().trim_right(path_separator) +
469 path_separator)
470 }
471 return path
472}
473
474// write_file writes `text` data to the file in `path`.
475// If `path` exists, the contents of `path` will be overwritten with the contents of `text`.
476pub fn write_file(path string, text string) ! {
477 mut f := create(path)!
478 unsafe { f.write_full_buffer(text.str, usize(text.len))! }
479 f.close()
480}
481
482pub struct ExecutableNotFoundError {
483 Error
484}
485
486pub fn (err ExecutableNotFoundError) msg() string {
487 return 'os: failed to find executable'
488}
489
490fn error_failed_to_find_executable() IError {
491 return &ExecutableNotFoundError{}
492}
493
494// find_abs_path_of_executable walks the environment PATH, just like most shell do, it returns
495// the absolute path of the executable if found
496pub fn find_abs_path_of_executable(exepath string) !string {
497 if exepath == '' {
498 return error('expected non empty `exepath`')
499 }
500
501 for suffix in executable_suffixes {
502 fexepath := exepath + suffix
503 if is_abs_path(fexepath) {
504 return real_path(fexepath)
505 }
506 mut res := ''
507 path := getenv('PATH')
508 paths := path.split(path_delimiter)
509 for p in paths {
510 found_abs_path := join_path_single(p, fexepath)
511 $if trace_find_abs_path_of_executable ? {
512 dump(found_abs_path)
513 }
514 if exists(found_abs_path) && is_executable(found_abs_path) {
515 res = found_abs_path
516 break
517 }
518 }
519 if res.len > 0 {
520 return real_path(res)
521 }
522 }
523 return error_failed_to_find_executable()
524}
525
526// exists_in_system_path returns `true` if `prog` exists in the system's PATH
527pub fn exists_in_system_path(prog string) bool {
528 find_abs_path_of_executable(prog) or { return false }
529 return true
530}
531
532// is_file returns a `bool` indicating whether the given `path` is a file.
533pub fn is_file(path string) bool {
534 return exists(path) && !is_dir(path)
535}
536
537// join_path returns a path as string from input string parameter(s).
538[manualfree]
539pub fn join_path(base string, dirs ...string) string {
540 // TODO: fix freeing of `dirs` when the passed arguments are variadic,
541 // but do not free the arr, when `os.join_path(base, ...arr)` is called.
542 mut sb := strings.new_builder(base.len + dirs.len * 50)
543 defer {
544 unsafe { sb.free() }
545 }
546 sbase := base.trim_right('\\/')
547 defer {
548 unsafe { sbase.free() }
549 }
550 sb.write_string(sbase)
551 for d in dirs {
552 sb.write_string(path_separator)
553 sb.write_string(d)
554 }
555 mut res := sb.str()
556 if res.contains('/./') {
557 // Fix `join_path("/foo/bar", "./file.txt")` => `/foo/bar/./file.txt`
558 res = res.replace('/./', '/')
559 }
560 return res
561}
562
563// join_path_single appends the `elem` after `base`, using a platform specific
564// path_separator.
565[manualfree]
566pub fn join_path_single(base string, elem string) string {
567 // TODO: deprecate this and make it `return os.join_path(base, elem)`,
568 // when freeing variadic args vs ...arr is solved in the compiler
569 mut sb := strings.new_builder(base.len + elem.len + 1)
570 defer {
571 unsafe { sb.free() }
572 }
573 sbase := base.trim_right('\\/')
574 defer {
575 unsafe { sbase.free() }
576 }
577 sb.write_string(sbase)
578 sb.write_string(path_separator)
579 sb.write_string(elem)
580 return sb.str()
581}
582
583// walk_ext returns a recursive list of all files in `path` ending with `ext`.
584pub fn walk_ext(path string, ext string) []string {
585 mut res := []string{}
586 impl_walk_ext(path, ext, mut res)
587 return res
588}
589
590fn impl_walk_ext(path string, ext string, mut out []string) {
591 if !is_dir(path) {
592 return
593 }
594 mut files := ls(path) or { return }
595 separator := if path.ends_with(path_separator) { '' } else { path_separator }
596 for file in files {
597 if file.starts_with('.') {
598 continue
599 }
600 p := path + separator + file
601 if is_dir(p) && !is_link(p) {
602 impl_walk_ext(p, ext, mut out)
603 } else if file.ends_with(ext) {
604 out << p
605 }
606 }
607}
608
609// walk traverses the given directory `path`.
610// When a file is encountred, it will call the callback `f` with current file as argument.
611// Note: walk can be called even for deeply nested folders,
612// since it does not recurse, but processes them iteratively.
613pub fn walk(path string, f fn (string)) {
614 if path.len == 0 {
615 return
616 }
617 if !is_dir(path) {
618 return
619 }
620 mut remaining := []string{cap: 1000}
621 clean_path := path.trim_right(path_separator)
622 $if windows {
623 remaining << clean_path.replace('/', '\\')
624 } $else {
625 remaining << clean_path
626 }
627 for remaining.len > 0 {
628 cpath := remaining.pop()
629 pkind := kind_of_existing_path(cpath)
630 if pkind.is_link || !pkind.is_dir {
631 f(cpath)
632 continue
633 }
634 mut files := ls(cpath) or { continue }
635 for idx := files.len - 1; idx >= 0; idx-- {
636 remaining << cpath + path_separator + files[idx]
637 }
638 }
639}
640
641// FnWalkContextCB is used to define the callback functions, passed to os.walk_context
642pub type FnWalkContextCB = fn (voidptr, string)
643
644// walk_with_context traverses the given directory `path`.
645// For each encountred file *and* directory, it will call your `fcb` callback,
646// passing it the arbitrary `context` in its first parameter,
647// and the path to the file in its second parameter.
648// Note: walk_with_context can be called even for deeply nested folders,
649// since it does not recurse, but processes them iteratively.
650pub fn walk_with_context(path string, context voidptr, fcb FnWalkContextCB) {
651 if path.len == 0 {
652 return
653 }
654 if !is_dir(path) {
655 return
656 }
657 mut remaining := []string{cap: 1000}
658 clean_path := path.trim_right(path_separator)
659 $if windows {
660 remaining << clean_path.replace('/', '\\')
661 } $else {
662 remaining << clean_path
663 }
664 mut loops := 0
665 for remaining.len > 0 {
666 loops++
667 cpath := remaining.pop()
668 // call `fcb` for everything, but the initial folder:
669 if loops > 1 {
670 fcb(context, cpath)
671 }
672 pkind := kind_of_existing_path(cpath)
673 if pkind.is_link || !pkind.is_dir {
674 continue
675 }
676 mut files := ls(cpath) or { continue }
677 for idx := files.len - 1; idx >= 0; idx-- {
678 remaining << cpath + path_separator + files[idx]
679 }
680 }
681}
682
683// log will print "os.log: "+`s` ...
684pub fn log(s string) {
685 println('os.log: ' + s)
686}
687
688[params]
689pub struct MkdirParams {
690 mode u32 = 0o777 // note that the actual mode is affected by the process's umask
691}
692
693// mkdir_all will create a valid full path of all directories given in `path`.
694pub fn mkdir_all(opath string, params MkdirParams) ! {
695 other_seperator := if path_separator == '/' { '\\' } else { '/' }
696 path := opath.replace(other_seperator, path_separator)
697 mut p := if path.starts_with(path_separator) { path_separator } else { '' }
698 path_parts := path.trim_left(path_separator).split(path_separator)
699 for subdir in path_parts {
700 p += subdir + path_separator
701 if exists(p) && is_dir(p) {
702 continue
703 }
704 mkdir(p, params) or { return error('folder: ${p}, error: ${err}') }
705 }
706}
707
708// cache_dir returns the path to a *writable* user specific folder, suitable for writing non-essential data.
709pub fn cache_dir() string {
710 // See: https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
711 // There is a single base directory relative to which user-specific non-essential
712 // (cached) data should be written. This directory is defined by the environment
713 // variable $XDG_CACHE_HOME.
714 // $XDG_CACHE_HOME defines the base directory relative to which user specific
715 // non-essential data files should be stored. If $XDG_CACHE_HOME is either not set
716 // or empty, a default equal to $HOME/.cache should be used.
717 xdg_cache_home := getenv('XDG_CACHE_HOME')
718 if xdg_cache_home != '' {
719 return xdg_cache_home
720 }
721 cdir := join_path_single(home_dir(), '.cache')
722 if !is_dir(cdir) && !is_link(cdir) {
723 mkdir(cdir) or { panic(err) }
724 }
725 return cdir
726}
727
728// temp_dir returns the path to a folder, that is suitable for storing temporary files.
729pub fn temp_dir() string {
730 mut path := getenv('TMPDIR')
731 $if windows {
732 if path == '' {
733 // TODO see Qt's implementation?
734 // https://doc.qt.io/qt-5/qdir.html#tempPath
735 // https://github.com/qt/qtbase/blob/e164d61ca8263fc4b46fdd916e1ea77c7dd2b735/src/corelib/io/qfilesystemengine_win.cpp#L1275
736 path = getenv('TEMP')
737 if path == '' {
738 path = getenv('TMP')
739 }
740 if path == '' {
741 path = 'C:/tmp'
742 }
743 }
744 path = get_long_path(path) or { path }
745 }
746 $if macos {
747 // avoid /var/folders/6j/cmsk8gd90pd.... on macs
748 return '/tmp'
749 }
750 $if android {
751 // TODO test+use '/data/local/tmp' on Android before using cache_dir()
752 if path == '' {
753 path = cache_dir()
754 }
755 }
756 $if termux {
757 path = '/data/data/com.termux/files/usr/tmp'
758 }
759 if path == '' {
760 path = '/tmp'
761 }
762 return path
763}
764
765// vtmp_dir returns the path to a folder, that is writable to V programs, *and* specific
766// to the OS user. It can be overridden by setting the env variable `VTMP`.
767pub fn vtmp_dir() string {
768 mut vtmp := getenv('VTMP')
769 if vtmp.len > 0 {
770 return vtmp
771 }
772 uid := getuid()
773 vtmp = join_path_single(temp_dir(), 'v_${uid}')
774 if !exists(vtmp) || !is_dir(vtmp) {
775 // create a new directory, that is private to the user:
776 mkdir_all(vtmp, mode: 0o700) or { panic(err) }
777 }
778 setenv('VTMP', vtmp, true)
779 return vtmp
780}
781
782fn default_vmodules_path() string {
783 hdir := home_dir()
784 res := join_path_single(hdir, '.vmodules')
785 return res
786}
787
788// vmodules_dir returns the path to a folder, where v stores its global modules.
789pub fn vmodules_dir() string {
790 paths := vmodules_paths()
791 if paths.len > 0 {
792 return paths[0]
793 }
794 return default_vmodules_path()
795}
796
797// vmodules_paths returns a list of paths, where v looks up for modules.
798// You can customize it through setting the environment variable VMODULES
799// [manualfree]
800pub fn vmodules_paths() []string {
801 mut path := getenv('VMODULES')
802 if path == '' {
803 // unsafe { path.free() }
804 path = default_vmodules_path()
805 }
806 defer {
807 // unsafe { path.free() }
808 }
809 splitted := path.split(path_delimiter)
810 defer {
811 // unsafe { splitted.free() }
812 }
813 mut list := []string{cap: splitted.len}
814 for i in 0 .. splitted.len {
815 si := splitted[i]
816 trimmed := si.trim_right(path_separator)
817 list << trimmed
818 // unsafe { trimmed.free() }
819 // unsafe { si.free() }
820 }
821 return list
822}
823
824// resource_abs_path returns an absolute path, for the given `path`.
825// (the path is expected to be relative to the executable program)
826// See https://discordapp.com/channels/592103645835821068/592294828432424960/630806741373943808
827// It gives a convenient way to access program resources like images, fonts, sounds and so on,
828// *no matter* how the program was started, and what is the current working directory.
829[manualfree]
830pub fn resource_abs_path(path string) string {
831 exe := executable()
832 dexe := dir(exe)
833 mut base_path := real_path(dexe)
834 vresource := getenv('V_RESOURCE_PATH')
835 if vresource.len != 0 {
836 unsafe { base_path.free() }
837 base_path = vresource
838 }
839 fp := join_path_single(base_path, path)
840 res := real_path(fp)
841 unsafe {
842 fp.free()
843 vresource.free()
844 base_path.free()
845 dexe.free()
846 exe.free()
847 }
848 return res
849}
850
851pub struct Uname {
852pub mut:
853 sysname string
854 nodename string
855 release string
856 version string
857 machine string
858}
859
860pub fn execute_or_panic(cmd string) Result {
861 res := execute(cmd)
862 if res.exit_code != 0 {
863 eprintln('failed cmd: ${cmd}')
864 eprintln('failed code: ${res.exit_code}')
865 panic(res.output)
866 }
867 return res
868}
869
870pub fn execute_or_exit(cmd string) Result {
871 res := execute(cmd)
872 if res.exit_code != 0 {
873 eprintln('failed cmd: ${cmd}')
874 eprintln('failed code: ${res.exit_code}')
875 eprintln(res.output)
876 exit(1)
877 }
878 return res
879}
880
881// quoted path - return a quoted version of the path, depending on the platform.
882pub fn quoted_path(path string) string {
883 $if windows {
884 return if path.ends_with(path_separator) {
885 '"${path + path_separator}"'
886 } else {
887 '"${path}"'
888 }
889 } $else {
890 return "'${path}'"
891 }
892}
893
894// config_dir returns the path to the user configuration directory (depending on the platform).
895// On windows, that is `%AppData%`.
896// On macos, that is `~/Library/Application Support`.
897// On the rest, that is `$XDG_CONFIG_HOME`, or if that is not available, `~/.config`.
898// If the path cannot be determined, it returns an error.
899// (for example, when $HOME on linux, or %AppData% on windows is not defined)
900pub fn config_dir() !string {
901 $if windows {
902 app_data := getenv('AppData')
903 if app_data != '' {
904 return app_data
905 }
906 } $else $if macos || darwin || ios {
907 home := home_dir()
908 if home != '' {
909 return home + '/Library/Application Support'
910 }
911 } $else {
912 xdg_home := getenv('XDG_CONFIG_HOME')
913 if xdg_home != '' {
914 return xdg_home
915 }
916 home := home_dir()
917 if home != '' {
918 return home + '/.config'
919 }
920 }
921 return error('Cannot find config directory')
922}