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
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
|
||||
//"encoding/json"
|
||||
//"io/ioutil"
|
||||
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
input "github.com/charmbracelet/bubbles/textinput"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
te "github.com/muesli/termenv"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"pgm/data"
|
||||
"pgm/logger"
|
||||
|
||||
//"io/ioutil"
|
||||
//"github.com/spf13/viper"
|
||||
//"log"
|
||||
"os"
|
||||
//"pgm/data"
|
||||
//"pgm/logger"
|
||||
|
||||
"os"
|
||||
)
|
||||
|
||||
const focusedTextColor = "212"
|
||||
const focusedTextColor = "205"
|
||||
|
||||
var (
|
||||
color = te.ColorProfile().Color
|
||||
focusedPrompt = te.String("> ").Foreground(color("205")).String()
|
||||
blurredPrompt = "> "
|
||||
color = te.ColorProfile().Color
|
||||
focusedSubmitButton = "[ " + te.String("Submit").Foreground(color("205")).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
|
||||
var addCmd = &cobra.Command{
|
||||
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
|
||||
to quickly create a Cobra application.`,
|
||||
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)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Print out the final choice.
|
||||
if r := <-result; r != "" {
|
||||
fmt.Printf("\n---\nYou chose %s!\n", r)
|
||||
if cfg = <-result; len(cfg) !=0 {
|
||||
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(
|
||||
input.Blink(m.dbFileName),
|
||||
input.Blink(m.hostName),
|
||||
input.Blink(m.dbName),
|
||||
input.Blink(m.userName),
|
||||
input.Blink(m.fn),
|
||||
input.Blink(m.hostname),
|
||||
input.Blink(m.dbname),
|
||||
input.Blink(m.username),
|
||||
input.Blink(m.secret),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
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) {
|
||||
func (m hostCfgFile) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
var cmd tea.Cmd
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch msg.String() {
|
||||
|
||||
case "ctrl+c":
|
||||
case "ctrl+c", "esc":
|
||||
close(m.Filename)
|
||||
return m, tea.Quit
|
||||
|
||||
// Cycle between inputs
|
||||
case "tab", "shift+tab", "enter", "up", "down":
|
||||
|
||||
inputs := []input.Model{
|
||||
m.dbFileName,
|
||||
m.hostName,
|
||||
m.dbName,
|
||||
m.userName,
|
||||
m.fn,
|
||||
m.hostname,
|
||||
m.dbname,
|
||||
m.username,
|
||||
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?
|
||||
// If so, exit.
|
||||
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
|
||||
}
|
||||
|
||||
@ -190,10 +211,10 @@ func (m dbCfg) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
inputs[i].TextColor = ""
|
||||
}
|
||||
|
||||
m.dbFileName = inputs[0]
|
||||
m.hostName = inputs[1]
|
||||
m.dbName = inputs[2]
|
||||
m.userName = inputs[3]
|
||||
m.fn = inputs[0]
|
||||
m.hostname = inputs[1]
|
||||
m.dbname = inputs[2]
|
||||
m.username = inputs[3]
|
||||
m.secret = inputs[4]
|
||||
|
||||
if m.index == len(inputs) {
|
||||
@ -211,68 +232,77 @@ func (m dbCfg) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
|
||||
func updateInputs(msg tea.Msg, m dbCfg) (dbCfg, tea.Cmd) {
|
||||
// Pass messages and models through to text input components. Only text inputs
|
||||
// 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 (
|
||||
cmd 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)
|
||||
|
||||
m.hostName, cmd = input.Update(msg, m.hostName)
|
||||
m.hostname, cmd = input.Update(msg, m.hostname)
|
||||
cmds = append(cmds, cmd)
|
||||
|
||||
m.dbName, cmd = input.Update(msg, m.dbName)
|
||||
m.dbname, cmd = input.Update(msg, m.dbname)
|
||||
cmds = append(cmds, cmd)
|
||||
|
||||
m.userName, cmd = input.Update(msg, m.userName)
|
||||
m.username, cmd = input.Update(msg, m.username)
|
||||
cmds = append(cmds, cmd)
|
||||
|
||||
m.secret, cmd = input.Update(msg, m.secret)
|
||||
cmds = append(cmds, cmd)
|
||||
|
||||
|
||||
return m, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
func initialModel() dbCfg {
|
||||
dbFileName := input.NewModel()
|
||||
dbFileName.Placeholder = "database file name (no spaces)"
|
||||
dbFileName.Focus()
|
||||
func (m hostCfgFile) View() string {
|
||||
s := "\n"
|
||||
|
||||
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()
|
||||
hostName.Placeholder = "hostname"
|
||||
|
||||
dbName := input.NewModel()
|
||||
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}
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// Writes configs to .pgm/hosts directory
|
||||
func configWriter(t []input.Model){
|
||||
//
|
||||
//// Writes configs to .pgm/hosts directory
|
||||
func configWriter(s []string){
|
||||
h := viper.GetString("hostsDir")
|
||||
var c data.HostDetails
|
||||
var fp string
|
||||
var cfg []byte
|
||||
c = data.HostDetails{t[4].Value(), t[1].Value(), t[2].Value(), t[3].Value()}
|
||||
cfgFileName := t[0].Value()
|
||||
var isEmpty bool
|
||||
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) {
|
||||
fp = h + "/" + cfgFileName
|
||||
} else {
|
||||
e := "dupe"
|
||||
return dupeFileMsg(e)
|
||||
logger.Logger("[LOG] duplicate file name found: " + cfgFileName)
|
||||
os.Exit(1)
|
||||
}
|
||||
_ = cfg
|
||||
f, err := os.Create(fp)
|
||||
@ -294,3 +324,16 @@ func configWriter(t []input.Model){
|
||||
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
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/muesli/termenv"
|
||||
"github.com/spf13/cobra"
|
||||
"pgm/data"
|
||||
"pgm/loader"
|
||||
"pgm/logger"
|
||||
"io/ioutil"
|
||||
"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
|
||||
var loadCmd = &cobra.Command{
|
||||
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
|
||||
to quickly create a Cobra application.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
var dbcfg string
|
||||
var title string
|
||||
var pgHosts []string
|
||||
|
||||
var fn string
|
||||
result := make(chan string, 1)
|
||||
|
||||
pgHosts = data.ReadHosts()
|
||||
title = "choose db to load, \"l\" or enter will load your choice"
|
||||
err := loader.Loader(pgHosts, title, dbcfg)
|
||||
if err != nil {
|
||||
logger.Logger("[ERROR] loading error for testing, ignore.")
|
||||
var hostChoice host
|
||||
hostChoice = host{60, title, pgHosts, 0, false, false, 0, result }
|
||||
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() {
|
||||
rootCmd.AddCommand(loadCmd)
|
||||
|
||||
@ -61,3 +99,183 @@ func init() {
|
||||
// is called directly, e.g.:
|
||||
// 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,
|
||||
// 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
|
||||
// when this action is called directly.
|
||||
@ -76,7 +76,7 @@ func init() {
|
||||
viper.SetDefault("homeDir", homeDirectory)
|
||||
err = viper.ReadInConfig()
|
||||
if err != nil {
|
||||
logger.Logger("[ERROR] viper config issue" + err.Error())
|
||||
logger.Logger("[ERROR] viper hostCfgFile issue" + err.Error())
|
||||
}
|
||||
pgmDir := viper.GetString("pgm_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() {
|
||||
if cfgFile != "" {
|
||||
// Use config file from the flag.
|
||||
// Use hostCfgFile file from the flag.
|
||||
viper.SetConfigFile(cfgFile)
|
||||
} else {
|
||||
// Find home directory.
|
||||
@ -101,7 +101,7 @@ func InitConfig() {
|
||||
fmt.Println(err)
|
||||
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.SetConfigName(".pgm")
|
||||
|
||||
@ -120,8 +120,8 @@ func InitConfig() {
|
||||
|
||||
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 {
|
||||
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