Skip to content

Commit

Permalink
chore: add inital cli scaffold
Browse files Browse the repository at this point in the history
  • Loading branch information
s1ntaxe770r authored and andrew-s committed May 24, 2024
1 parent 58f65a2 commit 8368895
Show file tree
Hide file tree
Showing 6 changed files with 314 additions and 0 deletions.
91 changes: 91 additions & 0 deletions charm/input.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package charm

import (
"fmt"

"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)

type Input struct {
text string
placeholder string
initialValue string
hidden bool
required bool
}

var (
SuccessStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("#00FF00"))

ErrorStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("#FF0000")). // Red color
Bold(true)

inputStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("205"))
titleStyle = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("63"))
)

// Function to create and run the bubbletea model
func GetSensitiveInput(placeholder string, defaultValue string) (string, error) {
initialModel := model{
textInput: textinput.New(),
}
initialModel.textInput.Placeholder = placeholder
initialModel.textInput.SetValue(defaultValue)
initialModel.textInput.EchoMode = textinput.EchoPassword // Hides the input
initialModel.textInput.Focus()

p := tea.NewProgram(initialModel)
finalModel, err := p.Run()
if err != nil {
return "", err
}

return finalModel.(model).textInput.Value(), nil
}

// Bubbletea model
type model struct {
textInput textinput.Model
err error
}

func (m model) Init() tea.Cmd {
return textinput.Blink
}

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.Type {
case tea.KeyEnter:
if m.textInput.Value() == "" {
m.err = fmt.Errorf("input is required")
return m, nil
}
return m, tea.Quit
case tea.KeyCtrlC:
return m, tea.Quit
}
}
var cmd tea.Cmd
m.textInput, cmd = m.textInput.Update(msg)
return m, cmd
}

func (m model) View() string {
var errMsg string
if m.err != nil {
errMsg = ErrorStyle.Render(m.err.Error()) + "\n\n"
}

return fmt.Sprintf(
"%s\n\n%s\n\n%s",
titleStyle.Render("Enter your token:"),
inputStyle.Render(m.textInput.View()),
errMsg,
)
}
10 changes: 10 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package main

import (
"github.com/qernal/cli-qernal/commands"
)

func main() {
commands.RootCmd.Execute()

}
25 changes: 25 additions & 0 deletions commands/auth/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package auth

import (
"errors"

"github.com/spf13/cobra"
)

var AuthCmd = &cobra.Command{
Use: "auth",
Short: "Manage your auth tokens",
RunE: func(cmd *cobra.Command, args []string) error {
err := cmd.Help()
if err != nil {
return err
}
return errors.New("a valid subcommand is required")
},
}

func init() {

AuthCmd.AddCommand(loginCmd)

}
100 changes: 100 additions & 0 deletions commands/auth/init.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package auth

import (
"fmt"
"os"
"path/filepath"

"github.com/qernal/cli-qernal/charm"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"gopkg.in/yaml.v2"
)

type config struct {
Token string `yaml:"token"`
}

var (
loginCmd = &cobra.Command{
Use: "login",
Short: "Log in to your Qernal account",
Long: `log in to your Qernal account by searching for the QERNAL_TOKEN environment variable first.
the order in which values are searched for:
1. **QERNAL_TOKEN environment variable:** If set, this is used as the token.
2. **$HOME/.qernal/config.yaml file:** If the environment variable is not found, the CLI checks for the token in this file.
3. **User input:** If neither of the above is found, the user is prompted to enter their Qernal token.`,
RunE: func(cmd *cobra.Command, args []string) error {
token, err := getQernalToken()
if err != nil {
return err
}

return saveConfig(token)
},
}

cfgPath = filepath.Join(os.Getenv("HOME"), ".qernal", "config.yaml")
)

func getQernalToken() (string, error) {
// 1. Check environment variable
if token := os.Getenv("QERNAL_TOKEN"); token != "" {
fmt.Println(charm.SuccessStyle.Render("configuring CLI using environment variable ✅"))

return token, nil
}

// 2. Check config file
if token, err := readConfig(cfgPath); err == nil {
fmt.Println(charm.SuccessStyle.Render(fmt.Sprintf("Using token from %s.", cfgPath)))
return token, nil
} else if os.IsNotExist(err) {
// File doesn't exist, continue to prompt user
token, err := charm.GetSensitiveInput("clientid@clientsecret", ".....")
if err != nil {
fmt.Println(charm.ErrorStyle.Render(fmt.Sprintf("error retrieving input %s", err.Error())))
return "", err
}
return token, nil

}
token, err := charm.GetSensitiveInput("Enter your token", ".....")
if err != nil {
fmt.Println(charm.ErrorStyle.Render(fmt.Sprintf("error retrieving input %s", err.Error())))
return "", err
}
return token, nil
}

func readConfig(cfgPath string) (string, error) {
viper.SetConfigFile(cfgPath)

// Read the config file
if err := viper.ReadInConfig(); err != nil {
return "", fmt.Errorf("error reading config file, %s", err)
}

// Unmarshal the config into a struct
var cfg config
if err := viper.Unmarshal(&cfg); err != nil {
return "", fmt.Errorf("unable to decode into struct, %v", err)
}

return cfg.Token, nil
}

func saveConfig(token string) error {
cfg := &config{Token: token}
data, err := yaml.Marshal(cfg)
if err != nil {
return err
}

if err := os.MkdirAll(filepath.Dir(cfgPath), 0755); err != nil {
return err
}
return os.WriteFile(cfgPath, data, 0644)
}
31 changes: 31 additions & 0 deletions commands/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package commands

import (
"os"

"github.com/qernal/cli-qernal/commands/auth"
"github.com/spf13/cobra"
)

var RootCmd = &cobra.Command{
Use: "qernal",
Short: "CLI for interacting with qernal.com",
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return cmd.Help()
}
return nil
},
}

func Execute() {
err := RootCmd.Execute()
if err != nil {
os.Exit(1)
}
}

func init() {
RootCmd.AddCommand(auth.AuthCmd)

}
57 changes: 57 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package config

import (
"fmt"
"os"

"github.com/mitchellh/go-homedir"
"github.com/spf13/viper"
)

var (

// represents the current config
Current Config
)

type ErrNoTokenFound struct{}

func (m *ErrNoTokenFound) Error() string {
return "no token found"
}

type Config struct {
Token string `yaml:"token"`
}

func LoadConfig() {

token, found := os.LookupEnv("QERNAL_TOKEN")

if found && token != "" {
Current.Token = token
return
}

// lookup token in config
home, err := homedir.Dir()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
viper.SetConfigFile(fmt.Sprintf("%s/%s/%s", home, ".qernal", "config.yaml"))

err = viper.ReadInConfig()
if err != nil {
fmt.Print(fmt.Errorf("unable to read qernal config", err).Error())
}

token = viper.Get("token").(string)
if Current.Token != "" {
Current.Token = token
return
}

// TODO: prompt user for token
fmt.Println("no token found")
}

0 comments on commit 8368895

Please sign in to comment.