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