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
3 changes: 1 addition & 2 deletions cmd/alias.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package cmd
import (
"bytes"
"embed"
"log"
"os"
"text/template"

Expand Down Expand Up @@ -56,7 +55,7 @@ Read the template script 'tome-wrapper.sh.tmpl' for more information on how the
t, err := template.ParseFS(content, "embeds/tome-wrapper.sh.tmpl")
// Capture any error
if err != nil {
log.Fatalln(err)
log.Fatal(err)
}
buf := new(bytes.Buffer)
v, err := cmd.Flags().GetString("output")
Expand Down
2 changes: 0 additions & 2 deletions cmd/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ Copyright © 2024 Zander Hill <[email protected]>
package cmd

import (
"log"

"github.com/spf13/cobra"
"github.com/spf13/cobra/doc"
)
Expand Down
1 change: 0 additions & 1 deletion cmd/lib.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"encoding/json"
"fmt"
"io"
"log"
"net/url"
"os"
"os/exec"
Expand Down
5 changes: 4 additions & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/gobeam/stringy"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"go.uber.org/zap"
)

var rootDir string
Expand Down Expand Up @@ -64,9 +65,11 @@ func init() {
viper.SetDefault("license", "mit")
}

var log *zap.SugaredLogger

// initConfig reads in config file and ENV variables if set.
func initConfig() {
log := createLogger("initConfig", rootCmd.OutOrStderr())
log = createLogger("initConfig", rootCmd.OutOrStderr())
v := viper.GetViper()
var err error
rootDir, err = filepath.Abs(rootDir)
Expand Down
114 changes: 79 additions & 35 deletions cmd/struct.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cmd

import (
"bufio"
"fmt"
"os"
"path/filepath"
Expand Down Expand Up @@ -48,60 +49,103 @@ func (s *Script) IsExecutable() bool {
return isExecutableByOwner(fileInfo.Mode())
}

func (s *Script) parse() error {
b, err := os.ReadFile(s.path)
// ParseV2 returns the usage and help text for the script
// function aims to return early and perform as little work as possible
// to avoid reading the entire file and stay performant
// with large script folders and files
func (s *Script) ParseV2() (string, string, error) {
log.Debugw("Parsing script", "path", s.path)
file, err := os.Open(s.path)
if err != nil {
return err
return "", "", err
}

if strings.Contains(string(b), UsageKey) || strings.Contains(string(b), LegacyUsageKey) {
lines := strings.Split(string(b), "\n")
var linesStart int
for idx, line := range lines {
if strings.Contains(line, UsageKey) || strings.Contains(line, LegacyUsageKey) {
linesStart = idx
defer file.Close()

scanner := bufio.NewScanner(file)

// Parse the script file for the usage and help text
// Expected structure is:
// #!/bin/bash
// # USAGE: script.sh [options] <arg1> <arg2>
// # This is the help text for the script
// # It can span multiple lines
//
// echo 1

var usage, help string
idx := 0

var helpArr []string

startsWithComment := regexp.MustCompile(`^[/*\-#]+`)
for scanner.Scan() {
t := scanner.Text()
log.Debugw("Parsing line", "line", t)
// Skip the shebang line
if idx == 0 && strings.HasPrefix(t, "#!") {
log.Debugw("shebang", "line", t)
idx++
continue
}
// Normally this is the usage line
if idx == 1 {
log.Debugw("likely usage", "line", t)
if !startsWithComment.MatchString(t) {
usage = ""
help = ""
break
} else {
withoutCommentChars := strings.TrimLeft(t, "#/-*")
regexes := []regexp.Regexp{
*regexp.MustCompile(`(USAGE|SUMMARY):`),
*regexp.MustCompile(fmt.Sprintf(`(%s|%s)`, regexp.QuoteMeta(`$0`), regexp.QuoteMeta(filepath.Base(s.path)))),
*regexp.MustCompile(`TOME_[A-Z_]+`), // ignore tome option flags
}
for _, r := range regexes {
withoutCommentChars = r.ReplaceAllLiteralString(withoutCommentChars, "")
}
usage = strings.TrimSpace(withoutCommentChars)
log.Debugw("usage", "usage", usage)
idx++
}
}

var helpEnds int
for idx, line := range lines[linesStart:] {
if line == "" {
helpEnds = idx + linesStart
break
}
// Scan until we find an empty line
if startsWithComment.MatchString(t) {
t2 := strings.TrimSpace(strings.TrimLeft(t, "#/-*"))
log.Debugw("help line", "line", t2)
helpArr = append(helpArr, t2)
idx++
continue
} else {
break
}
helpTextLines := lines[linesStart:helpEnds]
helpText := strings.Join(helpTextLines, "\n")
}
help = strings.Join(helpArr, "\n")

s.usage = strings.TrimSpace(strings.SplitN(lines[linesStart], ":", 2)[1])
s.help = helpText
return usage, help, nil
}

func (s *Script) parse() error {
usage, help, err := s.ParseV2()
if err != nil {
return err
}
s.usage = usage
s.help = help

return nil
}

// Usage returns the usage string for the script
// after stripping out the script name or $0
// this is done to reduce visual noise
func (s *Script) Usage() string {
baseUsage := s.usage
prefixes := []string{"$0", filepath.Base(s.path)}
for _, prefix := range prefixes {
baseUsage = strings.TrimPrefix(baseUsage, prefix)
}
baseUsage = strings.TrimSpace(baseUsage)
return dedent.Dedent(baseUsage)
return dedent.Dedent(s.usage)
}

func (s *Script) Help() string {
lines := strings.Split(s.help, "\n")
var helpTextLines []string
toTrim := []string{"#", "//", "/\\*", "\\*/", "--"}
toTrimRegex := regexp.MustCompile(fmt.Sprintf("^(%s)+", strings.Join(toTrim, "|")))
for _, line := range lines {
helpTextLines = append(helpTextLines, toTrimRegex.ReplaceAllString(line, ""))
}
return dedent.Dedent(strings.Join(helpTextLines, "\n"))
return dedent.Dedent(s.help)
}

func (s *Script) PathWithoutRoot() string {
Expand Down