From c9d3b583f4c763e741cc25ebb99249dc6a41dbef Mon Sep 17 00:00:00 2001 From: Arsen Musayelyan Date: Wed, 25 Nov 2020 20:18:52 -0800 Subject: [PATCH] Remove bad code from 13-year-old me --- distance.go | 62 +++++++++++ main.go | 160 ++++++++++++++++++++++++++++ pak.go | 301 ---------------------------------------------------- slices.go | 39 +++++++ usage.go | 28 +++++ 5 files changed, 289 insertions(+), 301 deletions(-) create mode 100644 distance.go create mode 100644 main.go delete mode 100644 pak.go create mode 100644 slices.go create mode 100644 usage.go diff --git a/distance.go b/distance.go new file mode 100644 index 0000000..2252cb3 --- /dev/null +++ b/distance.go @@ -0,0 +1,62 @@ +package main + +import "math" + +func Jaro(a, b string) float64 { + la := float64(len(a)) + lb := float64(len(b)) + + // match range = max(len(a), len(b)) / 2 - 1 + matchRange := int(math.Floor(math.Max(la, lb)/2.0)) - 1 + matchRange = int(math.Max(0, float64(matchRange-1))) + var matches, halfs float64 + transposed := make([]bool, len(b)) + + for i := 0; i < len(a); i++ { + start := int(math.Max(0, float64(i-matchRange))) + end := int(math.Min(lb-1, float64(i+matchRange))) + + for j := start; j <= end; j++ { + if transposed[j] { + continue + } + + if a[i] == b[j] { + if i != j { + halfs++ + } + matches++ + transposed[j] = true + break + } + } + } + + if matches == 0 { + return 0 + } + + transposes := math.Floor(float64(halfs / 2)) + + return ((matches / la) + (matches / lb) + (matches-transposes)/matches) / 3.0 +} + + +func JaroWinkler(a, b string, boostThreshold float64, prefixSize int) float64 { + j := Jaro(a, b) + + if j <= boostThreshold { + return j + } + + prefixSize = int(math.Min(float64(len(a)), math.Min(float64(prefixSize), float64(len(b))))) + + var prefixMatch float64 + for i := 0; i < prefixSize; i++ { + if a[i] == b[i] { + prefixMatch++ + } + } + + return j + 0.1*prefixMatch*(1.0-j) +} \ No newline at end of file diff --git a/main.go b/main.go new file mode 100644 index 0000000..25383f1 --- /dev/null +++ b/main.go @@ -0,0 +1,160 @@ +/* + Pak: Wrapper designed for package managers to unify software management commands between distros + Copyright (C) 2020 Arsen Musayelyan + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +package main + +import ( + "fmt" + "io/ioutil" + "log" + "os" + "os/exec" + "os/user" + "regexp" + "strings" +) + +func main() { + // Put all arguments into a variable + args := os.Args[1:] + + // Check which currentUser is running command + currentUser, err := user.Current() + if err != nil { log.Fatal(err) } + + // Check to make sure root is not being used unless -r/--root specified + if !Contains(args, "-r") && !Contains(args, "--root") { + if strings.Contains(currentUser.Username, "root") { + fmt.Println("Do not run as root, this program will invoke root for you if selected in config.") + fmt.Println("If you would like to bypass this, run this command with -r or --root.") + os.Exit(1) + } + } else { + if Contains(args, "-r") { + args = removeAtIndex(args, Find(args, "-r")) + } else if Contains(args, "--root") { + args = removeAtIndex(args, Find(args, "--root")) + } + } + + // Parse config file removing all comments and empty lines + config, err := ioutil.ReadFile("/etc/pak.cfg") + if err != nil { log.Fatal(err) } + commentRegex := regexp.MustCompile(`#.*`) + emptyLineRegex := regexp.MustCompile(`(?m)^\s*\n`) + parsedConfig := commentRegex.ReplaceAllString(string(config), "") + parsedConfig = emptyLineRegex.ReplaceAllString(parsedConfig, "") + + cfg := strings.Split(parsedConfig, "\n") + //fmt.Println(cfg) //DEBUG + + // Set first line of config to variable + packageManagerCommand := cfg[0] + //fmt.Println(packageManagerCommand) //DEBUG + + // Parse list of commands in config line 2 and set to variable as array + commands := strings.Split(cfg[1], ",") + //fmt.Println(commands) //DEBUG + + // Set the root option in config line 3 to a variable + useRoot := cfg[2] + //fmt.Println(useRoot) //DEBUG + + // Set command to use to invoke root at config line 4 to a variable + rootCommand := cfg[3] + //fmt.Println(rootCommand) //DEBUG + + // Parse list of shortcuts in config and line 5 set to variable as an array + shortcuts := strings.Split(cfg[4], ",") + //fmt.Println(shortcuts) //DEBUG + + // Parse list of shortcuts in config line 6 and set to variable as array + shortcutMappings := strings.Split(cfg[5], ",") + //fmt.Println(shortcutMappings) //DEBUG + + // Check if config file allows root and set boolean to a variable + var useRootBool bool + if useRoot == "yes" { + useRootBool = true + } else if useRoot == "no" { + useRootBool = false + } + //fmt.Println(useRootBool) //DEBUG + + // Create similar to slice to put all matched commands into + var similarTo []string + + // Displays help message if no arguments provided or -h/--help is passed + if len(args) == 0 || Contains(args, "-h") || Contains(args, "--help") || Contains(args, "help") { + printHelpMessage(packageManagerCommand, useRootBool, rootCommand, commands, shortcuts) + os.Exit(0) + } + + // Create distance slice to store JaroWinkler distance values + var distance []float64 + // Appends JaroWinkler distance between each available command and the first argument to an array + for _,command := range commands { + distance = append(distance, JaroWinkler(command, args[0], 1, 0)) + } + + // Compares each distance to the max of the distance slice and appends the closest command to similarTo + for index, element := range distance { + // If current element is the closest to the first argument + if element == Max(distance) { + // Append command at same index as distance to similarTo + similarTo = append(similarTo, commands[index]) + } + } + + // Deals with shortcuts + for index, shortcut := range shortcuts { + // If the first argument is a shortcut and similarTo does not already contain its mapping, append it + if args[0] == shortcut && !Contains(similarTo, shortcutMappings[index]) { + similarTo = append(similarTo, shortcutMappings[index]) + } + } + + // If similarTo is still empty, log it fatally as something is wrong with the config or the code + if len(similarTo) == 0 { log.Fatalln("This command does not match any known commands or shortcuts") } + + // Print text showing command being run and package manager being used + fmt.Println("Running:", strings.Title(similarTo[0]), "using", strings.Title(packageManagerCommand)) + // Run package manager with the proper arguments passed if more than one argument exists + var cmdArr []string + // If root is to be used, append it to cmdArr + if useRootBool { cmdArr = append(cmdArr, rootCommand) } + // Create slice with all commands and arguments for the package manager + cmdArr = append(cmdArr, []string{packageManagerCommand, similarTo[0]}...) + // If greater than 2 arguments, append them to cmdArr + if len(args) >= 2 { cmdArr = append(cmdArr, strings.Join(args[1:], " ")) } + // Create space separated string from cmdArr + cmdStr := strings.Join(cmdArr, " ") + // Instantiate exec.Command object with command sh, flag -c, and cmdStr + command := exec.Command("sh", "-c", cmdStr) + // Set standard outputs for command + command.Stdout = os.Stdout + command.Stdin = os.Stdin + command.Stderr = os.Stderr + // Run command + err = command.Run() + // If command returned an error, log fatally with explanation + if err != nil { + fmt.Println("Error received from child process") + log.Fatal(err) + } +} \ No newline at end of file diff --git a/pak.go b/pak.go deleted file mode 100644 index e529feb..0000000 --- a/pak.go +++ /dev/null @@ -1,301 +0,0 @@ -/* - Pak: Wrapper designed for package managers to unify software management commands between distros - Copyright (C) 2020 Arsen Musayelyan - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -package main - -import ( - "fmt" - "log" - "math" - "os" - "os/exec" - "strings" -) - - -// Print help screen - -func printHelpMessage(packageManagerCommand string, useRootBool bool, rootCommand string, commands []string, shortcuts []string) { - fmt.Println("Arsen Musayelyan's Package Manager Wrapper") - fmt.Println("Current package manager is:", packageManagerCommand) - if useRootBool { fmt.Println("Using root with:", rootCommand, "\n") } else { fmt.Println("Not using root\n") } - fmt.Println("Usage: pak [package]\nExample: pak in hello\n") - fmt.Println("The available commands are:\n" + strings.Join(commands, "\n"),"\n") - fmt.Println("The available shortcuts are:\n" + strings.Join(shortcuts, "\n"), "\n") - fmt.Println("The available flags are:\n--help, -h: Shows this help screen\n--root, -r: Bypass root error") - fmt.Println("misc: All", packageManagerCommand, "flags\n") - fmt.Println("Writing the whole command is uneccesary, just use enough to differentiate") - os.Exit(0) -} - -// Remove an element at an index from a slice -func removeAtIndex(s []string, index int) []string { - return append(s[:index], s[index+1:]...) -} - -// Check if slice contains string -func Contains(slice []string, val string) bool { - for _, item := range slice { - if item == val { - return true - } - } - return false -} - -func Max(array []float64) float64 { - var max float64 = array[0] - var min float64 = array[0] - for _, value := range array { - if max < value { - max = value - } - if min > value { - min = value - } - } - return max -} - -func Find(slice []string, val string) int { - for i, item := range slice { - if item == val { - return i - } - } - return -1 -} - -func Jaro(a, b string) float64 { - la := float64(len(a)) - lb := float64(len(b)) - - // match range = max(len(a), len(b)) / 2 - 1 - matchRange := int(math.Floor(math.Max(la, lb)/2.0)) - 1 - matchRange = int(math.Max(0, float64(matchRange-1))) - var matches, halfs float64 - transposed := make([]bool, len(b)) - - for i := 0; i < len(a); i++ { - start := int(math.Max(0, float64(i-matchRange))) - end := int(math.Min(lb-1, float64(i+matchRange))) - - for j := start; j <= end; j++ { - if transposed[j] { - continue - } - - if a[i] == b[j] { - if i != j { - halfs++ - } - matches++ - transposed[j] = true - break - } - } - } - - if matches == 0 { - return 0 - } - - transposes := math.Floor(float64(halfs / 2)) - - return ((matches / la) + (matches / lb) + (matches-transposes)/matches) / 3.0 -} - - -func JaroWinkler(a, b string, boostThreshold float64, prefixSize int) float64 { - j := Jaro(a, b) - - if j <= boostThreshold { - return j - } - - prefixSize = int(math.Min(float64(len(a)), math.Min(float64(prefixSize), float64(len(b))))) - - var prefixMatch float64 - for i := 0; i < prefixSize; i++ { - if a[i] == b[i] { - prefixMatch++ - } - } - - return j + 0.1*prefixMatch*(1.0-j) -} - - -func main() { - // Put all arguments into a variable - args := os.Args[1:] - - // Check which user is running command - usr, err := exec.Command("whoami").Output() - if err != nil { - log.Fatal(err) - } - - // Check to make sure root is not being used unless -r/--root specified - if !Contains(args, "-r") && !Contains(args, "--root") { - if strings.Contains(string(usr), "root") { - fmt.Println("Do not run as root, this program will invoke root for you if selected in config.") - fmt.Println("If you would like to bypass this, run this command with -r or --root.") - os.Exit(1) - } - } else { - if Contains(args, "-r") { - args = removeAtIndex(args, Find(args, "-r")) - } else if Contains(args, "--root") { - args = removeAtIndex(args, Find(args, "--root")) - } - } - - // Parse config file removing all comments and empty lines - cfgStr, err := exec.Command("sed", "-e", "s/#.*$//", "-e", "/^$/d", "/etc/pak.cfg").Output() - if err != nil { - log.Fatal(err) - } - cfg := strings.Split(string(cfgStr), "\n") - //fmt.Println(cfg) //DEBUG - - // Set first line of config to variable - packageManagerCommand := cfg[0] - //fmt.Println(packageManagerCommand) //DEBUG - - // Parse list of commands in config line 2 and set to variable as array - commands := strings.Split(cfg[1], ",") - //fmt.Println(commands) //DEBUG - - // Set the root option in config line 3 to a variable - useRoot := cfg[2] - //fmt.Println(useRoot) //DEBUG - - // Set command to use to invoke root at config line 4 to a variable - rootCommand := cfg[3] - //fmt.Println(rootCommand) //DEBUG - - // Parse list of shortcuts in config and line 5 set to variable as an array - shortcuts := strings.Split(cfg[4], ",") - //fmt.Println(shortcuts) //DEBUG - - // Parse list of shortcuts in config line 6 and set to variable as array - shortcutMappings := strings.Split(cfg[5], ",") - //fmt.Println(shortcutMappings) //DEBUG - - // Check if config file allows root and set boolean to a variable - var useRootBool bool - if useRoot == "yes" { - useRootBool = true - } else if useRoot == "no" { - useRootBool = false - } - //fmt.Println(useRootBool) //DEBUG - - // Create similar to slice to detect incomplete and misspelled commands - var similarTo []string - - // Displays help message if no arguments provided or -h/--help is passed - - if len(args) == 0 || Contains(args, "-h") || Contains(args, "--help") || Contains(args, "help") { - printHelpMessage(packageManagerCommand, useRootBool, rootCommand, commands, shortcuts) - } - - // Checks for known commands in first argument - // Appends percent similarity between command and all commands in array to an array - var dist []float64 - for _,command := range commands { - dist = append(dist, JaroWinkler(command, args[0], 1, 0)) - } - - // Appends the suspected command to an array - for count, element := range dist { - if element == Max(dist) { - similarTo = append(similarTo, commands[count]) - } - } - - // Deals with shortcuts - for index, shortcut := range shortcuts { - if args[0] == shortcut && !Contains(similarTo, shortcutMappings[index]) { - similarTo = nil - similarTo = append(similarTo, shortcutMappings[index]) - } - } - - // Run package manager with the proper arguments passed` - fmt.Println("Running:", strings.Title(similarTo[0]), "using", strings.Title(packageManagerCommand)) - if len(similarTo) == 1 && len(args) >= 2 { - if useRootBool { - cmdArr := []string{rootCommand, packageManagerCommand, similarTo[0], strings.Join(args[1:], " ")} - cmdStr := strings.Join(cmdArr, " ") - command := exec.Command("sh", "-c", cmdStr) - command.Stdout = os.Stdout - command.Stdin = os.Stdin - command.Stderr = os.Stderr - error := command.Run() - if error != nil { - fmt.Println("Error received from child process") - log.Fatal(error) - } - } else { - cmdArr :=[]string{packageManagerCommand, similarTo[0], strings.Join(args[1:], " ")} - cmdStr := strings.Join(cmdArr, " ") - command := exec.Command("sh", "-c", cmdStr) - command.Stdout = os.Stdout - command.Stdin = os.Stdin - command.Stderr = os.Stderr - error := command.Run() - if error != nil { - fmt.Println("Error received from child process") - log.Fatal(error) - } - } - } else if len(similarTo) != 1 && len(args) >= 1 && similarTo != nil { - fmt.Println("Ambiguous:", similarTo) - } else if similarTo == nil && len(args) >= 1 { - fmt.Println("Command", args[0], "not known") - os.Exit(1) - } else { - if useRootBool { - cmdArr :=[]string{rootCommand, packageManagerCommand, similarTo[0]} - cmdStr := strings.Join(cmdArr, " ") - command := exec.Command("sh", "-c", cmdStr) - command.Stdout = os.Stdout - command.Stdin = os.Stdin - command.Stderr = os.Stderr - error := command.Run() - if error != nil { - fmt.Println("Error received from child process") - log.Fatal(error) - } - } else { - cmdArr :=[]string{packageManagerCommand, similarTo[0]} - cmdStr := strings.Join(cmdArr, " ") - command := exec.Command("sh", "-c", cmdStr) - command.Stdout = os.Stdout - command.Stdin = os.Stdin - command.Stderr = os.Stderr - error := command.Run() - if error != nil { - fmt.Println("Error received from child process") - log.Fatal(error) - } - } - } -} \ No newline at end of file diff --git a/slices.go b/slices.go new file mode 100644 index 0000000..0e35773 --- /dev/null +++ b/slices.go @@ -0,0 +1,39 @@ +package main + +// Remove an element at an index from a slice +func removeAtIndex(s []string, index int) []string { + return append(s[:index], s[index+1:]...) +} + +// Check if slice contains string +func Contains(slice []string, val string) bool { + for _, item := range slice { + if item == val { + return true + } + } + return false +} + +func Max(array []float64) float64 { + var max = array[0] + var min = array[0] + for _, value := range array { + if max < value { + max = value + } + if min > value { + min = value + } + } + return max +} + +func Find(slice []string, val string) int { + for i, item := range slice { + if item == val { + return i + } + } + return -1 +} \ No newline at end of file diff --git a/usage.go b/usage.go new file mode 100644 index 0000000..e9db21a --- /dev/null +++ b/usage.go @@ -0,0 +1,28 @@ +package main + +import ( + "fmt" + "strings" +) + +// Print help screen +func printHelpMessage(packageManagerCommand string, useRootBool bool, rootCommand string, commands []string, shortcuts []string) { + fmt.Println("Arsen Musayelyan's Package Manager Wrapper") + fmt.Println("Current package manager is:", packageManagerCommand) + if useRootBool { fmt.Println("Using root with command:", rootCommand) } else { fmt.Println("Not using root") } + fmt.Println() + fmt.Println("Usage: pak [package]") + fmt.Println("Example: pak in hello") + fmt.Println() + fmt.Println("The available commands are:") + fmt.Println(strings.Join(commands, "\n")) + fmt.Println() + fmt.Println("The available shortcuts are:") + fmt.Println(strings.Join(shortcuts, "\n")) + fmt.Println() + fmt.Println("The available flags are:") + fmt.Println("--help, -h: Shows this help screen") + fmt.Println("--root, -r: Bypasses root user check") + fmt.Println() + fmt.Println("Pak uses a string distance algorithm, so `pak in` is valid as is `pak inst` or `pak install`") +} \ No newline at end of file