got it working!
This commit is contained in:
parent
686728dc6d
commit
71848481d2
235
cmd/add.go
235
cmd/add.go
@ -16,49 +16,40 @@ limitations under the License.
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"io/ioutil"
|
|
||||||
|
|
||||||
//"encoding/json"
|
//"encoding/json"
|
||||||
|
//"io/ioutil"
|
||||||
|
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
input "github.com/charmbracelet/bubbles/textinput"
|
input "github.com/charmbracelet/bubbles/textinput"
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
te "github.com/muesli/termenv"
|
te "github.com/muesli/termenv"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"pgm/data"
|
"pgm/data"
|
||||||
"pgm/logger"
|
"pgm/logger"
|
||||||
|
|
||||||
//"io/ioutil"
|
//"github.com/spf13/viper"
|
||||||
//"log"
|
//"log"
|
||||||
"os"
|
//"pgm/data"
|
||||||
//"pgm/logger"
|
//"pgm/logger"
|
||||||
|
|
||||||
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
const focusedTextColor = "212"
|
const focusedTextColor = "205"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
color = te.ColorProfile().Color
|
||||||
focusedPrompt = te.String("> ").Foreground(color("205")).String()
|
focusedPrompt = te.String("> ").Foreground(color("205")).String()
|
||||||
blurredPrompt = "> "
|
blurredPrompt = "> "
|
||||||
color = te.ColorProfile().Color
|
|
||||||
focusedSubmitButton = "[ " + te.String("Submit").Foreground(color("205")).String() + " ]"
|
focusedSubmitButton = "[ " + te.String("Submit").Foreground(color("205")).String() + " ]"
|
||||||
blurredSubmitButton = "[ " + te.String("Submit").Foreground(color("240")).String() + " ]"
|
blurredSubmitButton = "[ " + te.String("Submit").Foreground(color("240")).String() + " ]"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
type dupeFileMsg string
|
|
||||||
|
|
||||||
type dbCfg struct {
|
|
||||||
index int
|
|
||||||
dbFileName input.Model
|
|
||||||
hostName input.Model
|
|
||||||
dbName input.Model
|
|
||||||
userName input.Model
|
|
||||||
secret input.Model
|
|
||||||
submitButton string
|
|
||||||
}
|
|
||||||
|
|
||||||
// addCmd represents the add command
|
// addCmd represents the add command
|
||||||
var addCmd = &cobra.Command{
|
var addCmd = &cobra.Command{
|
||||||
Use: "add",
|
Use: "add",
|
||||||
@ -70,17 +61,23 @@ Cobra is a CLI library for Go that empowers applications.
|
|||||||
This application is a tool to generate the needed files
|
This application is a tool to generate the needed files
|
||||||
to quickly create a Cobra application.`,
|
to quickly create a Cobra application.`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
result := make(chan string, 1)
|
// This is where we'll listen for the choice the user makes in the Bubble
|
||||||
|
// Tea program.
|
||||||
|
var cfg []string
|
||||||
|
result := make(chan []string, 1)
|
||||||
|
|
||||||
if err := tea.NewProgram(initialModel()).Start(); err != nil {
|
if err := tea.NewProgram(initialModel(result)).Start(); err != nil {
|
||||||
fmt.Printf("could not start program: %s\n", err)
|
fmt.Printf("could not start program: %s\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print out the final choice.
|
// Print out the final choice.
|
||||||
if r := <-result; r != "" {
|
if cfg = <-result; len(cfg) !=0 {
|
||||||
fmt.Printf("\n---\nYou chose %s!\n", r)
|
configWriter(cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,59 +96,78 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (m dbCfg) Init() tea.Cmd {
|
type hostCfgFile struct {
|
||||||
|
index int
|
||||||
|
fn input.Model
|
||||||
|
hostname input.Model
|
||||||
|
dbname input.Model
|
||||||
|
username input.Model
|
||||||
|
secret input.Model
|
||||||
|
submitButton string
|
||||||
|
Filename chan []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func initialModel(Filename chan []string ) hostCfgFile {
|
||||||
|
fn := input.NewModel()
|
||||||
|
fn.Placeholder = "filename (no spaces)"
|
||||||
|
fn.Focus()
|
||||||
|
fn.Prompt = focusedPrompt
|
||||||
|
fn.TextColor = focusedTextColor
|
||||||
|
fn.CharLimit = 32
|
||||||
|
|
||||||
|
hn := input.NewModel()
|
||||||
|
hn.Placeholder = "hostname"
|
||||||
|
hn.Prompt = blurredPrompt
|
||||||
|
hn.CharLimit = 64
|
||||||
|
|
||||||
|
dbn := input.NewModel()
|
||||||
|
dbn.Placeholder = "dbname"
|
||||||
|
dbn.Prompt = blurredPrompt
|
||||||
|
dbn.CharLimit = 64
|
||||||
|
|
||||||
|
un := input.NewModel()
|
||||||
|
un.Placeholder = "username"
|
||||||
|
un.Prompt = blurredPrompt
|
||||||
|
un.CharLimit = 30
|
||||||
|
|
||||||
|
|
||||||
|
secret := input.NewModel()
|
||||||
|
secret.Placeholder = "secret"
|
||||||
|
secret.Prompt = blurredPrompt
|
||||||
|
secret.CharLimit = 99
|
||||||
|
|
||||||
|
return hostCfgFile{0, fn, hn, dbn, un, secret,blurredSubmitButton, Filename}
|
||||||
|
|
||||||
|
}
|
||||||
|
func (m hostCfgFile) Init() tea.Cmd {
|
||||||
return tea.Batch(
|
return tea.Batch(
|
||||||
input.Blink(m.dbFileName),
|
input.Blink(m.fn),
|
||||||
input.Blink(m.hostName),
|
input.Blink(m.hostname),
|
||||||
input.Blink(m.dbName),
|
input.Blink(m.dbname),
|
||||||
input.Blink(m.userName),
|
input.Blink(m.username),
|
||||||
input.Blink(m.secret),
|
input.Blink(m.secret),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m hostCfgFile) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
|
|
||||||
func (m dbCfg) View() string {
|
|
||||||
s := "\n"
|
|
||||||
|
|
||||||
inputs := []string{
|
|
||||||
input.View(m.dbFileName),
|
|
||||||
input.View(m.hostName),
|
|
||||||
input.View(m.dbName),
|
|
||||||
input.View(m.userName),
|
|
||||||
input.View(m.secret),
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < len(inputs); i++ {
|
|
||||||
s += inputs[i]
|
|
||||||
if i < len(inputs)-1 {
|
|
||||||
s += "\n"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
s += "\n\n" + m.submitButton + "\n"
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func (m dbCfg) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
||||||
var cmd tea.Cmd
|
var cmd tea.Cmd
|
||||||
|
|
||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
case tea.KeyMsg:
|
case tea.KeyMsg:
|
||||||
switch msg.String() {
|
switch msg.String() {
|
||||||
|
|
||||||
case "ctrl+c":
|
case "ctrl+c", "esc":
|
||||||
|
close(m.Filename)
|
||||||
return m, tea.Quit
|
return m, tea.Quit
|
||||||
|
|
||||||
// Cycle between inputs
|
// Cycle between inputs
|
||||||
case "tab", "shift+tab", "enter", "up", "down":
|
case "tab", "shift+tab", "enter", "up", "down":
|
||||||
|
|
||||||
inputs := []input.Model{
|
inputs := []input.Model{
|
||||||
m.dbFileName,
|
m.fn,
|
||||||
m.hostName,
|
m.hostname,
|
||||||
m.dbName,
|
m.dbname,
|
||||||
m.userName,
|
m.username,
|
||||||
m.secret,
|
m.secret,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,6 +176,11 @@ func (m dbCfg) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
// Did the user press enter while the submit button was focused?
|
// Did the user press enter while the submit button was focused?
|
||||||
// If so, exit.
|
// If so, exit.
|
||||||
if s == "enter" && m.index == len(inputs) {
|
if s == "enter" && m.index == len(inputs) {
|
||||||
|
var cfg []string
|
||||||
|
for _, i := range inputs{
|
||||||
|
cfg = append(cfg, i.Value())
|
||||||
|
}
|
||||||
|
m.Filename <- cfg
|
||||||
return m, tea.Quit
|
return m, tea.Quit
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,10 +211,10 @@ func (m dbCfg) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
inputs[i].TextColor = ""
|
inputs[i].TextColor = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
m.dbFileName = inputs[0]
|
m.fn = inputs[0]
|
||||||
m.hostName = inputs[1]
|
m.hostname = inputs[1]
|
||||||
m.dbName = inputs[2]
|
m.dbname = inputs[2]
|
||||||
m.userName = inputs[3]
|
m.username = inputs[3]
|
||||||
m.secret = inputs[4]
|
m.secret = inputs[4]
|
||||||
|
|
||||||
if m.index == len(inputs) {
|
if m.index == len(inputs) {
|
||||||
@ -211,68 +232,77 @@ func (m dbCfg) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
return m, cmd
|
return m, cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pass messages and models through to text input components. Only text inputs
|
||||||
func updateInputs(msg tea.Msg, m dbCfg) (dbCfg, tea.Cmd) {
|
// with Focus() set will respond, so it's safe to simply update all of them
|
||||||
|
// here without any further logic.
|
||||||
|
func updateInputs(msg tea.Msg, m hostCfgFile) (hostCfgFile, tea.Cmd) {
|
||||||
var (
|
var (
|
||||||
cmd tea.Cmd
|
cmd tea.Cmd
|
||||||
cmds []tea.Cmd
|
cmds []tea.Cmd
|
||||||
)
|
)
|
||||||
|
|
||||||
m.dbFileName, cmd = input.Update(msg, m.dbFileName)
|
m.fn, cmd = input.Update(msg, m.fn)
|
||||||
cmds = append(cmds, cmd)
|
cmds = append(cmds, cmd)
|
||||||
|
|
||||||
m.hostName, cmd = input.Update(msg, m.hostName)
|
m.hostname, cmd = input.Update(msg, m.hostname)
|
||||||
cmds = append(cmds, cmd)
|
cmds = append(cmds, cmd)
|
||||||
|
|
||||||
m.dbName, cmd = input.Update(msg, m.dbName)
|
m.dbname, cmd = input.Update(msg, m.dbname)
|
||||||
cmds = append(cmds, cmd)
|
cmds = append(cmds, cmd)
|
||||||
|
|
||||||
m.userName, cmd = input.Update(msg, m.userName)
|
m.username, cmd = input.Update(msg, m.username)
|
||||||
cmds = append(cmds, cmd)
|
cmds = append(cmds, cmd)
|
||||||
|
|
||||||
m.secret, cmd = input.Update(msg, m.secret)
|
m.secret, cmd = input.Update(msg, m.secret)
|
||||||
|
cmds = append(cmds, cmd)
|
||||||
|
|
||||||
|
|
||||||
return m, tea.Batch(cmds...)
|
return m, tea.Batch(cmds...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func initialModel() dbCfg {
|
func (m hostCfgFile) View() string {
|
||||||
dbFileName := input.NewModel()
|
s := "\n"
|
||||||
dbFileName.Placeholder = "database file name (no spaces)"
|
|
||||||
dbFileName.Focus()
|
|
||||||
|
|
||||||
|
inputs := []string{
|
||||||
|
input.View(m.fn),
|
||||||
|
input.View(m.hostname),
|
||||||
|
input.View(m.dbname),
|
||||||
|
input.View(m.username),
|
||||||
|
input.View(m.secret),
|
||||||
|
}
|
||||||
|
|
||||||
hostName := input.NewModel()
|
for i := 0; i < len(inputs); i++ {
|
||||||
hostName.Placeholder = "hostname"
|
s += inputs[i]
|
||||||
|
if i < len(inputs)-1 {
|
||||||
dbName := input.NewModel()
|
s += "\n"
|
||||||
dbName.Placeholder = "database name on host"
|
}
|
||||||
|
}
|
||||||
userName := input.NewModel()
|
|
||||||
userName.Placeholder = "username"
|
|
||||||
|
|
||||||
secret := input.NewModel()
|
|
||||||
secret.Placeholder = "secret"
|
|
||||||
|
|
||||||
return dbCfg{0, dbFileName, hostName, dbName, userName, secret, blurredSubmitButton}
|
|
||||||
|
|
||||||
|
s += "\n\n" + m.submitButton + "\n"
|
||||||
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
//// Writes configs to .pgm/hosts directory
|
||||||
|
func configWriter(s []string){
|
||||||
// Writes configs to .pgm/hosts directory
|
|
||||||
func configWriter(t []input.Model){
|
|
||||||
h := viper.GetString("hostsDir")
|
h := viper.GetString("hostsDir")
|
||||||
var c data.HostDetails
|
var c data.HostDetails
|
||||||
var fp string
|
var fp string
|
||||||
var cfg []byte
|
var cfg []byte
|
||||||
c = data.HostDetails{t[4].Value(), t[1].Value(), t[2].Value(), t[3].Value()}
|
var isEmpty bool
|
||||||
cfgFileName := t[0].Value()
|
isEmpty = checkEmpty(s)
|
||||||
|
if isEmpty == true {
|
||||||
|
logger.Logger("[LOG] one or more fields was empty when writing file: " + s[0] )
|
||||||
|
fmt.Println("one or more fields was empty when submitted, please try again")
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
c = data.HostDetails{s[4], s[1], s[2], s[3]}
|
||||||
|
cfgFileName := s[0]
|
||||||
if _, err := os.Stat(h + "/" + cfgFileName); os.IsNotExist(err) {
|
if _, err := os.Stat(h + "/" + cfgFileName); os.IsNotExist(err) {
|
||||||
fp = h + "/" + cfgFileName
|
fp = h + "/" + cfgFileName
|
||||||
} else {
|
} else {
|
||||||
e := "dupe"
|
logger.Logger("[LOG] duplicate file name found: " + cfgFileName)
|
||||||
return dupeFileMsg(e)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
_ = cfg
|
_ = cfg
|
||||||
f, err := os.Create(fp)
|
f, err := os.Create(fp)
|
||||||
@ -294,3 +324,16 @@ func configWriter(t []input.Model){
|
|||||||
fmt.Println("failed to log")
|
fmt.Println("failed to log")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// looks through user inputs for zero length strings
|
||||||
|
// if any one is empty we do not accept the input and exit.
|
||||||
|
func checkEmpty(s []string ) bool {
|
||||||
|
for _, ip := range s {
|
||||||
|
if len(ip) < 1 {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
234
cmd/load.go
234
cmd/load.go
@ -16,13 +16,41 @@ limitations under the License.
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
"github.com/muesli/termenv"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"pgm/data"
|
"io/ioutil"
|
||||||
"pgm/loader"
|
|
||||||
"pgm/logger"
|
|
||||||
"os"
|
"os"
|
||||||
|
"pgm/data"
|
||||||
|
"pgm/logger"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type host struct {
|
||||||
|
tic int
|
||||||
|
t string
|
||||||
|
choices []string
|
||||||
|
choice int
|
||||||
|
chosen bool
|
||||||
|
Quitting bool
|
||||||
|
Frames int
|
||||||
|
File chan string
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
term = termenv.ColorProfile()
|
||||||
|
subtle = makeFgStyle("241")
|
||||||
|
dot = colorFg(" • ", "236")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
// billyCmd represents the billy command
|
// billyCmd represents the billy command
|
||||||
var loadCmd = &cobra.Command{
|
var loadCmd = &cobra.Command{
|
||||||
Use: "load",
|
Use: "load",
|
||||||
@ -34,20 +62,30 @@ Cobra is a CLI library for Go that empowers applications.
|
|||||||
This application is a tool to generate the needed files
|
This application is a tool to generate the needed files
|
||||||
to quickly create a Cobra application.`,
|
to quickly create a Cobra application.`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
var dbcfg string
|
|
||||||
var title string
|
var title string
|
||||||
var pgHosts []string
|
var pgHosts []string
|
||||||
|
|
||||||
|
var fn string
|
||||||
|
result := make(chan string, 1)
|
||||||
|
|
||||||
pgHosts = data.ReadHosts()
|
pgHosts = data.ReadHosts()
|
||||||
title = "choose db to load, \"l\" or enter will load your choice"
|
title = "choose db to load, \"l\" or enter will load your choice"
|
||||||
err := loader.Loader(pgHosts, title, dbcfg)
|
var hostChoice host
|
||||||
if err != nil {
|
hostChoice = host{60, title, pgHosts, 0, false, false, 0, result }
|
||||||
logger.Logger("[ERROR] loading error for testing, ignore.")
|
if err := tea.NewProgram(hostChoice).Start(); err != nil {
|
||||||
|
logger.Logger("[ERROR] tea error occured")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print out the final choice.
|
||||||
|
if fn = <-result; len(fn) !=0 {
|
||||||
|
logger.Logger("[LOG] loading file: " + fn)
|
||||||
}
|
}
|
||||||
os.Exit(0)
|
|
||||||
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(loadCmd)
|
rootCmd.AddCommand(loadCmd)
|
||||||
|
|
||||||
@ -61,3 +99,183 @@ func init() {
|
|||||||
// is called directly, e.g.:
|
// is called directly, e.g.:
|
||||||
// billyCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
// billyCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
func (m host) Init() tea.Cmd {
|
||||||
|
return tick()
|
||||||
|
}
|
||||||
|
|
||||||
|
func choicesUpdate(msg tea.Msg, m host) (tea.Model, tea.Cmd) {
|
||||||
|
switch msg := msg.(type) {
|
||||||
|
|
||||||
|
case tea.KeyMsg:
|
||||||
|
switch msg.String() {
|
||||||
|
case "q", "esc":
|
||||||
|
m.Quitting = true
|
||||||
|
return m, tea.Quit
|
||||||
|
case "j", "down":
|
||||||
|
m.choice += 1
|
||||||
|
if m.choice > len(m.choices) {
|
||||||
|
m.choice = len(m.choices)
|
||||||
|
}
|
||||||
|
case "k", "up":
|
||||||
|
m.choice -= 1
|
||||||
|
if m.choice < 0 {
|
||||||
|
m.choice = 0
|
||||||
|
}
|
||||||
|
case "l", "enter":
|
||||||
|
m.File <- m.choices[m.choice]
|
||||||
|
return m, tea.Quit
|
||||||
|
}
|
||||||
|
|
||||||
|
case tickMsg:
|
||||||
|
if m.tic == 0 {
|
||||||
|
m.Quitting = true
|
||||||
|
return m, tea.Quit
|
||||||
|
}
|
||||||
|
m.tic -= 1
|
||||||
|
return m, tick()
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update loop for the second view after a choice has been made
|
||||||
|
func (m host) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
|
if msg, ok := msg.(tea.KeyMsg); ok {
|
||||||
|
k := msg.String()
|
||||||
|
if k == "q" || k == "esc" || k == "ctrl+c" {
|
||||||
|
m.Quitting = true
|
||||||
|
return m, tea.Quit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !m.chosen {
|
||||||
|
return choicesUpdate(msg, m)
|
||||||
|
}
|
||||||
|
return m, tea.Quit
|
||||||
|
}
|
||||||
|
|
||||||
|
// Views return a string based on data in the host. That string which will be
|
||||||
|
// rendered to the terminal.
|
||||||
|
func (m host) View() string {
|
||||||
|
var s string
|
||||||
|
if m.Quitting {
|
||||||
|
logger.Logger("[LOG] leaving pgm")
|
||||||
|
return "leaving pgm..."
|
||||||
|
|
||||||
|
}
|
||||||
|
if !m.chosen {
|
||||||
|
s = choicesView(m)
|
||||||
|
} else {
|
||||||
|
m.Quitting = true
|
||||||
|
logger.Logger("[LOG] leaving pgm")
|
||||||
|
return "leaving pgm..."
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Creates the menu for choosing a file from your configs.
|
||||||
|
// Exits after 60 seconds.
|
||||||
|
// Gets Title from outside in the load method if this... should ever be needed elsewhere.
|
||||||
|
func choicesView(m host) string {
|
||||||
|
var menu string
|
||||||
|
c := m.choice
|
||||||
|
|
||||||
|
tpl := m.t + "\n\n"
|
||||||
|
tpl += "%s\n\n"
|
||||||
|
tpl += "menu exits in %s seconds\n\n"
|
||||||
|
tpl += subtle("j/k, up/down: select") + dot + subtle("enter: choose") + dot + subtle("q, esc: quit")
|
||||||
|
for i, s := range m.choices {
|
||||||
|
menu = menu + "::" + checkbox(s, c == i)
|
||||||
|
}
|
||||||
|
mn := strings.Replace(menu, "::", "\n", -1)
|
||||||
|
choices := fmt.Sprintf(
|
||||||
|
mn,
|
||||||
|
)
|
||||||
|
|
||||||
|
return fmt.Sprintf(tpl, choices, colorFg(strconv.Itoa(m.tic), "79"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// the rest of these functions are to do with looks and aesthetics
|
||||||
|
|
||||||
|
// returns a neat checkbox
|
||||||
|
func checkbox(label string, checked bool) string {
|
||||||
|
if checked {
|
||||||
|
return colorFg("[x] "+label, "212")
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("[ ] %s", label)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fun colors
|
||||||
|
func colorFg(val, color string) string {
|
||||||
|
return termenv.String(val).Foreground(term.Color(color)).String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a function that will colorize the foreground of a given string.
|
||||||
|
func makeFgStyle(color string) func(string) string {
|
||||||
|
return termenv.Style{}.Foreground(term.Color(color)).Styled
|
||||||
|
}
|
||||||
|
|
||||||
|
// Frame event
|
||||||
|
func Frame() tea.Cmd {
|
||||||
|
return tea.Tick(time.Second/60, func(time.Time) tea.Msg {
|
||||||
|
return FrameMsg{}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type tickMsg struct{}
|
||||||
|
type FrameMsg struct{}
|
||||||
|
|
||||||
|
// exits if no choice is selected in a minute
|
||||||
|
func tick() tea.Cmd {
|
||||||
|
return tea.Tick(time.Second, func(time.Time) tea.Msg {
|
||||||
|
return tickMsg{}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// loads a yaml file into the correct format for consumption.
|
||||||
|
// errs the pipeline step if there is not a configuration in this repository.
|
||||||
|
func loadhost(c string, p *data.HostDetails) *data.HostDetails {
|
||||||
|
cfg, err := ioutil.ReadFile(c)
|
||||||
|
if err != nil {
|
||||||
|
logger.Logger("[ERROR] unable to find this filepath: " + err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
json.Unmarshal(cfg, &p)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Simple struct for a view in the UI.
|
||||||
|
type Scr struct {
|
||||||
|
Query string `json:"query"`
|
||||||
|
Columns int `json:"columns"`
|
||||||
|
UIType string `json:"uiType"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Quitting bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Scr) Init() tea.Cmd {
|
||||||
|
return tick()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Scr) View() string {
|
||||||
|
|
||||||
|
return m.Query
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Scr) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
|
if msg, ok := msg.(tea.KeyMsg); ok {
|
||||||
|
k := msg.String()
|
||||||
|
if k == "q" || k == "esc" || k == "ctrl+c" {
|
||||||
|
m.Quitting = true
|
||||||
|
return m, tea.Quit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|||||||
14
cmd/root.go
14
cmd/root.go
@ -66,7 +66,7 @@ func init() {
|
|||||||
// Cobra supports persistent flags, which, if defined here,
|
// Cobra supports persistent flags, which, if defined here,
|
||||||
// will be global for your application.
|
// will be global for your application.
|
||||||
|
|
||||||
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.pgm.yaml)")
|
rootCmd.PersistentFlags().StringVar(&cfgFile, "hostCfgFile", "", "hostCfgFile file (default is $HOME/.pgm.yaml)")
|
||||||
|
|
||||||
// Cobra also supports local flags, which will only run
|
// Cobra also supports local flags, which will only run
|
||||||
// when this action is called directly.
|
// when this action is called directly.
|
||||||
@ -76,7 +76,7 @@ func init() {
|
|||||||
viper.SetDefault("homeDir", homeDirectory)
|
viper.SetDefault("homeDir", homeDirectory)
|
||||||
err = viper.ReadInConfig()
|
err = viper.ReadInConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Logger("[ERROR] viper config issue" + err.Error())
|
logger.Logger("[ERROR] viper hostCfgFile issue" + err.Error())
|
||||||
}
|
}
|
||||||
pgmDir := viper.GetString("pgm_dir")
|
pgmDir := viper.GetString("pgm_dir")
|
||||||
logsDir := viper.GetString("logs_dir")
|
logsDir := viper.GetString("logs_dir")
|
||||||
@ -89,10 +89,10 @@ func init() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// initConfig reads in config file and ENV variables if set.
|
// initConfig reads in hostCfgFile file and ENV variables if set.
|
||||||
func InitConfig() {
|
func InitConfig() {
|
||||||
if cfgFile != "" {
|
if cfgFile != "" {
|
||||||
// Use config file from the flag.
|
// Use hostCfgFile file from the flag.
|
||||||
viper.SetConfigFile(cfgFile)
|
viper.SetConfigFile(cfgFile)
|
||||||
} else {
|
} else {
|
||||||
// Find home directory.
|
// Find home directory.
|
||||||
@ -101,7 +101,7 @@ func InitConfig() {
|
|||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
// Search config in home directory with name ".pgm" (without extension).
|
// Search hostCfgFile in home directory with name ".pgm" (without extension).
|
||||||
viper.AddConfigPath(home)
|
viper.AddConfigPath(home)
|
||||||
viper.SetConfigName(".pgm")
|
viper.SetConfigName(".pgm")
|
||||||
|
|
||||||
@ -120,8 +120,8 @@ func InitConfig() {
|
|||||||
|
|
||||||
viper.AutomaticEnv() // read in environment variables that match
|
viper.AutomaticEnv() // read in environment variables that match
|
||||||
|
|
||||||
// If a config file is found, read it in.
|
// If a hostCfgFile file is found, read it in.
|
||||||
if err := viper.ReadInConfig(); err == nil {
|
if err := viper.ReadInConfig(); err == nil {
|
||||||
logger.Logger("[LOG] Using config file:"+ viper.ConfigFileUsed())
|
logger.Logger("[LOG] Using hostCfgFile file:"+ viper.ConfigFileUsed())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
264
loader/loader.go
264
loader/loader.go
@ -1,264 +0,0 @@
|
|||||||
package loader
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
|
||||||
"pgm/ui"
|
|
||||||
|
|
||||||
"github.com/muesli/termenv"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"pgm/config"
|
|
||||||
"pgm/data"
|
|
||||||
"pgm/logger"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type host struct {
|
|
||||||
tic int
|
|
||||||
t string
|
|
||||||
choices []string
|
|
||||||
choice int
|
|
||||||
chosen bool
|
|
||||||
Quitting bool
|
|
||||||
Frames int
|
|
||||||
File string
|
|
||||||
isTest bool
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
term = termenv.ColorProfile()
|
|
||||||
subtle = makeFgStyle("241")
|
|
||||||
dot = colorFg(" • ", "236")
|
|
||||||
)
|
|
||||||
|
|
||||||
func Loader(t []string, s string, b string) error {
|
|
||||||
var hostChoice host
|
|
||||||
var isT bool
|
|
||||||
isT = setTest(s)
|
|
||||||
hostChoice = host{60, s, t, 0, false, false, 0, b, isT}
|
|
||||||
if err := tea.NewProgram(hostChoice).Start(); err != nil {
|
|
||||||
if isT == true {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
logger.Logger("[ERROR] tea error occured")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// method to enable go tests if set to true
|
|
||||||
// loader function will test that it can load the ui to chose a database
|
|
||||||
// and quits the ui
|
|
||||||
func setTest(s string) bool {
|
|
||||||
if s == "test" {
|
|
||||||
logger.Logger("[LOG] is test: true")
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
logger.Logger("[LOG] is test: false")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m host) Init() tea.Cmd {
|
|
||||||
return tick()
|
|
||||||
}
|
|
||||||
|
|
||||||
func choicesUpdate(msg tea.Msg, m host) (tea.Model, tea.Cmd) {
|
|
||||||
switch msg := msg.(type) {
|
|
||||||
|
|
||||||
case tea.KeyMsg:
|
|
||||||
switch msg.String() {
|
|
||||||
case "q", "esc":
|
|
||||||
m.Quitting = true
|
|
||||||
return m, tea.Quit
|
|
||||||
case "j", "down":
|
|
||||||
m.choice += 1
|
|
||||||
if m.choice > len(m.choices) {
|
|
||||||
m.choice = len(m.choices)
|
|
||||||
}
|
|
||||||
case "k", "up":
|
|
||||||
m.choice -= 1
|
|
||||||
if m.choice < 0 {
|
|
||||||
m.choice = 0
|
|
||||||
}
|
|
||||||
case "l", "enter":
|
|
||||||
m.File = m.choices[m.choice]
|
|
||||||
g := LoadUi(m)
|
|
||||||
ui.GraphicUi(g)
|
|
||||||
return m, tea.Quit
|
|
||||||
}
|
|
||||||
|
|
||||||
case tickMsg:
|
|
||||||
if m.isTest == true {
|
|
||||||
m.File = "test"
|
|
||||||
m.Quitting = true
|
|
||||||
return m, tea.Quit
|
|
||||||
}
|
|
||||||
if m.tic == 0 {
|
|
||||||
m.Quitting = true
|
|
||||||
return m, tea.Quit
|
|
||||||
}
|
|
||||||
m.tic -= 1
|
|
||||||
return m, tick()
|
|
||||||
}
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update loop for the second view after a choice has been made
|
|
||||||
func (m host) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
||||||
if msg, ok := msg.(tea.KeyMsg); ok {
|
|
||||||
k := msg.String()
|
|
||||||
if k == "q" || k == "esc" || k == "ctrl+c" {
|
|
||||||
m.Quitting = true
|
|
||||||
return m, tea.Quit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !m.chosen {
|
|
||||||
return choicesUpdate(msg, m)
|
|
||||||
}
|
|
||||||
return m, tea.Quit
|
|
||||||
}
|
|
||||||
|
|
||||||
// Views return a string based on data in the host. That string which will be
|
|
||||||
// rendered to the terminal.
|
|
||||||
func (m host) View() string {
|
|
||||||
var s string
|
|
||||||
if m.Quitting {
|
|
||||||
logger.Logger("[LOG] leaving pgm")
|
|
||||||
return "leaving pgm..."
|
|
||||||
|
|
||||||
}
|
|
||||||
if !m.chosen {
|
|
||||||
s = choicesView(m)
|
|
||||||
} else {
|
|
||||||
m.Quitting = true
|
|
||||||
logger.Logger("[LOG] leaving pgm")
|
|
||||||
return "leaving pgm..."
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// Passes our config to the screen file.
|
|
||||||
// screen is tasked with rendering more in depth views.
|
|
||||||
func LoadUi(m host) data.HostDetails {
|
|
||||||
var g data.HostDetails
|
|
||||||
var h string
|
|
||||||
h, err := os.UserHomeDir()
|
|
||||||
if err != nil {
|
|
||||||
logger.Logger("[ERROR] uable to set home dir: " + err.Error())
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
config.ReadConfig()
|
|
||||||
l := viper.GetString("hosts_dir")
|
|
||||||
filepath := h + "/" + l + "/" + m.File
|
|
||||||
loadhost(filepath, &g)
|
|
||||||
return g
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates the menu for choosing a file from your configs.
|
|
||||||
// Exits after 60 seconds.
|
|
||||||
// Gets Title from outside in the load method if this... should ever be needed elsewhere.
|
|
||||||
func choicesView(m host) string {
|
|
||||||
var menu string
|
|
||||||
c := m.choice
|
|
||||||
|
|
||||||
tpl := m.t + "\n\n"
|
|
||||||
tpl += "%s\n\n"
|
|
||||||
tpl += "menu exits in %s seconds\n\n"
|
|
||||||
tpl += subtle("j/k, up/down: select") + dot + subtle("enter: choose") + dot + subtle("q, esc: quit")
|
|
||||||
for i, s := range m.choices {
|
|
||||||
menu = menu + "::" + checkbox(s, c == i)
|
|
||||||
}
|
|
||||||
mn := strings.Replace(menu, "::", "\n", -1)
|
|
||||||
choices := fmt.Sprintf(
|
|
||||||
mn,
|
|
||||||
)
|
|
||||||
|
|
||||||
return fmt.Sprintf(tpl, choices, colorFg(strconv.Itoa(m.tic), "79"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// the rest of these functions are to do with looks and aesthetics
|
|
||||||
|
|
||||||
// returns a neat checkbox
|
|
||||||
func checkbox(label string, checked bool) string {
|
|
||||||
if checked {
|
|
||||||
return colorFg("[x] "+label, "212")
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("[ ] %s", label)
|
|
||||||
}
|
|
||||||
|
|
||||||
// fun colors
|
|
||||||
func colorFg(val, color string) string {
|
|
||||||
return termenv.String(val).Foreground(term.Color(color)).String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return a function that will colorize the foreground of a given string.
|
|
||||||
func makeFgStyle(color string) func(string) string {
|
|
||||||
return termenv.Style{}.Foreground(term.Color(color)).Styled
|
|
||||||
}
|
|
||||||
|
|
||||||
// Frame event
|
|
||||||
func Frame() tea.Cmd {
|
|
||||||
return tea.Tick(time.Second/60, func(time.Time) tea.Msg {
|
|
||||||
return FrameMsg{}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
type tickMsg struct{}
|
|
||||||
type FrameMsg struct{}
|
|
||||||
|
|
||||||
// exits if no choice is selected in a minute
|
|
||||||
func tick() tea.Cmd {
|
|
||||||
return tea.Tick(time.Second, func(time.Time) tea.Msg {
|
|
||||||
return tickMsg{}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// loads a yaml file into the correct format for consumption.
|
|
||||||
// errs the pipeline step if there is not a configuration in this repository.
|
|
||||||
func loadhost(c string, p *data.HostDetails) *data.HostDetails {
|
|
||||||
cfg, err := ioutil.ReadFile(c)
|
|
||||||
if err != nil {
|
|
||||||
logger.Logger("[ERROR] unable to find this filepath: " + err.Error())
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
json.Unmarshal(cfg, &p)
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Simple struct for a view in the UI.
|
|
||||||
type Scr struct {
|
|
||||||
Query string `json:"query"`
|
|
||||||
Columns int `json:"columns"`
|
|
||||||
UIType string `json:"uiType"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
Quitting bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Scr) Init() tea.Cmd {
|
|
||||||
return tick()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Scr) View() string {
|
|
||||||
|
|
||||||
return m.Query
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Scr) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
||||||
if msg, ok := msg.(tea.KeyMsg); ok {
|
|
||||||
k := msg.String()
|
|
||||||
if k == "q" || k == "esc" || k == "ctrl+c" {
|
|
||||||
m.Quitting = true
|
|
||||||
return m, tea.Quit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
120
ui/ui.go
120
ui/ui.go
@ -1,120 +0,0 @@
|
|||||||
package ui
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"pgm/data"
|
|
||||||
|
|
||||||
"github.com/charmbracelet/bubbles/viewport"
|
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
|
||||||
"github.com/charmbracelet/glamour"
|
|
||||||
"github.com/muesli/termenv"
|
|
||||||
)
|
|
||||||
|
|
||||||
const content = `
|
|
||||||
Today’s Menu
|
|
||||||
|
|
||||||
| Name | Price | Notes |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| Tsukemono | $2 | Just an appetizer |
|
|
||||||
| Tomato Soup | $4 | Made with San Marzano tomatoes |
|
|
||||||
| Okonomiyaki | $4 | Takes a few minutes to make |
|
|
||||||
| Curry | $3 | We can add squash if you’d like |
|
|
||||||
|
|
||||||
Seasonal Dishes
|
|
||||||
|
|
||||||
| Name | Price | Notes |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| Steamed bitter melon | $2 | Not so bitter |
|
|
||||||
| Takoyaki | $3 | Fun to eat |
|
|
||||||
| Winter squash | $3 | Today it's pumpkin |
|
|
||||||
|
|
||||||
Desserts
|
|
||||||
|
|
||||||
| Name | Price | Notes |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| Dorayaki | $4 | Looks good on rabbits |
|
|
||||||
| Banana Split | $5 | A classic |
|
|
||||||
| Cream Puff | $3 | Pretty creamy! |
|
|
||||||
|
|
||||||
All our dishes are made in-house by Karen, our chef. Most of our ingredients
|
|
||||||
are from are our garden, or the fish market down the street.
|
|
||||||
|
|
||||||
Some famous people that have eaten here lately:
|
|
||||||
|
|
||||||
* [x] René Redzepi
|
|
||||||
* [x] David Chang
|
|
||||||
* [ ] Jiro Ono (maybe some day)
|
|
||||||
|
|
||||||
Bon appétit!
|
|
||||||
`
|
|
||||||
|
|
||||||
var term = termenv.ColorProfile()
|
|
||||||
|
|
||||||
type example struct {
|
|
||||||
viewport viewport.Model
|
|
||||||
}
|
|
||||||
|
|
||||||
func newExample() (*example, error) {
|
|
||||||
vp := viewport.Model{Width: 78, Height: 20}
|
|
||||||
|
|
||||||
renderer, err := glamour.NewTermRenderer(glamour.WithStylePath("notty"))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
str, err := renderer.Render(content)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
vp.SetContent(str)
|
|
||||||
|
|
||||||
return &example{
|
|
||||||
viewport: vp,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e example) Init() tea.Cmd {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e example) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
||||||
switch msg := msg.(type) {
|
|
||||||
case tea.KeyMsg:
|
|
||||||
switch msg.String() {
|
|
||||||
case "q", "ctrl+c":
|
|
||||||
return e, tea.Quit
|
|
||||||
case "a":
|
|
||||||
default:
|
|
||||||
vp, _ := viewport.Update(msg, e.viewport)
|
|
||||||
e.viewport = vp
|
|
||||||
}
|
|
||||||
case tea.WindowSizeMsg:
|
|
||||||
e.viewport.Width = msg.Width
|
|
||||||
return e, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return e, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e example) View() string {
|
|
||||||
return viewport.View(e.viewport) + e.helpView()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e example) helpView() string {
|
|
||||||
return termenv.String("\n ↑/↓: Navigate • q: Quit\n").Foreground(term.Color("241")).String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func GraphicUi(g data.HostDetails) {
|
|
||||||
model, err := newExample()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Could not intialize Bubble Tea model:", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := tea.NewProgram(model).Start(); err != nil {
|
|
||||||
fmt.Println("Bummer, there's been an error:", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue
Block a user