Skip to content

Commit

Permalink
UI (#18)
Browse files Browse the repository at this point in the history
* feat: add lipgloss and huh

- use lipgloss to render the tables
- huh to handle the inputs, interactivity basically

* chore: update readme
  • Loading branch information
musaubrian authored Apr 13, 2024
1 parent 8d20cb5 commit 5b5865d
Show file tree
Hide file tree
Showing 9 changed files with 198 additions and 61 deletions.
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
[![Go Reference](https://pkg.go.dev/badge/github.com/musaubrian/tg.svg)](https://pkg.go.dev/github.com/musaubrian/tg)
[![tests](https://github.com/musaubrian/tg/actions/workflows/test.yml/badge.svg)](https://github.com/musaubrian/tg/actions/workflows/test.yml)

> A cli tool to help manage your passwords

A command line password manager.

Thats it.


## Installation
Expand All @@ -23,4 +26,8 @@ cd tg
go build .
```

or get one of the releases [archives](https://github.com/musaubrian/tg/releases)

Contributions are welcomed

Powered by [huh](https://github.com/charmbracelet/huh) and [lipgloss](https://github.com/charmbracelet/lipgloss)
32 changes: 25 additions & 7 deletions cmd/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"log"
"os"

"github.com/charmbracelet/huh"
"github.com/musaubrian/tg/internal/model"
"github.com/spf13/cobra"
)
Expand All @@ -25,7 +26,22 @@ var deleteSiteCmd = &cobra.Command{
Use: "site",
Short: "Remove a record using its site name",
Run: func(cmd *cobra.Command, args []string) {
var confirm bool

siteName := model.GetInput("SiteName")

title := fmt.Sprintf("This will delete all records related to site [%s]\n\nDo you want to continue?", siteName)

err := huh.NewConfirm().Title(title).Affirmative("Yes").Negative("No!!").Value(&confirm).Run()
if err != nil {
log.Fatal(err)
}

if !confirm {
fmt.Println("Stopping process")
os.Exit(1)
}

model.DeleteRecord(siteName, model.SiteName)
},
}
Expand All @@ -39,19 +55,21 @@ CAUTION!!
If multiple sites have the same username, they will all be deleted
`,
Run: func(cmd *cobra.Command, args []string) {
var confirm bool

userName := model.GetInput("UserName")
fmt.Printf("This will delete all records with the username [%s]\n", userName)
agree := model.GetInput("Continue y/N")
title := fmt.Sprintf("This will delete all records related to the user [%s]\n\nDo you want to continue?", userName)

if len(agree) < 1 || agree == "n" {
err := huh.NewConfirm().Title(title).Affirmative("Yes").Negative("No!!").Value(&confirm).Run()
if err != nil {
log.Fatal(err)
}
if !confirm {
fmt.Println("Stopping process")
os.Exit(1)
} else if agree == "y" {
model.DeleteRecord(userName, model.Username)
} else {
log.Fatal("Unknown option")
}

model.DeleteRecord(userName, model.Username)
},
}

Expand Down
11 changes: 6 additions & 5 deletions cmd/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
var listCmd = &cobra.Command{
Use: "list",
Short: "List all records in the database",
Long: `Lists all the records(sitename, username, password) available
Long: `Lists all the records(sitename, username, password) available
in the database`,
Aliases: []string{"l"},
Run: func(cmd *cobra.Command, args []string) {
Expand All @@ -22,11 +22,12 @@ var listCmd = &cobra.Command{
}
sites := model.ListAll()
if pretty {
t.AddHeader("\n#", "USERNAME", "SITE_NAME", "PASSWORD")
for i, site := range sites {
t.AddLine(i+1, site.UserName, site.Name, site.Password)
t := t.Headers("USERNAME", "SITE_NAME", "PASSWORD")

for _, site := range sites {
t.Row(site.UserName, site.Name, site.Password)
}
t.Print()
fmt.Println(t)
} else {
for _, site := range sites {
fmt.Println("\nSiteName:", site.Name)
Expand Down
19 changes: 10 additions & 9 deletions cmd/pwd.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,17 @@ parse 'c' as argument to copy it without displaying it
`,
Run: func(cmd *cobra.Command, args []string) {
pwd := utils.GeneratePassword()
if len(args) < 1 {
fmt.Println(pwd)
} else {
if args[0] == "c" {
utils.CopyToClipboard(pwd)
fmt.Println("Copied to clipboard")
} else {
log.Fatalf("Run `%s pwd -h` to see how to use this command", rootCmd.Use)
}

copyPwd, err := rootCmd.Flags().GetBool("copy")
if err != nil {
log.Fatal(err)
}
if copyPwd {
utils.CopyToClipboard(pwd)
fmt.Println("Copied to clipboard")
return
}
fmt.Println(pwd)
},
}

Expand Down
10 changes: 7 additions & 3 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ import (
"os"
"path"

"github.com/cheynewallace/tabby"
"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/lipgloss/table"
"github.com/musaubrian/tg/internal/model"
"github.com/musaubrian/tg/internal/utils"
"github.com/spf13/cobra"
)

var t = tabby.New()
var t = table.New().StyleFunc(func(row, col int) lipgloss.Style {
return lipgloss.NewStyle().Width(26)
})

// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Expand Down Expand Up @@ -54,5 +57,6 @@ func init() {
// Cobra also supports local flags, which will only run
// when this action is called directly.
// rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
rootCmd.PersistentFlags().BoolP("pretty", "p", false, "List results in a nice table format")
rootCmd.PersistentFlags().BoolP("pretty", "p", true, "List results in a nice table format")
rootCmd.PersistentFlags().BoolP("copy", "c", false, "Copy password to clipboard")
}
91 changes: 73 additions & 18 deletions cmd/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"log"

"github.com/charmbracelet/huh"
"github.com/musaubrian/tg/internal/model"
"github.com/musaubrian/tg/internal/utils"
"github.com/spf13/cobra"
Expand All @@ -24,32 +25,49 @@ var searchByUserName = &cobra.Command{
Example: `
tg search user some_username
tinygo search user some_username -p
tg search user some_username -c
`,
Run: func(cmd *cobra.Command, args []string) {
var selectedFromMultiple string

if len(args) < 1 {
log.Fatal("You did not parse value to search for")
}
pretty, err := rootCmd.Flags().GetBool("pretty")
if err != nil {
log.Fatal(err)
}
copyPwd, err := rootCmd.Flags().GetBool("copy")
if err != nil {
log.Fatal(err)
}
results, err := model.SearchRecords(args[0], model.Username)
if err != nil {
log.Fatal(err)
}

if len(results) == 1 {
utils.CopyToClipboard(results[0].Password)
fmt.Printf("Copied [%s's] password for [%s] to clipboard\n", results[0].UserName, results[0].Name)
return
if copyPwd {
if len(results) == 1 {
utils.CopyToClipboard(results[0].Password)
fmt.Printf("Copied [%s's] password for [%s] to clipboard\n", results[0].UserName, results[0].Name)
return
} else {
huh.NewSelect[string]().Title("Multiple values returned, pick from the site you want").Options(
generateHuhOpts(results, "user")...,
).Value(&selectedFromMultiple).Run()

utils.CopyToClipboard(selectedFromMultiple)
fmt.Println("Copied password to clipboard")
return
}
}

if pretty {
t.AddHeader("\n#", "USER_NAME", "SITE_NAME", "PASSWORD")
for i, v := range results {
t.AddLine(i+1, v.UserName, v.Name, v.Password)
t := t.Headers("USERNAME", "SITE_NAME", "PASSWORD")
for _, site := range results {
t.Row(site.UserName, site.Name, site.Password)
}
t.Print()
fmt.Println(t)
} else {
for _, site := range results {
fmt.Println("\nSiteName:", site.Name)
Expand All @@ -66,9 +84,11 @@ var searchbySiteName = &cobra.Command{
Short: "Search for site by sitename",
Example: `
tinygo search site some_sitename
tinygo search site some_sitename -p
tinygo search site some_sitename -c
`,
Run: func(cmd *cobra.Command, args []string) {
var selectedFromMultiple string

if len(args) < 1 {
log.Fatal("You did not parse value to search for")
}
Expand All @@ -80,17 +100,33 @@ tinygo search site some_sitename -p
if err != nil {
log.Fatal(err)
}
if len(results) == 1 {
utils.CopyToClipboard(results[0].Password)
fmt.Printf("Copied [%s's] password for [%s] to clipboard\n", results[0].UserName, results[0].Name)
return
copyPwd, err := rootCmd.Flags().GetBool("copy")
if err != nil {
log.Fatal(err)
}

if copyPwd {
if len(results) == 1 {
utils.CopyToClipboard(results[0].Password)
fmt.Printf("Copied [%s's] password for [%s] to clipboard\n", results[0].UserName, results[0].Name)
return
} else {
huh.NewSelect[string]().Title("Multiple values returned, pick the user you want").Options(
generateHuhOpts(results, "site")...,
).Value(&selectedFromMultiple).Run()

utils.CopyToClipboard(selectedFromMultiple)
fmt.Println("Copied password to clipboard")
return
}
}

if pretty {
t.AddHeader("\n#", "USER_NAME", "SITE_NAME", "PASSWORD")
for i, v := range results {
t.AddLine(i+1, v.UserName, v.Name, v.Password)
t := t.Headers("USERNAME", "SITE_NAME", "PASSWORD")
for _, site := range results {
t.Row(site.UserName, site.Name, site.Password)
}
t.Print()
fmt.Println(t)
} else {
for _, site := range results {
fmt.Println("\nSiteName:", site.Name)
Expand All @@ -117,3 +153,22 @@ func init() {
// is called directly, e.g.:
// searchCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

func generateHuhOpts(results []model.Site, searchParam string) []huh.Option[string] {
var opts []huh.Option[string]
for _, v := range results {
if searchParam == "site" {
opts = append(opts, huh.Option[string]{
Key: v.UserName,
Value: v.Password,
})
} else {
opts = append(opts, huh.Option[string]{
Key: v.Name,
Value: v.Password,
})
}
}

return opts
}
23 changes: 20 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,37 @@ go 1.19

require (
github.com/atotto/clipboard v0.1.4
github.com/cheynewallace/tabby v1.1.1
github.com/charmbracelet/huh v0.3.0
github.com/charmbracelet/lipgloss v0.10.0
github.com/fatih/color v1.14.1
github.com/spf13/cobra v1.6.1
gorm.io/driver/sqlite v1.4.4
gorm.io/gorm v1.24.5
)

require (
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/catppuccin/go v0.2.0 // indirect
github.com/charmbracelet/bubbles v0.17.2-0.20240108170749-ec883029c8e6 // indirect
github.com/charmbracelet/bubbletea v0.25.0 // indirect
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mattn/go-sqlite3 v1.14.15 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.15.2 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/sys v0.3.0 // indirect
golang.org/x/sync v0.4.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/term v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
)
Loading

0 comments on commit 5b5865d

Please sign in to comment.