325 lines
8.2 KiB
V
325 lines
8.2 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)!
|
|
|
|
// Create a symlink in /usr/local/bin if possible (for immediate use without sourcing profile)
|
|
if core.is_linux()! {
|
|
usr_local_bin := '/usr/local/bin/${args.cmdname}'
|
|
if os.exists(usr_local_bin) {
|
|
os.rm(usr_local_bin) or {}
|
|
}
|
|
|
|
// Try to create symlink, but don't fail if it doesn't work (might need sudo)
|
|
os.execute('ln -sf ${destpath} ${usr_local_bin}')
|
|
}
|
|
}
|
|
|
|
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 := ''
|
|
// Use ~/hero/bin for all platforms to avoid permission issues
|
|
dest = '${os.home_dir()}/hero/bin'
|
|
dir_ensure(dest)!
|
|
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('/bin/sh ${pp}')
|
|
if res.exit_code != 0 {
|
|
console.print_stderr('WARNING: your profile is corrupt, did:\nsource ${pp}\n${res}')
|
|
return error('profile corrupt')
|
|
} else {
|
|
return '. ${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 {
|
|
mut preferred := profile_paths_preferred()!
|
|
if preferred.len == 0 {
|
|
return error("can't find profile_path, found none")
|
|
}
|
|
return preferred[0]
|
|
}
|