mirror of
https://github.com/rwinkhart/MUTN.git
synced 2026-08-27 20:36:29 -04:00
Refactor for better modularity (share more code among edit functions, rename "offline" package to "backend"
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
#!/bin/sh
|
||||
gofmt -l -w -s ./src/cli/*.go
|
||||
gofmt -l -w -s ./src/offline/*.go
|
||||
gofmt -l -w -s ./src/backend/*.go
|
||||
git add -f extra src .gitignore commit.sh go.mod go.sum LICENSE main.go README.md
|
||||
git commit -m "$1"
|
||||
git push
|
||||
|
||||
@@ -2,8 +2,8 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/rwinkhart/MUTN/src/backend"
|
||||
"github.com/rwinkhart/MUTN/src/cli"
|
||||
"github.com/rwinkhart/MUTN/src/offline"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
@@ -23,34 +23,34 @@ func main() {
|
||||
if strings.HasPrefix(args[1], "/") {
|
||||
|
||||
// store location of target entry
|
||||
targetLocation := offline.TargetLocationFormat(args[1][1:])
|
||||
targetLocation := backend.TargetLocationFormat(args[1][1:])
|
||||
|
||||
// entry reader shortcut (if no other arguments are supplied)
|
||||
if argsCount == 2 {
|
||||
cli.EntryReaderShortcut(targetLocation, true, false)
|
||||
cli.EntryReaderDecrypt(targetLocation, true, false)
|
||||
// perform other operations on the entry (if other arguments are supplied)
|
||||
} else if argsCount == 3 || (argsCount == 4 && (args[3] == "show" || args[3] == "-s")) {
|
||||
if argsCount == 3 { // default to "password" if no field is specified (for copy, edit, and add)
|
||||
switch args[2] {
|
||||
case "show", "-s":
|
||||
cli.EntryReaderShortcut(targetLocation, false, false)
|
||||
cli.EntryReaderDecrypt(targetLocation, false, false)
|
||||
case "copy":
|
||||
offline.CopyArgument(targetLocation, 0, args[0])
|
||||
backend.CopyArgument(targetLocation, 0, args[0])
|
||||
case "edit":
|
||||
cli.EditEntry(targetLocation, true, 0)
|
||||
cli.EditEntryField(targetLocation, true, 0)
|
||||
case "gen":
|
||||
cli.AddEntry(targetLocation, true, 1)
|
||||
case "add":
|
||||
cli.AddEntry(targetLocation, true, 0)
|
||||
case "shear":
|
||||
offline.Shear(targetLocation)
|
||||
backend.Shear(targetLocation)
|
||||
default:
|
||||
cli.HelpMain()
|
||||
}
|
||||
} else { // handle "show" or "-s" argument for gen, edit, and add
|
||||
switch args[2] {
|
||||
case "edit":
|
||||
cli.EditEntry(targetLocation, false, 0)
|
||||
cli.EditEntryField(targetLocation, false, 0)
|
||||
case "gen":
|
||||
cli.AddEntry(targetLocation, false, 1)
|
||||
case "add":
|
||||
@@ -82,7 +82,7 @@ func main() {
|
||||
default:
|
||||
cli.HelpCopy()
|
||||
}
|
||||
offline.CopyArgument(targetLocation, field, args[0])
|
||||
backend.CopyArgument(targetLocation, field, args[0])
|
||||
case "edit":
|
||||
var field int // indicates which field to edit
|
||||
switch args[3] {
|
||||
@@ -109,13 +109,13 @@ func main() {
|
||||
cli.HelpEdit()
|
||||
}
|
||||
if argsCount == 4 {
|
||||
cli.EditEntry(targetLocation, true, field)
|
||||
cli.EditEntryField(targetLocation, true, field)
|
||||
} else {
|
||||
switch args[4] {
|
||||
case "show", "-s":
|
||||
cli.EditEntry(targetLocation, false, field)
|
||||
cli.EditEntryField(targetLocation, false, field)
|
||||
default:
|
||||
cli.EditEntry(targetLocation, true, field)
|
||||
cli.EditEntryField(targetLocation, true, field)
|
||||
}
|
||||
}
|
||||
case "gen":
|
||||
@@ -151,7 +151,7 @@ func main() {
|
||||
case "note", "-n":
|
||||
cli.AddEntry(targetLocation, true, 2)
|
||||
case "folder", "-f":
|
||||
offline.AddFolder(targetLocation)
|
||||
backend.AddFolder(targetLocation)
|
||||
default:
|
||||
cli.HelpAdd()
|
||||
}
|
||||
@@ -164,20 +164,20 @@ func main() {
|
||||
} else {
|
||||
switch args[1] {
|
||||
case "clipclear":
|
||||
offline.ClipClearArgument()
|
||||
backend.ClipClearArgument()
|
||||
case "sync":
|
||||
if !offline.Windows {
|
||||
if !backend.Windows {
|
||||
cli.SshypSync() // TODO Remove after native sync is implemented
|
||||
} else {
|
||||
fmt.Println(offline.AnsiError + "\"sync\" is not yet implemented" + offline.AnsiReset)
|
||||
fmt.Println(backend.AnsiError + "\"sync\" is not yet implemented" + backend.AnsiReset)
|
||||
}
|
||||
os.Exit(0)
|
||||
case "init":
|
||||
cli.TempInitCli()
|
||||
case "tweak":
|
||||
fmt.Println(offline.AnsiError + "\"tweak\" is not yet implemented" + offline.AnsiReset)
|
||||
fmt.Println(backend.AnsiError + "\"tweak\" is not yet implemented" + backend.AnsiReset)
|
||||
os.Exit(0)
|
||||
// TODO offline.Tweak()
|
||||
// TODO backend.Tweak()
|
||||
case "copy":
|
||||
cli.HelpCopy()
|
||||
case "edit":
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package offline
|
||||
package backend
|
||||
|
||||
import (
|
||||
"os"
|
||||
@@ -1,6 +1,6 @@
|
||||
//go:build !windows
|
||||
|
||||
package offline
|
||||
package backend
|
||||
|
||||
// EntryRoot path to libmutton entry directory
|
||||
var EntryRoot = home + "/.local/share/libmutton"
|
||||
@@ -1,6 +1,6 @@
|
||||
//go:build windows
|
||||
|
||||
package offline
|
||||
package backend
|
||||
|
||||
// EntryRoot path to libmutton entry directory
|
||||
var EntryRoot = home + "\\AppData\\Local\\libmutton\\entries"
|
||||
@@ -1,4 +1,4 @@
|
||||
package offline
|
||||
package backend
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -1,4 +1,4 @@
|
||||
package offline
|
||||
package backend
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -1,4 +1,4 @@
|
||||
package offline
|
||||
package backend
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
@@ -41,7 +41,7 @@ func CopyArgument(targetLocation string, field int, executableName string) {
|
||||
|
||||
for { // keep field copied to clipboard, refresh on 30-second intervals
|
||||
currentTime := time.Now()
|
||||
copyField(genTOTP(secret, currentTime, forSteam), "")
|
||||
copyField(GenTOTP(secret, currentTime, forSteam), "")
|
||||
// sleep until next 30-second interval
|
||||
time.Sleep(time.Duration(30-(currentTime.Second()%30)) * time.Second)
|
||||
}
|
||||
@@ -68,8 +68,8 @@ func ClipClearArgument() {
|
||||
}
|
||||
}
|
||||
|
||||
// genTOTP generates a TOTP token from a secret (supports standard and Steam TOTP)
|
||||
func genTOTP(secret string, time time.Time, forSteam bool) string {
|
||||
// GenTOTP generates a TOTP token from a secret (supports standard and Steam TOTP)
|
||||
func GenTOTP(secret string, time time.Time, forSteam bool) string {
|
||||
var totpToken string
|
||||
var err error
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//go:build darwin
|
||||
|
||||
package offline
|
||||
package backend
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -1,6 +1,6 @@
|
||||
//go:build termux
|
||||
|
||||
package offline
|
||||
package backend
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -1,6 +1,6 @@
|
||||
//go:build !windows && !darwin && !termux
|
||||
|
||||
package offline
|
||||
package backend
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -1,6 +1,6 @@
|
||||
//go:build windows
|
||||
|
||||
package offline
|
||||
package backend
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -1,10 +1,26 @@
|
||||
package offline
|
||||
package backend
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// GetOldEntryData decrypts and returns old entry data (with all required lines present)
|
||||
func GetOldEntryData(targetLocation string, field int) []string {
|
||||
// ensure targetLocation exists
|
||||
TargetIsFile(targetLocation, true, 2)
|
||||
|
||||
// read old entry data
|
||||
unencryptedEntry := DecryptGPG(targetLocation)
|
||||
|
||||
// return the old entry data with all required lines present
|
||||
if field > 0 {
|
||||
return EnsureSliceLength(unencryptedEntry, field)
|
||||
} else {
|
||||
return unencryptedEntry
|
||||
}
|
||||
}
|
||||
|
||||
// Rename renames oldLocation to newLocation
|
||||
func Rename(oldLocation string, newLocation string) {
|
||||
// ensure newLocation does not exist
|
||||
@@ -1,4 +1,4 @@
|
||||
package offline
|
||||
package backend
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -1,4 +1,4 @@
|
||||
package offline
|
||||
package backend
|
||||
|
||||
import (
|
||||
"os"
|
||||
@@ -1,6 +1,6 @@
|
||||
//go:build !windows
|
||||
|
||||
package offline
|
||||
package backend
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -1,6 +1,6 @@
|
||||
//go:build windows
|
||||
|
||||
package offline
|
||||
package backend
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -1,4 +1,4 @@
|
||||
package offline
|
||||
package backend
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -1,6 +1,6 @@
|
||||
//go:build !windows
|
||||
|
||||
package offline
|
||||
package backend
|
||||
|
||||
// TargetLocationFormat returns the target location of an entry formatted for the current platform
|
||||
func TargetLocationFormat(entryName string) string {
|
||||
@@ -1,6 +1,6 @@
|
||||
//go:build windows
|
||||
|
||||
package offline
|
||||
package backend
|
||||
|
||||
import "strings"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package offline
|
||||
package backend
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
+2
-2
@@ -1,14 +1,14 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"github.com/rwinkhart/MUTN/src/offline"
|
||||
"github.com/rwinkhart/MUTN/src/backend"
|
||||
termy "golang.org/x/crypto/ssh/terminal"
|
||||
"os"
|
||||
)
|
||||
|
||||
// global variables used across multiple files
|
||||
var (
|
||||
rootLength = len(offline.EntryRoot)
|
||||
rootLength = len(backend.EntryRoot)
|
||||
width, _, _ = termy.GetSize(int(os.Stdout.Fd()))
|
||||
)
|
||||
|
||||
|
||||
+5
-5
@@ -2,16 +2,16 @@ package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/rwinkhart/MUTN/src/offline"
|
||||
"github.com/rwinkhart/MUTN/src/backend"
|
||||
"os"
|
||||
)
|
||||
|
||||
// AddEntry creates a new entry at targetLocation by taking user input via CLI prompts
|
||||
// entryType: 0 = standard (password), 1 = auto-generated password, 2 = note
|
||||
func AddEntry(targetLocation string, hidePassword bool, entryType uint8) {
|
||||
_, isAccessible := offline.TargetIsFile(targetLocation, false, 0)
|
||||
_, isAccessible := backend.TargetIsFile(targetLocation, false, 0)
|
||||
if isAccessible {
|
||||
fmt.Println(offline.AnsiError + "Target location already exists" + offline.AnsiReset)
|
||||
fmt.Println(backend.AnsiError + "Target location already exists" + backend.AnsiReset)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ func AddEntry(targetLocation string, hidePassword bool, entryType uint8) {
|
||||
if entryType == 0 {
|
||||
password = inputHidden("Password:")
|
||||
} else {
|
||||
password = offline.StringGen(inputInt("Password length:", -1), inputBinary("Generate a complex (special characters) password?"), 0.2)
|
||||
password = backend.StringGen(inputInt("Password length:", -1), inputBinary("Generate a complex (special characters) password?"), 0.2)
|
||||
}
|
||||
|
||||
url := input("URL:")
|
||||
@@ -41,5 +41,5 @@ func AddEntry(targetLocation string, hidePassword bool, entryType uint8) {
|
||||
}
|
||||
|
||||
// write and preview the new entry
|
||||
writeEntryShortcut(targetLocation, unencryptedEntry, hidePassword)
|
||||
writeEntryCLI(targetLocation, unencryptedEntry, hidePassword)
|
||||
}
|
||||
|
||||
+23
-39
@@ -3,7 +3,7 @@ package cli
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/rwinkhart/MUTN/src/offline"
|
||||
"github.com/rwinkhart/MUTN/src/backend"
|
||||
"os"
|
||||
"os/exec"
|
||||
"reflect"
|
||||
@@ -13,25 +13,19 @@ import (
|
||||
// RenameCli renames an entry at oldLocation to a new location (user input)
|
||||
func RenameCli(oldLocation string) {
|
||||
// ensure targetLocation exists
|
||||
offline.TargetIsFile(oldLocation, true, 0)
|
||||
backend.TargetIsFile(oldLocation, true, 0)
|
||||
|
||||
// prompt user for new location and rename
|
||||
newLocation := offline.EntryRoot + offline.PathSeparator + input("New location:")
|
||||
offline.Rename(oldLocation, newLocation)
|
||||
newLocation := backend.EntryRoot + backend.PathSeparator + input("New location:")
|
||||
backend.Rename(oldLocation, newLocation)
|
||||
|
||||
// exit is done from offline.Rename
|
||||
// exit is done from backend.Rename
|
||||
}
|
||||
|
||||
// EditEntry edits a field of an entry at targetLocation (user input), does not allow for editing notes
|
||||
func EditEntry(targetLocation string, hidePassword bool, field int) {
|
||||
// ensure targetLocation exists
|
||||
offline.TargetIsFile(targetLocation, true, 2)
|
||||
|
||||
// read old entry data
|
||||
unencryptedEntry := offline.DecryptGPG(targetLocation)
|
||||
|
||||
// ensure slice is long enough for field
|
||||
unencryptedEntry = offline.EnsureSliceLength(unencryptedEntry, field)
|
||||
// EditEntryField edits a field of an entry at targetLocation (user input), does not allow for editing notes
|
||||
func EditEntryField(targetLocation string, hidePassword bool, field int) {
|
||||
// fetch old entry data (with all required lines present)
|
||||
unencryptedEntry := backend.GetOldEntryData(targetLocation, field)
|
||||
|
||||
// edit the field
|
||||
switch field {
|
||||
@@ -44,20 +38,13 @@ func EditEntry(targetLocation string, hidePassword bool, field int) {
|
||||
}
|
||||
|
||||
// write and preview the modified entry
|
||||
writeEntryShortcut(targetLocation, unencryptedEntry, hidePassword)
|
||||
writeEntryCLI(targetLocation, unencryptedEntry, hidePassword)
|
||||
}
|
||||
|
||||
// EditEntryNote edits the note of an entry at targetLocation (user input)
|
||||
func EditEntryNote(targetLocation string, hidePassword bool) {
|
||||
// ensure targetLocation exists
|
||||
offline.TargetIsFile(targetLocation, true, 2)
|
||||
|
||||
// read old entry data
|
||||
unencryptedEntry := offline.DecryptGPG(targetLocation)
|
||||
|
||||
// ensure slice is long enough for note
|
||||
// avoids errors when storing non-note data
|
||||
unencryptedEntry = offline.EnsureSliceLength(unencryptedEntry, 2) // 2 is used because it is the index of URL, the last non-note field
|
||||
// fetch old entry data (with all required lines present)
|
||||
unencryptedEntry := backend.GetOldEntryData(targetLocation, 2)
|
||||
|
||||
// store non-note data separately
|
||||
nonNoteData := unencryptedEntry[:3]
|
||||
@@ -68,36 +55,33 @@ func EditEntryNote(targetLocation string, hidePassword bool) {
|
||||
// edit the note
|
||||
editedNote, noteEdited := editNote(noteData)
|
||||
if !noteEdited { // exit early if the note was not edited
|
||||
fmt.Println(offline.AnsiError + "Entry is unchanged" + offline.AnsiReset)
|
||||
fmt.Println(backend.AnsiError + "Entry is unchanged" + backend.AnsiReset)
|
||||
os.Exit(1)
|
||||
}
|
||||
unencryptedEntry = append(nonNoteData, editedNote...)
|
||||
|
||||
// write and preview the modified entry
|
||||
writeEntryShortcut(targetLocation, unencryptedEntry, hidePassword)
|
||||
writeEntryCLI(targetLocation, unencryptedEntry, hidePassword)
|
||||
}
|
||||
|
||||
// GenUpdate generates a new password for an entry at targetLocation (user input)
|
||||
func GenUpdate(targetLocation string, hidePassword bool) {
|
||||
// ensure targetLocation exists
|
||||
offline.TargetIsFile(targetLocation, true, 2)
|
||||
|
||||
// read old entry data
|
||||
unencryptedEntry := offline.DecryptGPG(targetLocation)
|
||||
// fetch old entry data
|
||||
unencryptedEntry := backend.GetOldEntryData(targetLocation, 0)
|
||||
|
||||
// generate a new password
|
||||
unencryptedEntry[0] = offline.StringGen(inputInt("Password length:", -1), inputBinary("Generate a complex (special characters) password?"), 0.2)
|
||||
unencryptedEntry[0] = backend.StringGen(inputInt("Password length:", -1), inputBinary("Generate a complex (special characters) password?"), 0.2)
|
||||
|
||||
// write and preview the modified entry
|
||||
writeEntryShortcut(targetLocation, unencryptedEntry, hidePassword)
|
||||
writeEntryCLI(targetLocation, unencryptedEntry, hidePassword)
|
||||
}
|
||||
|
||||
// editNote uses the user-specified text editor to edit an existing note (or create a new one if baseNote is empty)
|
||||
// returns the edited note and a boolean indicating whether the note was edited
|
||||
func editNote(baseNote []string) ([]string, bool) {
|
||||
tempFile := offline.CreateTempFile()
|
||||
tempFile := backend.CreateTempFile()
|
||||
defer os.Remove(tempFile.Name())
|
||||
editor := offline.ReadConfig([]string{"textEditor"})[0]
|
||||
editor := backend.ReadConfig([]string{"textEditor"})[0]
|
||||
|
||||
// write baseNote to tempFile (if it is not empty)
|
||||
if len(baseNote) > 0 {
|
||||
@@ -116,13 +100,13 @@ func editNote(baseNote []string) ([]string, bool) {
|
||||
cmd.Stderr = os.Stderr
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
panic(offline.AnsiError + "Failed to write note with " + editor + offline.AnsiReset)
|
||||
panic(backend.AnsiError + "Failed to write note with " + editor + backend.AnsiReset)
|
||||
}
|
||||
|
||||
// open the tempFile for reading
|
||||
tempFile, err = os.Open(tempFile.Name())
|
||||
if err != nil {
|
||||
panic(offline.AnsiError + "Failed to write note with " + editor + offline.AnsiReset)
|
||||
panic(backend.AnsiError + "Failed to write note with " + editor + backend.AnsiReset)
|
||||
}
|
||||
|
||||
// read the edited note from the tempFile
|
||||
@@ -136,7 +120,7 @@ func editNote(baseNote []string) ([]string, bool) {
|
||||
tempFile.Close()
|
||||
|
||||
// remove trailing empty strings from the edited note
|
||||
note = offline.RemoveTrailingEmptyStrings(note)
|
||||
note = backend.RemoveTrailingEmptyStrings(note)
|
||||
|
||||
// trim trailing whitespace from each note line
|
||||
for i, line := range note {
|
||||
|
||||
+11
-11
@@ -2,7 +2,7 @@ package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/rwinkhart/MUTN/src/offline"
|
||||
"github.com/rwinkhart/MUTN/src/backend"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -23,10 +23,10 @@ func determineIndentation(skippedDirList []bool, dirList []string, currentDirInd
|
||||
var trimmedDirectory = dirList[currentDirIndex]
|
||||
|
||||
// determine initial indentation multiplier based on PathSeparator occurrences
|
||||
indent := strings.Count(trimmedDirectory, offline.PathSeparator) - 1 // subtract 1 to avoid indenting root-level directories
|
||||
indent := strings.Count(trimmedDirectory, backend.PathSeparator) - 1 // subtract 1 to avoid indenting root-level directories
|
||||
|
||||
for i, skipped := range skippedDirList[:currentDirIndex] { // checks each skipped directory to determine if it is a parent to the current directory
|
||||
if strings.HasPrefix(trimmedDirectory, dirList[i]+offline.PathSeparator) { // if the current directory is the child of this iteration's directory...
|
||||
if strings.HasPrefix(trimmedDirectory, dirList[i]+backend.PathSeparator) { // if the current directory is the child of this iteration's directory...
|
||||
if skipped { // ...and this iteration's directory was skipped...
|
||||
subtractor++ // increment the subtractor to indicate that the visual indentation should be reduced
|
||||
} else {
|
||||
@@ -72,7 +72,7 @@ func printFileEntry(entry string, lastSlash int, charCounter int, colorAlternato
|
||||
}
|
||||
|
||||
// print fileEntryName to screen
|
||||
fmt.Printf("%s%s%s ", colorCode, fileEntryName, offline.AnsiReset)
|
||||
fmt.Printf("%s%s%s ", colorCode, fileEntryName, backend.AnsiReset)
|
||||
|
||||
return charCounter, colorAlternator
|
||||
}
|
||||
@@ -84,16 +84,16 @@ func EntryListGen() {
|
||||
var dirList []string
|
||||
|
||||
// walk entry directory
|
||||
_ = filepath.WalkDir(offline.EntryRoot,
|
||||
_ = filepath.WalkDir(backend.EntryRoot,
|
||||
func(fullPath string, entry fs.DirEntry, err error) error {
|
||||
|
||||
// check for errors encountered while walking directory
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
fmt.Println(offline.AnsiError + "\nThe entry directory does not exist - run \"mutn init\" to create it" + offline.AnsiReset)
|
||||
fmt.Println(backend.AnsiError + "\nThe entry directory does not exist - run \"mutn init\" to create it" + backend.AnsiReset)
|
||||
} else {
|
||||
// otherwise, print the source of the error
|
||||
fmt.Println(offline.AnsiError + "\nAn unexpected error occurred while generating the entry list: " + err.Error() + offline.AnsiReset)
|
||||
fmt.Println(backend.AnsiError + "\nAn unexpected error occurred while generating the entry list: " + err.Error() + backend.AnsiReset)
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -112,7 +112,7 @@ func EntryListGen() {
|
||||
})
|
||||
|
||||
// print header bar w/total entry count
|
||||
fmt.Print("\n"+ansiBlackOnWhite, len(fileList), " libmutton entries:"+offline.AnsiReset)
|
||||
fmt.Print("\n"+ansiBlackOnWhite, len(fileList), " libmutton entries:"+backend.AnsiReset)
|
||||
|
||||
// dirList iteration
|
||||
dirListLength := len(dirList) // save length for multiple references below
|
||||
@@ -137,7 +137,7 @@ func EntryListGen() {
|
||||
|
||||
// check if next directory is within the current one
|
||||
if dirListLength > i+1 {
|
||||
if nextDir := dirList[i+1]; directory == nextDir[:strings.LastIndex(nextDir, offline.PathSeparator)] {
|
||||
if nextDir := dirList[i+1]; directory == nextDir[:strings.LastIndex(nextDir, backend.PathSeparator)] {
|
||||
containsSubdirectory = true
|
||||
} else {
|
||||
containsSubdirectory = false
|
||||
@@ -151,7 +151,7 @@ func EntryListGen() {
|
||||
for _, file := range fileList {
|
||||
|
||||
// print the current file if it belongs in the current directory - otherwise, break the loop and move on to the next directory
|
||||
if lastSlash := strings.LastIndex(file, offline.PathSeparator) + 1; file[:lastSlash-1] == directory {
|
||||
if lastSlash := strings.LastIndex(file, backend.PathSeparator) + 1; file[:lastSlash-1] == directory {
|
||||
|
||||
// print directory header if this is the first run of the loop
|
||||
if !containsFiles {
|
||||
@@ -171,7 +171,7 @@ func EntryListGen() {
|
||||
skippedDirList[i] = false // the directory header is being printed, indicate that it is not being skipped
|
||||
indent, vanityDirectory = determineIndentation(skippedDirList, dirList, i) // calculate the final indentation multiplier
|
||||
printDirectoryHeader(vanityDirectory, indent)
|
||||
fmt.Print(strings.Repeat(" ", indent*2) + ansiEmptyDirectoryWarning + "-empty directory-" + offline.AnsiReset)
|
||||
fmt.Print(strings.Repeat(" ", indent*2) + ansiEmptyDirectoryWarning + "-empty directory-" + backend.AnsiReset)
|
||||
} else { // warn if the only thing that exists is the root-level directory
|
||||
fmt.Print("\n\nNothing's here! For help creating your first entry, run \"mutn help\".")
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@ package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/rwinkhart/MUTN/src/offline"
|
||||
"github.com/rwinkhart/MUTN/src/backend"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func printDirectoryHeader(vanityDirectory string, indent int) {
|
||||
fmt.Printf("\n\n"+strings.Repeat(" ", indent*2)+ansiDirectoryHeader+"%s/"+offline.AnsiReset+"\n", vanityDirectory)
|
||||
fmt.Printf("\n\n"+strings.Repeat(" ", indent*2)+ansiDirectoryHeader+"%s/"+backend.AnsiReset+"\n", vanityDirectory)
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@ package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/rwinkhart/MUTN/src/offline"
|
||||
"github.com/rwinkhart/MUTN/src/backend"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func printDirectoryHeader(vanityDirectory string, indent int) {
|
||||
fmt.Printf("\n\n"+strings.Repeat(" ", indent*2)+ansiDirectoryHeader+"%s/"+offline.AnsiReset+"\n", strings.ReplaceAll(vanityDirectory, offline.PathSeparator, "/"))
|
||||
fmt.Printf("\n\n"+strings.Repeat(" ", indent*2)+ansiDirectoryHeader+"%s/"+backend.AnsiReset+"\n", strings.ReplaceAll(vanityDirectory, backend.PathSeparator, "/"))
|
||||
}
|
||||
|
||||
+11
-11
@@ -5,7 +5,7 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/rwinkhart/MUTN/src/offline"
|
||||
"github.com/rwinkhart/MUTN/src/backend"
|
||||
|
||||
"github.com/charmbracelet/glamour"
|
||||
)
|
||||
@@ -22,24 +22,24 @@ func EntryReader(decryptedEntry []string, hidePassword bool, sync bool) {
|
||||
// if the first field (password) is not empty, print it
|
||||
if decryptedEntry[0] != "" {
|
||||
if !hidePassword {
|
||||
fmt.Print(ansiDirectoryHeader + "Password:" + offline.AnsiReset + "\n" + ansiShownPassword + decryptedEntry[0] + offline.AnsiReset + "\n\n")
|
||||
fmt.Print(ansiDirectoryHeader + "Password:" + backend.AnsiReset + "\n" + ansiShownPassword + decryptedEntry[0] + backend.AnsiReset + "\n\n")
|
||||
} else {
|
||||
fmt.Print(ansiDirectoryHeader + "Password:" + offline.AnsiReset + "\n" + ansiEmptyDirectoryWarning + "End command in \"show\" or \"-s\" to view" + offline.AnsiReset + "\n\n")
|
||||
fmt.Print(ansiDirectoryHeader + "Password:" + backend.AnsiReset + "\n" + ansiEmptyDirectoryWarning + "End command in \"show\" or \"-s\" to view" + backend.AnsiReset + "\n\n")
|
||||
}
|
||||
}
|
||||
case 1:
|
||||
// if the second field (username) is not empty, print it
|
||||
if decryptedEntry[1] != "" {
|
||||
fmt.Print(ansiDirectoryHeader + "Username:" + offline.AnsiReset + "\n" + decryptedEntry[1] + "\n\n")
|
||||
fmt.Print(ansiDirectoryHeader + "Username:" + backend.AnsiReset + "\n" + decryptedEntry[1] + "\n\n")
|
||||
}
|
||||
case 2:
|
||||
// if the third field (url) is not empty, print it
|
||||
if decryptedEntry[2] != "" {
|
||||
fmt.Print(ansiDirectoryHeader + "URL:" + offline.AnsiReset + "\n" + decryptedEntry[2] + "\n\n")
|
||||
fmt.Print(ansiDirectoryHeader + "URL:" + backend.AnsiReset + "\n" + decryptedEntry[2] + "\n\n")
|
||||
}
|
||||
case 3:
|
||||
// print the notes header
|
||||
fmt.Println(ansiDirectoryHeader + "Notes:" + offline.AnsiReset)
|
||||
fmt.Println(ansiDirectoryHeader + "Notes:" + backend.AnsiReset)
|
||||
|
||||
// combine remaining fields into a single string (for markdown rendering)
|
||||
var markdownNotes []string
|
||||
@@ -57,17 +57,17 @@ func EntryReader(decryptedEntry []string, hidePassword bool, sync bool) {
|
||||
}
|
||||
}
|
||||
|
||||
if sync && !offline.Windows {
|
||||
if sync && !backend.Windows {
|
||||
SshypSync() // TODO Remove after native sync is implemented
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// EntryReaderShortcut is a shortcut for EntryReader that decrypts a GPG-encrypted file and prints the contents
|
||||
func EntryReaderShortcut(targetLocation string, hidePassword bool, sync bool) {
|
||||
if isFile, _ := offline.TargetIsFile(targetLocation, true, 2); isFile {
|
||||
EntryReader(offline.DecryptGPG(targetLocation), hidePassword, sync)
|
||||
// EntryReaderDecrypt is a wrapper for EntryReader that first decrypts a GPG-encrypted file before sending it to EntryReader
|
||||
func EntryReaderDecrypt(targetLocation string, hidePassword bool, sync bool) {
|
||||
if isFile, _ := backend.TargetIsFile(targetLocation, true, 2); isFile {
|
||||
EntryReader(backend.DecryptGPG(targetLocation), hidePassword, sync)
|
||||
}
|
||||
// do not exit, as this is the job of EntryReader
|
||||
}
|
||||
|
||||
+6
-6
@@ -2,7 +2,7 @@ package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/rwinkhart/MUTN/src/offline"
|
||||
"github.com/rwinkhart/MUTN/src/backend"
|
||||
"os"
|
||||
)
|
||||
|
||||
@@ -11,20 +11,20 @@ func TempInitCli() {
|
||||
// gpgID
|
||||
var gpgID string
|
||||
if inputBinary("Auto-generate GPG key?") {
|
||||
gpgID = offline.GpgKeyGen()
|
||||
gpgID = backend.GpgKeyGen()
|
||||
} else {
|
||||
// select GPG key from menu
|
||||
uidSlice := offline.GpgUIDListGen()
|
||||
uidSlice := backend.GpgUIDListGen()
|
||||
gpgIDInt := inputMenuGen("Select GPG key:", uidSlice)
|
||||
if gpgIDInt == 0 {
|
||||
fmt.Println(offline.AnsiError + "No GPG keys found - please generate one" + offline.AnsiReset)
|
||||
fmt.Println(backend.AnsiError + "No GPG keys found - please generate one" + backend.AnsiReset)
|
||||
os.Exit(1)
|
||||
}
|
||||
gpgID = uidSlice[gpgIDInt-1]
|
||||
}
|
||||
|
||||
// textEditor
|
||||
textEditor := input("Text editor (leave blank for $EDITOR, falls back to \"" + offline.FallbackEditor + "\"):")
|
||||
textEditor := input("Text editor (leave blank for $EDITOR, falls back to \"" + backend.FallbackEditor + "\"):")
|
||||
|
||||
offline.TempInit(map[string]string{"textEditor": textEditor, "gpgID": gpgID})
|
||||
backend.TempInit(map[string]string{"textEditor": textEditor, "gpgID": gpgID})
|
||||
}
|
||||
|
||||
+30
-30
@@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/rwinkhart/MUTN/src/offline"
|
||||
"github.com/rwinkhart/MUTN/src/backend"
|
||||
)
|
||||
|
||||
// global constants used only in this file
|
||||
@@ -14,13 +14,13 @@ const (
|
||||
)
|
||||
|
||||
func HelpMain() {
|
||||
fmt.Print(ansiBold + "\nMUTN | Copyright (c) 2024 Randall Winkhart\n" + offline.AnsiReset + `
|
||||
fmt.Print(ansiBold + "\nMUTN | Copyright (c) 2024 Randall Winkhart\n" + backend.AnsiReset + `
|
||||
This software exists under the MIT license; you may redistribute it under certain conditions.
|
||||
This program comes with absolutely no warranty; type "mutn version" for details.
|
||||
|
||||
` + ansiBold + "Usage:" + offline.AnsiReset + ` mutn [/<entry name> [argument] [option]] | [argument]
|
||||
` + ansiBold + "Usage:" + backend.AnsiReset + ` mutn [/<entry name> [argument] [option]] | [argument]
|
||||
|
||||
` + ansiBold + "Arguments:" + offline.AnsiReset + `
|
||||
` + ansiBold + "Arguments:" + backend.AnsiReset + `
|
||||
help|-h Bring up this menu
|
||||
version|-v Display version and license information
|
||||
init Set up MUTN (generates libmutton.ini)
|
||||
@@ -32,7 +32,7 @@ This program comes with absolutely no warranty; type "mutn version" for details.
|
||||
shear Delete an existing entry
|
||||
sync Manually sync the entry directory
|
||||
|
||||
` + ansiBold + "Options:" + offline.AnsiReset + `
|
||||
` + ansiBold + "Options:" + backend.AnsiReset + `
|
||||
copy:
|
||||
password|-pw|<blank> Copy the password of an entry to your clipboard
|
||||
username|-u Copy the username of an entry to your clipboard
|
||||
@@ -52,17 +52,17 @@ This program comes with absolutely no warranty; type "mutn version" for details.
|
||||
note|-n Add a note entry
|
||||
folder|-f Add a new folder for entries
|
||||
|
||||
` + ansiBold + "Tip 1:" + offline.AnsiReset + ` You can quickly read an entry with "mutn /<entry name>"
|
||||
` + ansiBold + "Tip 2:" + offline.AnsiReset + ` Type "mutn" (no arguments/options) to view a list of saved entries
|
||||
` + ansiBold + "Tip 3:" + offline.AnsiReset + ` Provide "add", "edit", "copy", or "gen" as the only argument to receive more specific help
|
||||
` + ansiBold + "Tip 4:" + offline.AnsiReset + " Using \"add\", \"edit\", or \"copy\" without specifying an option (field) will default to \"password\"\n\n")
|
||||
` + ansiBold + "Tip 1:" + backend.AnsiReset + ` You can quickly read an entry with "mutn /<entry name>"
|
||||
` + ansiBold + "Tip 2:" + backend.AnsiReset + ` Type "mutn" (no arguments/options) to view a list of saved entries
|
||||
` + ansiBold + "Tip 3:" + backend.AnsiReset + ` Provide "add", "edit", "copy", or "gen" as the only argument to receive more specific help
|
||||
` + ansiBold + "Tip 4:" + backend.AnsiReset + " Using \"add\", \"edit\", or \"copy\" without specifying an option (field) will default to \"password\"\n\n")
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func HelpAdd() {
|
||||
fmt.Print(ansiBold + "\nUsage:" + offline.AnsiReset + ` mutn /<entry name> add <option>
|
||||
fmt.Print(ansiBold + "\nUsage:" + backend.AnsiReset + ` mutn /<entry name> add <option>
|
||||
|
||||
` + ansiBold + "Options:" + offline.AnsiReset + `
|
||||
` + ansiBold + "Options:" + backend.AnsiReset + `
|
||||
add:
|
||||
password|-pw|<blank> Add a password entry
|
||||
note|-n Add a note entry
|
||||
@@ -71,9 +71,9 @@ func HelpAdd() {
|
||||
}
|
||||
|
||||
func HelpEdit() {
|
||||
fmt.Print(ansiBold + "\nUsage:" + offline.AnsiReset + ` mutn /<entry name> edit <option>
|
||||
fmt.Print(ansiBold + "\nUsage:" + backend.AnsiReset + ` mutn /<entry name> edit <option>
|
||||
|
||||
` + ansiBold + "Options:" + offline.AnsiReset + `
|
||||
` + ansiBold + "Options:" + backend.AnsiReset + `
|
||||
edit:
|
||||
password|-pw|<blank> Change the password of an entry
|
||||
username|-u Change the username of an entry
|
||||
@@ -84,9 +84,9 @@ func HelpEdit() {
|
||||
}
|
||||
|
||||
func HelpCopy() {
|
||||
fmt.Print(ansiBold + "\nUsage:" + offline.AnsiReset + ` mutn /<entry name> copy <option>
|
||||
fmt.Print(ansiBold + "\nUsage:" + backend.AnsiReset + ` mutn /<entry name> copy <option>
|
||||
|
||||
` + ansiBold + "Options:" + offline.AnsiReset + `
|
||||
` + ansiBold + "Options:" + backend.AnsiReset + `
|
||||
copy:
|
||||
password|-pw|<blank> Copy the password in an entry to your clipboard
|
||||
username|-u Copy the username in an entry to your clipboard
|
||||
@@ -97,18 +97,18 @@ func HelpCopy() {
|
||||
}
|
||||
|
||||
func HelpGen() {
|
||||
fmt.Print(ansiBold + "\nUsage:" + offline.AnsiReset + ` mutn /<entry name> gen [option]
|
||||
fmt.Print(ansiBold + "\nUsage:" + backend.AnsiReset + ` mutn /<entry name> gen [option]
|
||||
|
||||
` + ansiBold + "Options:" + offline.AnsiReset + `
|
||||
` + ansiBold + "Options:" + backend.AnsiReset + `
|
||||
gen:
|
||||
update|-u Generate a password for an existing entry
|
||||
|
||||
` + ansiBold + "Tip:" + offline.AnsiReset + " If no options are provided, a new password entry is generated\n\n")
|
||||
` + ansiBold + "Tip:" + backend.AnsiReset + " If no options are provided, a new password entry is generated\n\n")
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func Version() {
|
||||
fmt.Print(ansiBold + "\n MIT License" + offline.AnsiReset + `
|
||||
fmt.Print(ansiBold + "\n MIT License" + backend.AnsiReset + `
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
@@ -136,18 +136,18 @@ ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
"\n\n---------------------------------------------------------" +
|
||||
"\n\n MUTN is a simple, self-hosted,\n SSH-synchronized password manager based on libmutton\n\n" +
|
||||
" .. ..\n" +
|
||||
" /()\\''.''. " + ansiVersionMeat + "♥♥♥♥" + offline.AnsiReset + " .''.''/()\\ _)\n" +
|
||||
" _. : * " + ansiVersionMeat + "♥♥♥♥♥♥ ♥♥♥♥♥♥♥♥" + offline.AnsiReset + " * : <[◎]|_|=\n" +
|
||||
" }-}-*] `..'..' " + ansiVersionMeat + "♥♥♥♥♥♥♥♥♥♥♥♥♥" + offline.AnsiReset + " `..'..' |\n" +
|
||||
" ◎-◎ // \\\\ " + ansiVersionMeat + "♥♥♥♥♥♥♥♥♥" + offline.AnsiReset + " // \\\\ /|\\\n" +
|
||||
" /()\\''.''. " + ansiVersionMeat + "♥♥♥♥" + backend.AnsiReset + " .''.''/()\\ _)\n" +
|
||||
" _. : * " + ansiVersionMeat + "♥♥♥♥♥♥ ♥♥♥♥♥♥♥♥" + backend.AnsiReset + " * : <[◎]|_|=\n" +
|
||||
" }-}-*] `..'..' " + ansiVersionMeat + "♥♥♥♥♥♥♥♥♥♥♥♥♥" + backend.AnsiReset + " `..'..' |\n" +
|
||||
" ◎-◎ // \\\\ " + ansiVersionMeat + "♥♥♥♥♥♥♥♥♥" + backend.AnsiReset + " // \\\\ /|\\\n" +
|
||||
ansiVersionOutline + "<><><><><><><><><><><><><><>-<><><><><><><><><><><><><><>\n" +
|
||||
"\\" + ansiBlackOnWhite + " " + offline.AnsiReset + ansiVersionOutline + "/\n" +
|
||||
"\\" + ansiBlackOnWhite + " MUTN Version 0.0.1 " + offline.AnsiReset + ansiVersionOutline + "/\n" +
|
||||
"\\" + ansiBlackOnWhite + " The Butchered Update " + offline.AnsiReset + ansiVersionOutline + "/\n" +
|
||||
"\\" + ansiBlackOnWhite + " " + offline.AnsiReset + ansiVersionOutline + "/\n" +
|
||||
"\\" + ansiBlackOnWhite + " Copyright (c) 2024 Randall Winkhart " + offline.AnsiReset + ansiVersionOutline + "/\n" +
|
||||
"\\" + ansiBlackOnWhite + " " + offline.AnsiReset + ansiVersionOutline + "/\n" +
|
||||
"<><><><><><><><><><><><><><>-<><><><><><><><><><><><><><>\n" + offline.AnsiReset +
|
||||
"\\" + ansiBlackOnWhite + " " + backend.AnsiReset + ansiVersionOutline + "/\n" +
|
||||
"\\" + ansiBlackOnWhite + " MUTN Version 0.0.1 " + backend.AnsiReset + ansiVersionOutline + "/\n" +
|
||||
"\\" + ansiBlackOnWhite + " The Butchered Update " + backend.AnsiReset + ansiVersionOutline + "/\n" +
|
||||
"\\" + ansiBlackOnWhite + " " + backend.AnsiReset + ansiVersionOutline + "/\n" +
|
||||
"\\" + ansiBlackOnWhite + " Copyright (c) 2024 Randall Winkhart " + backend.AnsiReset + ansiVersionOutline + "/\n" +
|
||||
"\\" + ansiBlackOnWhite + " " + backend.AnsiReset + ansiVersionOutline + "/\n" +
|
||||
"<><><><><><><><><><><><><><>-<><><><><><><><><><><><><><>\n" + backend.AnsiReset +
|
||||
"\n For more information, see:\n\n" +
|
||||
" https://github.com/rwinkhart/MUTN\n" +
|
||||
" https://github.com/rwinkhart/libmutton\n\n")
|
||||
|
||||
@@ -3,7 +3,7 @@ package cli
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/rwinkhart/MUTN/src/offline"
|
||||
"github.com/rwinkhart/MUTN/src/backend"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -71,16 +71,16 @@ func inputMenuGen(prompt string, options []string) int {
|
||||
return inputInt(prompt, len(options))
|
||||
}
|
||||
|
||||
// writeEntryShortcut writes an entry to targetLocation and previews it (errors if no data is supplied)
|
||||
func writeEntryShortcut(targetLocation string, unencryptedEntry []string, hidePassword bool) {
|
||||
if offline.EntryIsNotEmpty(unencryptedEntry) {
|
||||
// writeEntryCLI writes an entry to targetLocation and previews it (errors if no data is supplied)
|
||||
func writeEntryCLI(targetLocation string, unencryptedEntry []string, hidePassword bool) {
|
||||
if backend.EntryIsNotEmpty(unencryptedEntry) {
|
||||
// write the entry to the target location
|
||||
offline.WriteEntry(targetLocation, unencryptedEntry)
|
||||
backend.WriteEntry(targetLocation, unencryptedEntry)
|
||||
// preview the entry
|
||||
fmt.Println(ansiBold + "\nEntry Preview:" + offline.AnsiReset)
|
||||
fmt.Println(ansiBold + "\nEntry Preview:" + backend.AnsiReset)
|
||||
EntryReader(unencryptedEntry, hidePassword, true)
|
||||
} else {
|
||||
fmt.Println(offline.AnsiError + "No data supplied for entry" + offline.AnsiReset)
|
||||
fmt.Println(backend.AnsiError + "No data supplied for entry" + backend.AnsiReset)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user