Files
herolib/lib/osal/cmds.v
2025-02-08 14:11:51 +01:00

317 lines
7.8 KiB
V

module osal
import os
import freeflowuniverse.herolib.core.pathlib
import freeflowuniverse.herolib.core
import freeflowuniverse.herolib.ui.console
// import regex
import freeflowuniverse.herolib.core.texttools
pub struct CmdAddArgs {
pub mut:
cmdname string
source string @[required] // path where the binary is
symlink bool // if rather than copy do a symlink
reset bool = true // if existing cmd will delete
// bin_repo_url string = 'https://github.com/freeflowuniverse/freeflow_binary' // binary where we put the results
}
// copy a binary to the right location on the local computer .
// e.g. is /usr/local/bin on linux .
// e.g. is ~/hero/bin on osx .
// will also add the bin location to the path of .zprofile and .zshrc (different per platform)
pub fn cmd_add(args_ CmdAddArgs) ! {
mut args := args_
if args.cmdname == '' {
args.cmdname = os.base(args.source)
}
mut dest := bin_path()!
mut sourcepath := pathlib.get_file(path: args.source, create: false)!
mut destpath := '${dest}/${args.cmdname}'
console.print_debug(destpath)
// check if there is other file
res := os.execute('which ${args.cmdname}')
if res.exit_code == 0 {
existing_path := res.output.trim_space()
if destpath != existing_path {
console.print_debug(' - did find a cmd which is not in path we expect:\n expected:${destpath}\n got:${existing_path}')
if args.reset {
if existing_path.contains('homebrew/bin') {
exec(cmd: 'brew uninstall ${args.cmdname}') or {
return error('failed to remove existing command using brew')
}
} else {
os.rm(existing_path)!
}
} else {
return error("existing cmd found on: ${existing_path} and can't delete.\nWas trying to install on ${destpath}.")
}
}
}
if args.symlink {
sourcepath.link(destpath, true)!
} else {
sourcepath.copy(dest: destpath, rsync: false)!
}
mut destfile := pathlib.get_file(path: destpath, create: false)!
destfile.chmod(0o770)! // includes read & write & execute
// lets make sure this path is in profile
profile_path_add_remove(paths2add: dest)!
}
pub fn profile_path_add_hero() !string {
mut dest := bin_path()!
profile_path_add_remove(paths2add: dest)!
return dest
}
pub fn bin_path() !string {
mut dest := ''
if core.is_osx()! {
dest = '${os.home_dir()}/hero/bin'
dir_ensure(dest)!
} else {
dest = '/usr/local/bin'
}
return dest
}
pub fn hero_path() !string {
mut dest := ''
dest = '${os.home_dir()}/hero'
dir_ensure(dest)!
return dest
}
///usr/local on linux, ${os.home_dir()}/hero on osx
pub fn usr_local_path() !string {
mut dest := ''
if core.is_osx()! {
dest = '${os.home_dir()}/hero'
dir_ensure(dest)!
} else {
dest = '/usr/local'
}
return dest
}
// return the source statement if the profile exists
pub fn profile_path_source() !string {
if core.hostname() or { '' } == 'rescue' {
return ''
}
pp := profile_path()!
if os.exists(pp) {
res := os.execute('source ${pp}')
if res.exit_code != 0 {
console.print_stderr('WARNING: your profile is corrupt: ${pp}')
return error('profile corrupt')
} else {
return 'source ${pp}'
}
}
return ''
}
// return source $path && .
// or empty if it doesn't exist
pub fn profile_path_source_and() !string {
p := profile_path_source() or { return '' }
if p.len==0{
return ""
}
return '${p} && '
}
fn profile_paths_get(content string) []string {
mut paths := []string{}
for line in content.split_into_lines() {
if line.contains('PATH') {
post := line.all_after_last('=').trim('\'" ,')
splitted := post.split(':')
for item in splitted {
item2 := item.trim(' "\'')
if item2 !in paths && !item2.contains('PATH') {
paths << item2
}
}
}
}
return paths
}
@[params]
pub struct ProfilePathAddRemoveArgs {
pub mut:
paths_profile string
paths2add string
paths2delete string
allprofiles bool
}
// add and/or remove paths from profiles
// if paths_profile not specified it will walk over all of them
pub fn profile_path_add_remove(args_ ProfilePathAddRemoveArgs) ! {
mut args := args_
mut paths_profile := texttools.to_array(args.paths_profile)
mut paths2add := texttools.to_array(args.paths2add)
mut paths2delete := texttools.to_array(args.paths2delete)
if paths_profile.len == 0 {
if args.allprofiles {
paths_profile = profile_paths_all()!
} else {
paths_profile = profile_paths_preferred()!
}
}
for path_profile_str in paths_profile {
mut path_profile := pathlib.get_file(path: path_profile_str, create: true)!
mut c := path_profile.read()!
mut c_out := '' // the result file
mut paths_existing_inprofile := profile_paths_get(c)
console.print_debug(" -- profile path profile:'${path_profile_str}' add:'${args.paths2add}' delete:'${args.paths2delete}'")
// Remove paths to delete
for mut todelete in paths2delete {
todelete = todelete.trim_space()
if todelete.len > 0 {
if todelete.starts_with('/') || todelete.starts_with('~') {
paths_existing_inprofile = paths_existing_inprofile.filter(it != todelete)
paths_existing_inprofile = paths_existing_inprofile.filter(it.replace('~',
os.home_dir()) != todelete)
} else {
paths_existing_inprofile = paths_existing_inprofile.filter(!(it.contains(todelete)))
}
}
}
// Add new paths if they don't exist
for mut path2add in paths2add {
if path2add !in paths_existing_inprofile {
path2add = path2add.replace('~', os.home_dir())
if !os.exists(path2add) {
return error("can't add path to profile, doesn't exist: ${path2add}")
}
paths_existing_inprofile << path2add
}
}
// Remove existing PATH declarations
lines := c.split_into_lines()
for line in lines {
if !line.to_lower().starts_with('export path=') {
c_out += line + '\n'
}
}
// Sort the paths
paths_existing_inprofile.sort()
// println(paths_existing_inprofile)
// if true{panic("ss")}
// Add the sorted paths
for item in paths_existing_inprofile {
c_out += 'export PATH=\$PATH:${item}\n'
}
// Only write if the content has changed
if c.trim_space() != c_out.trim_space() {
path_profile.write(c_out)!
}
}
}
// is same as executing which in OS
// returns path or error
pub fn cmd_path(cmd string) !string {
res := os.execute('which ${cmd}')
if res.exit_code == 0 {
return res.output.trim_space()
}
return error("can't do find path for cmd: ${cmd}")
}
// delete cmds from found locations
// can be one command of multiple
pub fn cmd_delete(cmd string) ! {
cmds := texttools.to_array(cmd)
for cmd2 in cmds {
res := cmd_path(cmd2) or { '' }
if res.len > 0 {
if os.exists(res) {
if core.sudo_path_ok(res)! {
os.rm(res)!
} else {
if core.interactive()! {
execute_silent('sudo rm -rf ${res}')!
} else {
return error("can't remove ${res} as sudo because non interactive as part of cmd delete.")
}
}
}
}
}
}
// return possible profile paths in OS
pub fn profile_paths_all() ![]string {
mut profile_files_ := []string{}
profile_files_ = [
'/etc/profile',
'/etc/bash.bashrc',
'${os.home_dir()}/.bashrc',
'${os.home_dir()}/.bash_profile',
'${os.home_dir()}/.profile',
'${os.home_dir()}/.zprofile',
'${os.home_dir()}/.zshrc',
]
mut profile_files2 := []string{}
for file in profile_files_ {
if os.exists(file) {
profile_files2 << file
}
}
return profile_files_
}
pub fn profile_paths_preferred() ![]string {
mut toadd := []string{}
if core.is_osx()! {
toadd << '${os.home_dir()}/.zprofile'
toadd << '${os.home_dir()}/.zshrc'
} else {
toadd << '${os.home_dir()}/.bash_profile'
toadd << '${os.home_dir()}/.bashrc'
toadd << '${os.home_dir()}/.zshrc'
}
mut profile_files2 := []string{}
for file in toadd {
if os.exists(file) {
println('${file} exists')
profile_files2 << file
}
}
return profile_files2
}
pub fn profile_path() !string {
if core.is_osx()! {
return '${os.home_dir()}/.zprofile'
} else {
return '${os.home_dir()}/.bash_profile'
}
}