Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 26 additions & 18 deletions utils/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,10 @@ import (
"github.com/mmoehabb/luci/types"
)

func GetShellConfig(c types.Config) *types.ShellConfig {
var shellConfig *types.ShellConfig

switch GetShellType() {
case types.Bash:
shellConfig = &c.Bash
case types.Zshell:
shellConfig = &c.Zshell
case types.Bat:
shellConfig = &c.Bat
default:
shellConfig = &c.Bash
}

return shellConfig
}

// Return an Action or ActionRecord within the config c.
// Dig recursively traverses the configuration structure based on the provided
// input keys. It navigates through ShellConfig, AnnotatedAction, or map[string]any
// types to find and return the action matching the given inputs. Returns nil if
// no matching action is found.
func Dig(action any, inputs []string) any {
for i, input := range inputs {
switch actTyped := action.(type) {
Expand Down Expand Up @@ -56,3 +42,25 @@ func Dig(action any, inputs []string) any {

return action
}

// MapToAnnotatedAction converts a generic map[string]any to an AnnotatedAction.
// It extracts the title, description, and value fields from the map and returns
// a properly typed AnnotatedAction struct. Fields that are not present default
// to empty strings.
func MapToAnnotatedAction(m map[string]any) types.AnnotatedAction {
title := ""
if m["title"] != nil {
title = m["title"].(string)
}

description := ""
if m["description"] != nil {
description = m["description"].(string)
}

return types.AnnotatedAction{
Title: title,
Description: description,
Value: m["value"],
}
}
46 changes: 5 additions & 41 deletions utils/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ example = "echo Hello World!"
example = "echo Hello World!"
`

// LoadDefaultConfig loads the application configuration from luci.config.toml.
// If the configuration file does not exist, it creates a default configuration
// file with example settings. The function returns a populated Config struct
// that contains all shell-specific settings and metadata.
func LoadDefaultConfig() types.Config {
const configPath = "luci.config.toml"

Expand All @@ -44,46 +48,6 @@ func LoadDefaultConfig() types.Config {

// Parse the json data and perform the action the user passes in the arguments
c := types.Config{}
err = toml.Unmarshal(data, &c)
if err != nil {
panic(err)
}

// Convert map[string]any maps with value keys to AnnotatedActions
shellc := *GetShellConfig(c)
digForAnnotatedActions(shellc)

Must(toml.Unmarshal(data, &c))
return c
}

func digForAnnotatedActions(m map[string]any) {
for k, v := range m {
switch v := v.(type) {
case map[string]any:
if v["value"] == nil {
digForAnnotatedActions(v)
continue
}
annAct := MapToAnnotatedAction(v)
m[k] = annAct
}
}
}

func MapToAnnotatedAction(m map[string]any) types.AnnotatedAction {
title := ""
if m["title"] != nil {
title = m["title"].(string)
}

description := ""
if m["description"] != nil {
description = m["description"].(string)
}

return types.AnnotatedAction{
Title: title,
Description: description,
Value: m["value"],
}
}
13 changes: 11 additions & 2 deletions utils/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import (
"github.com/mmoehabb/luci/types"
)

// Act is the main entry point for executing actions based on user input.
// It takes a configuration and a list of input arguments, resolves the appropriate
// action from the shell configuration, and executes it. If no matching action is found,
// it prints usage information or displays available actions.
func Act(c types.Config, inputs []string) {
shell := *GetShellConfig(c)
action := Dig(shell, inputs)
Expand Down Expand Up @@ -71,11 +75,16 @@ func execAction(action any) bool {
case []string:
var cmd *exec.Cmd
var cmdStr = strings.Join(action, " && ")
if runtime.GOOS == "windows" {

switch runtime.GOOS {
case "windows":
cmd = exec.Command("cmd", "/C", cmdStr)
} else {
case "darwin":
cmd = exec.Command("/bin/zsh", "-c", cmdStr)
default:
cmd = exec.Command("/bin/sh", "-c", cmdStr)
}

PrintCommand(cmdStr)
execCmd(cmd)
return true
Expand Down
15 changes: 15 additions & 0 deletions utils/print.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import (
"github.com/mmoehabb/luci/types"
)

// PrintHeader displays the application logo, title, and description from the
// provided configuration. It uses colored output to make the header visually
// distinctive and wraps the description text for better readability.
func PrintHeader(c types.Config) {
color.HiGreen(`
/\\_/\\
Expand All @@ -25,6 +28,9 @@ func PrintHeader(c types.Config) {
color.Yellow("\nUsage:\n\n")
}

// PrintUsage prints the complete usage information, including the header and
// all available actions from the shell configuration. It iterates through
// each action in the configuration and displays them in a formatted manner.
func PrintUsage(c types.Config) {
PrintHeader(c)
shell := *GetShellConfig(c)
Expand All @@ -33,6 +39,9 @@ func PrintUsage(c types.Config) {
}
}

// PrintActionWithInputs resolves an action from the configuration using the
// provided inputs and prints it. It returns an error if the action cannot be
// found, otherwise nil on successful printing.
func PrintActionWithInputs(c map[string]any, inputs []string, level int) error {
action := Dig(c, inputs)
if action == nil {
Expand All @@ -42,6 +51,9 @@ func PrintActionWithInputs(c map[string]any, inputs []string, level int) error {
return nil
}

// PrintAction prints an action in a formatted way based on its type. It handles
// AnnotatedAction, map[string]any, []string, and string types, applying
// appropriate colors and indentation to display hierarchical action structures.
func PrintAction(action any, inputs []string, level int) {
switch action := action.(type) {
case types.AnnotatedAction:
Expand Down Expand Up @@ -79,6 +91,9 @@ func PrintAction(action any, inputs []string, level int) {
}
}

// PrintCommand displays the command that is about to be executed with a
// highlighted green background and white text, making it visually distinct
// in the terminal output.
func PrintCommand(cmd string) {
color.New(color.BgGreen, color.FgHiWhite).Printf("+ %s", cmd)
fmt.Println()
Expand Down
27 changes: 27 additions & 0 deletions utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import (
"github.com/mmoehabb/luci/types"
)

// GetShellType detects and returns the appropriate shell type for the current
// operating system. It returns Bash for Linux, Zshell for macOS (darwin),
// Bat for Windows, and Unknown for any other OS.
func GetShellType() types.ShellType {
switch runtime.GOOS {
case "linux":
Expand All @@ -19,6 +22,30 @@ func GetShellType() types.ShellType {
}
}

// GetShellConfig returns a pointer to the shell-specific configuration section
// from the provided config based on the current operating system's shell type.
// It selects between Bash, Zshell, or Bat configurations, defaulting to Bash
// if the shell type is unknown.
func GetShellConfig(c types.Config) *types.ShellConfig {
var shellConfig *types.ShellConfig

switch GetShellType() {
case types.Bash:
shellConfig = &c.Bash
case types.Zshell:
shellConfig = &c.Zshell
case types.Bat:
shellConfig = &c.Bat
default:
shellConfig = &c.Bash
}

return shellConfig
}

// Must panics if the provided error is not nil, otherwise it returns without error.
// This function is used for error handling in scenarios where errors indicate
// critical failures that should halt program execution.
func Must(err error) {
if err != nil {
panic(err)
Expand Down