Skip to content

Commit f1c56a2

Browse files
author
Florian Heinze
committed
TASK: refactored to look nicely with cobra
1 parent 20978a5 commit f1c56a2

16 files changed

Lines changed: 286 additions & 357 deletions

File tree

cmd/completion.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package cmd
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
"os"
6+
)
7+
8+
func buildCompletionCommand() *cobra.Command {
9+
return &cobra.Command{
10+
Use: "DSR_COMPLETION [bash|zsh|fish|powershell]",
11+
Short: "Generate completion script for common shells",
12+
Long: `Generate Autocomplete Scripts for common shells for the DevScriptRunner tool
13+
14+
To load completions:
15+
16+
Bash:
17+
18+
$ source <(dev DSR_COMPLETION bash)
19+
20+
# To load completions for each session, execute once:
21+
Linux:
22+
$ dev DSR_COMPLETION bash > /etc/bash_completion.d/dev
23+
MacOS:
24+
$ dev DSR_COMPLETION bash > /usr/local/etc/bash_completion.d/dev
25+
26+
Zsh:
27+
28+
# If shell completion is not already enabled in your environment you will need
29+
# to enable it. You can execute the following once:
30+
31+
$ echo "autoload -U compinit; compinit" >> ~/.zshrc
32+
33+
# To load completions for each session, execute once:
34+
$ dev DSR_COMPLETION zsh > "${fpath[1]}/_dev"
35+
36+
# You will need to start a new shell for this setup to take effect.
37+
38+
Fish:
39+
40+
$ dev DSR_COMPLETION fish | source
41+
42+
# To load completions for each session, execute once:
43+
$ dev DSR_COMPLETION fish > ~/.config/fish/completions/dev.fish
44+
`,
45+
DisableFlagsInUseLine: true,
46+
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
47+
Args: cobra.ExactValidArgs(1),
48+
Run: func(cmd *cobra.Command, args []string) {
49+
switch args[0] {
50+
case "bash":
51+
cmd.Root().GenBashCompletion(os.Stdout)
52+
case "zsh":
53+
cmd.Root().GenZshCompletion(os.Stdout)
54+
case "fish":
55+
cmd.Root().GenFishCompletion(os.Stdout, true)
56+
case "powershell":
57+
cmd.Root().GenPowerShellCompletion(os.Stdout)
58+
}
59+
},
60+
}
61+
}

cmd/init.go

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,42 @@ package cmd
33
import (
44
"github.com/gookit/color"
55
"github.com/spf13/cobra"
6+
"log"
7+
"main/utils"
8+
"os"
9+
"path/filepath"
610
)
711

812
func buildInitCommand() *cobra.Command {
9-
var execRootCmd = &cobra.Command{
10-
Use: "INIT",
11-
Short: "TODO",
12-
Long: color.Sprintf(`Usage: TODO`),
13-
Args: cobra.RangeArgs(1, 2),
13+
return &cobra.Command{
14+
Use: "DSR_INIT",
15+
Short: "creates dev.sh and dev_setup.sh in current folder",
16+
Long: color.Sprintf(`This creates an example create a dev.sh and dev_setup.sh in your current directory.`),
17+
Args: cobra.NoArgs,
1418

1519
Run: func(cmd *cobra.Command, args []string) {
16-
color.Sprintf(`RUN COMMAND`)
20+
currentDirectory, err := os.Getwd()
21+
if err != nil {
22+
log.Fatalf("Failed to execute: '%s'", err.Error())
23+
}
24+
devShTargetPath := filepath.Join(currentDirectory, "dev.sh")
25+
devSetupShTargetPath := filepath.Join(currentDirectory, "dev_setup.sh")
26+
27+
if !utils.FileExists(devShTargetPath) {
28+
// we can access embedded assets by using the path use din the annotation
29+
utils.CopyAssetToPath("templates/dev.sh", devShTargetPath)
30+
if !utils.FileExists(devSetupShTargetPath) {
31+
// We do not want to add dev_setup.sh if INIT was already run.
32+
// The file might have been deleted on purpose.
33+
utils.CopyAssetToPath("templates/dev_setup.sh", devSetupShTargetPath)
34+
} else {
35+
color.Yellow.Println("dev_setup.sh is already present!")
36+
}
37+
} else {
38+
color.Yellow.Println("dev.sh is already present.")
39+
color.Style{color.Yellow, color.Bold}.Println("Skipping INIT!")
40+
}
41+
os.Exit(0)
1742
},
1843
}
19-
return execRootCmd
2044
}

cmd/no_tasks.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package cmd
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
)
6+
7+
// This is a hack add a hint to cobra rendering
8+
func buildNoTaskCommand(reason string) *cobra.Command {
9+
return &cobra.Command{
10+
Short: "\n " + reason + "\n",
11+
DisableFlagParsing: true,
12+
DisableAutoGenTag: true,
13+
DisableFlagsInUseLine: true,
14+
DisableSuggestions: true,
15+
Run: func(cmd *cobra.Command, args []string) {
16+
// if Run is not present the command will be listed
17+
// in the "additional commands" section of cobra
18+
// DO NOTHING
19+
},
20+
}
21+
}

cmd/root.go

Lines changed: 29 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -2,41 +2,35 @@ package cmd
22

33
import (
44
"fmt"
5-
"github.com/logrusorgru/aurora/v3"
65
"github.com/spf13/cobra"
76
"log"
8-
"math/rand"
7+
"main/utils"
98
"os"
109
"path/filepath"
11-
"regexp"
12-
"sort"
13-
"strings"
14-
"time"
10+
"strconv"
1511
)
1612

17-
const DEV_SCRIPT_MARKER = "DEV_SCRIPT_MARKER"
18-
const DEV_SCRIPT_NAME = "dev.sh"
19-
const MAX_DEPTH = 100
20-
const INIT_ARGUMENT = "INIT"
21-
const LOGGING_PREFIX = "DSR - " // DevScriptRunner
22-
const LOGGING_WSPACE = " "
23-
const LOGGING_BAR = "-------------------------------------------------------"
24-
25-
var rootCmd = &cobra.Command{
26-
Long: "LONG",
27-
Use: "drydock",
13+
var RootCmd = &cobra.Command{
14+
Use: "dev",
15+
Long: `
16+
DevScriptRunner is a helper to run task from a dev.sh file
17+
from within a nested folder structure also providing autocompletion
18+
and other nifty feature ;)
19+
`,
2820
Short: "SHORT",
2921
Example: "",
3022
}
3123

3224
// Execute adds all child commands to the root command and sets flags appropriately.
3325
// This is called by main.main(). It only needs to happen once to the rootCmd.
3426
func Execute(version, commit string) {
35-
rootCmd.AddCommand(buildInitCommand())
36-
addDevScriptTasksAsCommands(rootCmd)
37-
rand.Seed(time.Now().UnixNano())
38-
39-
if err := rootCmd.Execute(); err != nil {
27+
cobra.EnableCommandSorting = false
28+
RootCmd.AddCommand(buildSectionCommand("tasks"))
29+
addDevScriptTasksAsCommands(RootCmd)
30+
RootCmd.AddCommand(buildSectionCommand("utils"))
31+
RootCmd.AddCommand(buildInitCommand())
32+
// RootCmd.AddCommand(buildCompletionCommand())
33+
if err := RootCmd.Execute(); err != nil {
4034
fmt.Println(err)
4135
os.Exit(1)
4236
}
@@ -54,80 +48,27 @@ func addDevScriptTasksAsCommands(rootCmd *cobra.Command) {
5448

5549
steps := 0
5650
for {
57-
devScriptPath := filepath.Join(currentDirectory, DEV_SCRIPT_NAME)
58-
if fileExists(devScriptPath) {
59-
fmt.Println(aurora.Magenta("Found " + currentDirectory + "/" + DEV_SCRIPT_NAME))
60-
if fileContains(devScriptPath, DEV_SCRIPT_MARKER) {
61-
fmt.Println(aurora.Magenta("with '" + DEV_SCRIPT_MARKER + "'"))
62-
tasks := parseDevScriptTasks(devScriptPath)
63-
for _, task := range tasks {
64-
rootCmd.AddCommand(buildTaskCommand(task, devScriptPath))
51+
devScriptPath := filepath.Join(currentDirectory, utils.DEV_SCRIPT_NAME)
52+
if utils.FileExists(devScriptPath) {
53+
if utils.FileContains(devScriptPath, utils.DEV_SCRIPT_MARKER) {
54+
tasks := utils.ParseDevScriptTasks(devScriptPath)
55+
rootCmd.Long = rootCmd.Long + "\nDEV SCRIPT WITH " + strconv.Itoa(len(tasks)) + " TASKS FOUND AT:\n " + devScriptPath
56+
if len(tasks) > 0 {
57+
for _, task := range tasks {
58+
rootCmd.AddCommand(buildTaskCommand(task, devScriptPath))
59+
}
60+
} else {
61+
rootCmd.AddCommand(buildNoTaskCommand("NO TASKS IN DEV SCRIPT!"))
6562
}
6663
break
67-
} else {
68-
fmt.Println(aurora.Yellow(LOGGING_WSPACE + "Marker '" + DEV_SCRIPT_MARKER + "' is missing in " + DEV_SCRIPT_NAME))
69-
fmt.Println(aurora.Yellow(LOGGING_WSPACE + "Moving up, looking for new " + DEV_SCRIPT_NAME))
70-
// Not breaking here as we want to move up
7164
}
7265
}
73-
if currentDirectory == "/" || steps >= MAX_DEPTH {
74-
fmt.Println(aurora.Yellow(aurora.Bold(LOGGING_PREFIX + "No " + DEV_SCRIPT_NAME + " with " + DEV_SCRIPT_MARKER + " found. Nothing to do here :(")))
75-
fmt.Println(aurora.Magenta(aurora.Bold(LOGGING_BAR)))
66+
if currentDirectory == "/" || steps >= utils.MAX_DEPTH {
67+
rootCmd.AddCommand(buildNoTaskCommand("NO DEV SCRIPT WITH VALID MARKER FOUND!"))
7668
break
7769
}
7870
// Moving up one directory
7971
currentDirectory = filepath.Dir(currentDirectory)
8072
steps += 1
8173
}
8274
}
83-
84-
func fileExists(path string) bool {
85-
info, err := os.Stat(path)
86-
if os.IsNotExist(err) {
87-
return false
88-
}
89-
return !info.IsDir()
90-
}
91-
92-
func fileContains(filePath string, needle string) bool {
93-
b, err := os.ReadFile(filePath)
94-
if err != nil {
95-
log.Fatalf("Failed to execute: '%s'", err.Error())
96-
}
97-
markerIsPresent, err := regexp.Match(needle, b)
98-
if err != nil {
99-
log.Fatalf("Failed to execute: '%s'", err.Error())
100-
}
101-
return markerIsPresent
102-
}
103-
104-
type DevScriptTask struct {
105-
name string
106-
comments string
107-
}
108-
109-
func parseDevScriptTasks(devScriptPath string) []DevScriptTask {
110-
// https://regex101.com/r/5LVRcP/1 -> Iteration 1 without comments before
111-
// https://regex101.com/r/XyB410/1 -> Final Iteration with comments before ;)
112-
devScriptBytes, err := os.ReadFile(devScriptPath)
113-
if err != nil {
114-
log.Fatalf("Failed to execute: '%s'", err.Error())
115-
}
116-
117-
devScriptString := string(devScriptBytes)
118-
compiledRegex := regexp.MustCompile(`(?m)(?P<comments>(?:^#.*(?:\n|\r\n|\r))*)^(?:function )?(?P<name>[a-zA-Z0-9_-]+)\s?(?:\(\))?\s?{`)
119-
captureGroupNames := compiledRegex.SubexpNames()
120-
commentsIndex := sort.StringSlice(captureGroupNames).Search("comments")
121-
taskIndex := sort.StringSlice(captureGroupNames).Search("name")
122-
matches := compiledRegex.FindAllStringSubmatch(devScriptString, -1)
123-
124-
var results = []DevScriptTask{}
125-
for _, match := range matches {
126-
task := match[taskIndex]
127-
comments := match[commentsIndex]
128-
if !strings.HasPrefix(task, "_") {
129-
results = append(results, DevScriptTask{name: task, comments: comments})
130-
}
131-
}
132-
return results
133-
}

cmd/section.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package cmd
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
"main/utils"
6+
)
7+
8+
// This is a hack to add a section to cobra rendering
9+
func buildSectionTitle(title string) string {
10+
title = " " + title + " "
11+
beforeAndAfterSeparatorLength := (len(utils.SECTION_SEPARATOR) - len(title)) / 2
12+
remainingSeparator := utils.SECTION_SEPARATOR[0 : beforeAndAfterSeparatorLength-1]
13+
return "\n" + remainingSeparator + title + remainingSeparator + "\n"
14+
}
15+
16+
func buildSectionCommand(title string) *cobra.Command {
17+
return &cobra.Command{
18+
Short: buildSectionTitle(title),
19+
DisableFlagParsing: true,
20+
DisableAutoGenTag: true,
21+
DisableFlagsInUseLine: true,
22+
DisableSuggestions: true,
23+
Run: func(cmd *cobra.Command, args []string) {
24+
// if Run is not present the command will be listed
25+
// in the "additional commands" section of cobra
26+
// DO NOTHING
27+
},
28+
}
29+
}

cmd/task.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,20 @@ package cmd
33
import (
44
"github.com/spf13/cobra"
55
"log"
6+
"main/utils"
67
"os"
78
"path/filepath"
89
"syscall"
910
)
1011

11-
func buildTaskCommand(task DevScriptTask, devScriptPath string) *cobra.Command {
12+
func buildTaskCommand(task utils.DevScriptTask, devScriptPath string) *cobra.Command {
1213
var cmd = &cobra.Command{
13-
Use: task.name,
14-
Short: task.comments,
15-
Long: task.comments,
14+
Use: task.Name,
15+
Short: task.Comments,
16+
Long: task.Comments,
1617
Args: cobra.ArbitraryArgs,
1718
Run: func(cmd *cobra.Command, args []string) {
18-
execDevScriptWithArguments(devScriptPath, append([]string{task.name}, args...))
19+
execDevScriptWithArguments(devScriptPath, append([]string{task.Name}, args...))
1920
},
2021
}
2122
return cmd

0 commit comments

Comments
 (0)