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
4 changes: 2 additions & 2 deletions examples/luci.arduino-cli.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ arduino-cli monitor . -p /dev/ttyUSB0 \
"""


# ************ BAT COMMANDS ************
[bat]
# ************ POWERSHELL COMMANDS ************
[powershell]

install = """
arduino-cli config set network.connection_timeout 600s &&
Expand Down
5 changes: 4 additions & 1 deletion luci.config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,8 @@ act2 = "echo action3 act2"
[zshell.run]
example = "echo Hello World!"

[bat.run]
[powershell.run]
example = "echo Hello World!"

["*".run]
example-wildcard = "echo Hello Wildcard!"
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ func main() {
flag.Parse()

if *versionFlag {
fmt.Printf("LUCI version %s\n", "0.0.6")
fmt.Printf("LUCI version %s\n", "0.1.0")
os.Exit(0)
}

Expand Down
5 changes: 3 additions & 2 deletions types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ type Config struct {
Description string
Bash ShellConfig
Zshell ShellConfig
Bat ShellConfig
Powershell ShellConfig
Wildcard ShellConfig `toml:"*"`
}

type ShellConfig map[string]any // any: Action or ActionRecord
Expand All @@ -29,6 +30,6 @@ type ShellType int
const (
Bash ShellType = iota
Zshell
Bat
Powershell
Unknown
)
11 changes: 10 additions & 1 deletion utils/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ example = "echo Hello World!"
[zshell.run]
example = "echo Hello World!"

[bat.run]
[powershell.run]
example = "echo Hello World!"

["*"]
`

// LoadDefaultConfig loads the application configuration from luci.config.toml.
Expand Down Expand Up @@ -63,6 +65,13 @@ func ParseTomlConfig(data []byte) types.Config {
c.Bash[key] = nm
}
}
for key, value := range c.Wildcard {
switch value := value.(type) {
case map[string]any:
nm := parseMapValues(value)
c.Wildcard[key] = nm
}
}
return c
}

Expand Down
12 changes: 6 additions & 6 deletions utils/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,19 @@ import (

// 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.
// action from the shell configuration (with wildcard fallback), 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, i := Dig(shell, inputs)
merged := *GetMergedShellConfig(c)
action, i := Dig(merged, inputs)
args := inputs[i:]

if action == nil {
if len(inputs) <= 1 {
PrintUsage(c)
return
}
PrintActionWithInputs(shell, inputs[0:len(inputs)-1], 0)
PrintActionWithInputs(merged, inputs[0:len(inputs)-1], 0)
return
}

Expand Down Expand Up @@ -57,7 +57,7 @@ func Act(c types.Config, inputs []string) {
}
}

PrintActionWithInputs(shell, inputs, 0)
PrintActionWithInputs(merged, inputs, 0)
}

func execAction(action any, args []string) bool {
Expand Down
20 changes: 10 additions & 10 deletions utils/print.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ func PrintHeader(c types.Config) {
}

func PrintInteractiveUsage(c types.Config) {
shell := *GetShellConfig(c)
actions := CollectActions(shell)
merged := *GetMergedShellConfig(c)
actions := CollectActions(merged)
displayLevel(c, []string{}, actions)
}

Expand Down Expand Up @@ -73,12 +73,12 @@ func displayLevel(c types.Config, path []string, actions []ActionNode) {
if selected == BackKey {
if len(path) > 0 {
parentPath := path[:len(path)-1]
shell := *GetShellConfig(c)
merged := *GetMergedShellConfig(c)
var parentActions []ActionNode
if len(parentPath) == 0 {
parentActions = CollectActions(shell)
parentActions = CollectActions(merged)
} else {
parentConfig, _ := Dig(shell, parentPath)
parentConfig, _ := Dig(merged, parentPath)
if parentConfig != nil {
parentActions = CollectActions(parentConfig.(map[string]any))
}
Expand All @@ -89,8 +89,8 @@ func displayLevel(c types.Config, path []string, actions []ActionNode) {
}

newPath := append(path, selected)
shell := *GetShellConfig(c)
selectedAction, _ := Dig(shell, newPath)
merged := *GetMergedShellConfig(c)
selectedAction, _ := Dig(merged, newPath)

if selectedAction == nil {
displayLevel(c, path, actions)
Expand Down Expand Up @@ -127,9 +127,9 @@ func displayLevel(c types.Config, path []string, actions []ActionNode) {

func PrintUsage(c types.Config) {
PrintHeader(c)
shell := *GetShellConfig(c)
for action := range shell {
PrintActionWithInputs(shell, []string{action}, 0)
merged := *GetMergedShellConfig(c)
for action := range merged {
PrintActionWithInputs(merged, []string{action}, 0)
}
}

Expand Down
67 changes: 62 additions & 5 deletions utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,23 @@ import (

// 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.
// Powershell for Windows, and Unknown for any other OS.
func GetShellType() types.ShellType {
switch runtime.GOOS {
case "linux":
return types.Bash
case "darwin":
return types.Zshell
case "windows":
return types.Bat
return types.Powershell
default:
return types.Unknown
}
}

// 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
// It selects between Bash, Zshell, or Powershell configurations, defaulting to Bash
// if the shell type is unknown.
func GetShellConfig(c types.Config) *types.ShellConfig {
var shellConfig *types.ShellConfig
Expand All @@ -34,8 +34,8 @@ func GetShellConfig(c types.Config) *types.ShellConfig {
shellConfig = &c.Bash
case types.Zshell:
shellConfig = &c.Zshell
case types.Bat:
shellConfig = &c.Bat
case types.Powershell:
shellConfig = &c.Powershell
default:
shellConfig = &c.Bash
}
Expand All @@ -51,3 +51,60 @@ func Must(err error) {
panic(err)
}
}

// GetWildcardConfig returns a pointer to the wildcard ("*") configuration section.
// This section acts as a fallback when an action is not found in the shell-specific config.
func GetWildcardConfig(c types.Config) *types.ShellConfig {
return &c.Wildcard
}

// GetMergedShellConfig returns a pointer to a new ShellConfig that deep-merges
// the shell-specific config with the wildcard ("*") config. The shell config
// takes priority on key conflicts at every nesting level.
func GetMergedShellConfig(c types.Config) *types.ShellConfig {
shell := *GetShellConfig(c)
wildcard := *GetWildcardConfig(c)
merged := MergeShellConfigs(shell, wildcard)
return &merged
}

// MergeShellConfigs deep-merges overlay into base. Base takes priority on key
// conflicts. When both sides have a map[string]any at the same key, the merge
// recurses; otherwise base wins.
func MergeShellConfigs(base, overlay types.ShellConfig) types.ShellConfig {
result := make(types.ShellConfig)
for k, v := range base {
result[k] = v
}
for k, v := range overlay {
if existing, exists := result[k]; exists {
existingMap, eOk := existing.(map[string]any)
overlayMap, oOk := v.(map[string]any)
if eOk && oOk {
result[k] = mergeMaps(existingMap, overlayMap)
}
} else {
result[k] = v
}
}
return result
}

func mergeMaps(base, overlay map[string]any) map[string]any {
result := make(map[string]any)
for k, v := range base {
result[k] = v
}
for k, v := range overlay {
if existing, exists := result[k]; exists {
existingMap, eOk := existing.(map[string]any)
overlayMap, oOk := v.(map[string]any)
if eOk && oOk {
result[k] = mergeMaps(existingMap, overlayMap)
}
} else {
result[k] = v
}
}
return result
}
Loading