Skip to content

Commit

Permalink
Refactor shared code into a 'arguments' package
Browse files Browse the repository at this point in the history
  • Loading branch information
jdelStrother authored and mroth committed Feb 9, 2020
1 parent ffb3fc0 commit 38698d3
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 210 deletions.
87 changes: 87 additions & 0 deletions commands/arguments/arguments.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package arguments

import (
"os"
"path/filepath"
"regexp"
"strconv"
)

var expandArgDigitMatcher = regexp.MustCompile("^[0-9]{0,4}$")
var expandArgRangeMatcher = regexp.MustCompile("^([0-9]+)-([0-9]+)$")

// Evaluates a string of arguments and expands environment variables.
func EvaluateEnvironment(arg string, expandRelative bool) string {
expandedArg := os.ExpandEnv(arg)
if expandRelative {
return convertToRelativeIfFilePath(expandedArg)
}
return expandedArg
}

// For a given arg, try to determine if it represents a file, and if so, convert
// it to a relative filepath.
//
// Otherwise (or if any error conditions occur) return it unmolested.
func convertToRelativeIfFilePath(arg string) string {
if _, err := os.Stat(arg); err == nil {
wd, err1 := os.Getwd()
relPath, err2 := filepath.Rel(wd, arg)
if err1 == nil && err2 == nil {
return relPath
}
}
return arg
}

// Expand takes the list of arguments received from the command line and expands
// them given our special case rules.
//
// It handles converting numeric file placeholders and range placeholders into
// environment variable symbolic representation,
func Expand(args []string) []string {
var results []string
for _, arg := range args {
results = append(results, expandArg(arg)...)
}
return results
}

// expandArg "expands" a single argument we received on the command line.
//
// It's possible that argument represents a numeric file placeholder, in which
// case we will replace it with the syntax to represent the environment variable
// that it will be held in (e.g. "$e1").
//
// It's also possible that argument may represent a range, in which case it will
// return multiple instances of environment variable placeholders.
func expandArg(arg string) []string {

// ...is it a single digit?
dm := expandArgDigitMatcher.FindString(arg)
if dm != "" {
// dont expand if its actually a numerically named file or directory!
if _, err := os.Stat(dm); err == nil {
return []string{arg} //return as-is
}

result := "$e" + dm
return []string{result}
}

// ...is it a range?
rm := expandArgRangeMatcher.FindStringSubmatch(arg)
if rm != nil {
lo, _ := strconv.Atoi(rm[1])
hi, _ := strconv.Atoi(rm[2])

var results []string
for i := lo; i <= hi; i++ {
results = append(results, "$e"+strconv.Itoa(i))
}
return results
}

// if it was neither, return as-is
return []string{arg}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package exec
package arguments

import (
"reflect"
Expand All @@ -21,7 +21,7 @@ func TestExpand(t *testing.T) {
// normal looking strings
args := strings.Split(tc.args, " ")
expected := strings.Split(tc.expected, " ")
actual := expand(args)
actual := Expand(args)
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("ExpandArgs(%v): expected %v, actual %v", tc.args, expected, actual)
}
Expand Down
86 changes: 3 additions & 83 deletions commands/exec/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@ import (
"fmt"
"os"
"os/exec"
"path/filepath"
"regexp"
"strconv"

"github.com/mroth/scmpuff/commands/arguments"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -64,92 +62,14 @@ Takes a list of digits (1 4 5) or numeric ranges (1-5) or even both.`,
return expandCmd
}

var expandArgDigitMatcher = regexp.MustCompile("^[0-9]{0,4}$")
var expandArgRangeMatcher = regexp.MustCompile("^([0-9]+)-([0-9]+)$")

// Process expands args and performs all substitution, then returns the argument array
func Process(args []string) []string {
var processedArgs []string
for _, arg := range expand(args) {
processed := evaluateEnvironment(arg)
for _, arg := range arguments.Expand(args) {
processed := arguments.EvaluateEnvironment(arg, expandRelative)
processedArgs = append(processedArgs, processed)
}

return processedArgs
}

// Evaluates a string of arguments and expands environment variables.
func evaluateEnvironment(arg string) string {
expandedArg := os.ExpandEnv(arg)
if expandRelative {
return convertToRelativeIfFilePath(expandedArg)
}
return expandedArg
}

// For a given arg, try to determine if it represents a file, and if so, convert
// it to a relative filepath.
//
// Otherwise (or if any error conditions occur) return it unmolested.
func convertToRelativeIfFilePath(arg string) string {
if _, err := os.Stat(arg); err == nil {
wd, err1 := os.Getwd()
relPath, err2 := filepath.Rel(wd, arg)
if err1 == nil && err2 == nil {
return relPath
}
}
return arg
}

// Expand takes the list of arguments received from the command line and expands
// them given our special case rules.
//
// It handles converting numeric file placeholders and range placeholders into
// environment variable symbolic representation,
func expand(args []string) []string {
var results []string
for _, arg := range args {
results = append(results, expandArg(arg)...)
}
return results
}

// expandArg "expands" a single argument we received on the command line.
//
// It's possible that argument represents a numeric file placeholder, in which
// case we will replace it with the syntax to represent the environment variable
// that it will be held in (e.g. "$e1").
//
// It's also possible that argument may represent a range, in which case it will
// return multiple instances of environment variable placeholders.
func expandArg(arg string) []string {

// ...is it a single digit?
dm := expandArgDigitMatcher.FindString(arg)
if dm != "" {
// dont expand if its actually a numerically named file or directory!
if _, err := os.Stat(dm); err == nil {
return []string{arg} //return as-is
}

result := "$e" + dm
return []string{result}
}

// ...is it a range?
rm := expandArgRangeMatcher.FindStringSubmatch(arg)
if rm != nil {
lo, _ := strconv.Atoi(rm[1])
hi, _ := strconv.Atoi(rm[2])

var results []string
for i := lo; i <= hi; i++ {
results = append(results, "$e"+strconv.Itoa(i))
}
return results
}

// if it was neither, return as-is
return []string{arg}
}
85 changes: 3 additions & 82 deletions commands/expand/expand.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@ package expand

import (
"fmt"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"

"github.com/mroth/scmpuff/commands/arguments"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -48,17 +46,15 @@ Takes a list of digits (1 4 5) or numeric ranges (1-5) or even both.`,
return expandCmd
}

var expandArgDigitMatcher = regexp.MustCompile("^[0-9]{0,4}$")
var expandArgRangeMatcher = regexp.MustCompile("^([0-9]+)-([0-9]+)$")
var shellEscaper = regexp.MustCompile("([\\^()\\[\\]<>' \";\\|*])")

// Process expands args and performs all substitution, etc.
//
// Ends up with a final string that is TAB delineated between arguments.
func Process(args []string) string {
var processedArgs []string
for _, arg := range expand(args) {
processed := escape(evaluateEnvironment(arg))
for _, arg := range arguments.Expand(args) {
processed := escape(arguments.EvaluateEnvironment(arg, expandRelative))

// if we still ended up with a totally blank arg, escape it here.
// we handle this as a special case rather than in expandArg because we
Expand All @@ -78,78 +74,3 @@ func escape(arg string) string {
return shellEscaper.ReplaceAllString(arg, "\\$1")
}

// Evaluates a string of arguments and expands environment variables.
func evaluateEnvironment(arg string) string {
expandedArg := os.ExpandEnv(arg)
if expandRelative {
return convertToRelativeIfFilePath(expandedArg)
}
return expandedArg
}

// For a given arg, try to determine if it represents a file, and if so, convert
// it to a relative filepath.
//
// Otherwise (or if any error conditions occur) return it unmolested.
func convertToRelativeIfFilePath(arg string) string {
if _, err := os.Stat(arg); err == nil {
wd, err1 := os.Getwd()
relPath, err2 := filepath.Rel(wd, arg)
if err1 == nil && err2 == nil {
return relPath
}
}
return arg
}

// Expand takes the list of arguments received from the command line and expands
// them given our special case rules.
//
// It handles converting numeric file placeholders and range placeholders into
// environment variable symbolic representation,
func expand(args []string) []string {
var results []string
for _, arg := range args {
results = append(results, expandArg(arg)...)
}
return results
}

// expandArg "expands" a single argument we received on the command line.
//
// It's possible that argument represents a numeric file placeholder, in which
// case we will replace it with the syntax to represent the environment variable
// that it will be held in (e.g. "$e1").
//
// It's also possible that argument may represent a range, in which case it will
// return multiple instances of environment variable placeholders.
func expandArg(arg string) []string {

// ...is it a single digit?
dm := expandArgDigitMatcher.FindString(arg)
if dm != "" {
// dont expand if its actually a numerically named file or directory!
if _, err := os.Stat(dm); err == nil {
return []string{arg} //return as-is
}

result := "$e" + dm
return []string{result}
}

// ...is it a range?
rm := expandArgRangeMatcher.FindStringSubmatch(arg)
if rm != nil {
lo, _ := strconv.Atoi(rm[1])
hi, _ := strconv.Atoi(rm[2])

var results []string
for i := lo; i <= hi; i++ {
results = append(results, "$e"+strconv.Itoa(i))
}
return results
}

// if it was neither, return as-is
return []string{arg}
}
43 changes: 0 additions & 43 deletions commands/expand/expand_test.go
Original file line number Diff line number Diff line change
@@ -1,52 +1,9 @@
package expand

import (
"reflect"
"strings"
"testing"
)

// Expansion of multiple args at the same time
var testExpandCases = []struct {
args, expected string
}{
{"1 3 7", "$e1 $e3 $e7"},
{"1-3 6", "$e1 $e2 $e3 $e6"},
{"seven 2-5 1", "seven $e2 $e3 $e4 $e5 $e1"},
}

func TestExpand(t *testing.T) {
for _, tc := range testExpandCases {
// split here to emulate what Cobra will pass us but still write tests with
// normal looking strings
args := strings.Split(tc.args, " ")
expected := strings.Split(tc.expected, " ")
actual := expand(args)
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("ExpandArgs(%v): expected %v, actual %v", tc.args, expected, actual)
}
}
}

// Expansion of a single arg, which might still be a range
var testExpandArgCases = []struct {
arg string
expected []string
}{
{"1", []string{"$e1"}}, // single digit
{"1-3", []string{"$e1", "$e2", "$e3"}}, // range
{"seven", []string{"seven"}}, // no moleste
}

func TestExpandArg(t *testing.T) {
for _, tc := range testExpandArgCases {
actual := expandArg(tc.arg)
if !reflect.DeepEqual(actual, tc.expected) {
t.Fatalf("ExpandArg(%v): expected %v, actual %v", tc.arg, tc.expected, actual)
}
}
}

// Process expansion with an empty arg should be quoted so it doesnt get lost,
// special case handling that occurs in final step (to avoid escaping).
func TestProcessEmpty(t *testing.T) {
Expand Down

0 comments on commit 38698d3

Please sign in to comment.