Skip to content

Commit

Permalink
Add interactive option when only a path is provided
Browse files Browse the repository at this point in the history
  • Loading branch information
tjarratt authored and Tim Jarratt committed Oct 20, 2015
1 parent 5f6e774 commit 5712b80
Show file tree
Hide file tree
Showing 11 changed files with 489 additions and 27 deletions.
77 changes: 69 additions & 8 deletions arguments/parser.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package arguments

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

"github.com/maxbrunsfeld/counterfeiter/locator"
"github.com/maxbrunsfeld/counterfeiter/terminal"
)

type ArgumentParser interface {
Expand All @@ -15,32 +20,78 @@ func NewArgumentParser(
currentWorkingDir CurrentWorkingDir,
symlinkEvaler SymlinkEvaler,
fileStatReader FileStatReader,
ui terminal.UI,
interfaceLocator locator.InterfaceLocator,
) ArgumentParser {
return argumentParser{
return &argumentParser{
ui: ui,
failHandler: failHandler,
currentWorkingDir: currentWorkingDir,
symlinkEvaler: symlinkEvaler,
fileStatReader: fileStatReader,
interfaceLocator: interfaceLocator,
}
}

func (argParser argumentParser) ParseArguments(args ...string) ParsedArguments {
func (argParser *argumentParser) ParseArguments(args ...string) ParsedArguments {
sourcePackageDir := argParser.getSourceDir(args[0])
fakeImplName := getFakeName(args[1], *fakeNameFlag)
outputPath := argParser.getOutputPath(sourcePackageDir, fakeImplName, *outputPathFlag)

var interfaceName string

if len(args) > 1 {
interfaceName = args[1]
} else {
interfaceName = argParser.PromptUserForInterfaceName(sourcePackageDir)
}

fakeImplName := getFakeName(interfaceName, *fakeNameFlag)

outputPath := argParser.getOutputPath(
sourcePackageDir,
fakeImplName,
*outputPathFlag,
)

return ParsedArguments{
SourcePackageDir: sourcePackageDir,
OutputPath: outputPath,

InterfaceName: args[1],
InterfaceName: interfaceName,
FakeImplName: fakeImplName,

PrintToStdOut: len(args) == 3 && args[2] == "-",
PrintToStdOut: any(args, "-"),
}
}

func (parser *argumentParser) PromptUserForInterfaceName(filepath string) string {
parser.ui.WriteLine("Which interface to counterfeit?")

interfacesInPackage := parser.interfaceLocator.GetInterfacesFromFilePath(filepath)

for i, interfaceName := range interfacesInPackage {
parser.ui.WriteLine(fmt.Sprintf("%d. %s", i+1, interfaceName))
}
parser.ui.WriteLine("")

response := parser.ui.ReadLineFromStdin()
parsedResponse, err := strconv.ParseInt(response, 10, 64)
if err != nil {
parser.failHandler("Unknown option '%s'", response)
return ""
}

option := int(parsedResponse - 1)
if option < 0 || option >= len(interfacesInPackage) {
parser.failHandler("Unknown option '%s'", response)
return ""
}

return interfacesInPackage[option]
}

type argumentParser struct {
ui terminal.UI
interfaceLocator locator.InterfaceLocator
failHandler FailHandler
currentWorkingDir CurrentWorkingDir
symlinkEvaler SymlinkEvaler
Expand All @@ -67,7 +118,7 @@ func getFakeName(interfaceName, arg string) string {

var camelRegexp = regexp.MustCompile("([a-z])([A-Z])")

func (argParser argumentParser) getOutputPath(sourceDir, fakeName, arg string) string {
func (argParser *argumentParser) getOutputPath(sourceDir, fakeName, arg string) string {
if arg == "" {
snakeCaseName := strings.ToLower(camelRegexp.ReplaceAllString(fakeName, "${1}_${2}"))
return filepath.Join(sourceDir, "fakes", snakeCaseName+".go")
Expand All @@ -79,7 +130,7 @@ func (argParser argumentParser) getOutputPath(sourceDir, fakeName, arg string) s
}
}

func (argParser argumentParser) getSourceDir(arg string) string {
func (argParser *argumentParser) getSourceDir(arg string) string {
if !filepath.IsAbs(arg) {
arg = filepath.Join(argParser.currentWorkingDir(), arg)
}
Expand All @@ -97,4 +148,14 @@ func (argParser argumentParser) getSourceDir(arg string) string {
}
}

func any(slice []string, needle string) bool {
for _, str := range slice {
if str == needle {
return true
}
}

return false
}

type FailHandler func(string, ...interface{})
72 changes: 67 additions & 5 deletions arguments/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import (
"testing"
"time"

locatorFakes "github.com/maxbrunsfeld/counterfeiter/locator/fakes"
terminalFakes "github.com/maxbrunsfeld/counterfeiter/terminal/fakes"

. "github.com/maxbrunsfeld/counterfeiter/arguments"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
Expand All @@ -22,17 +25,42 @@ var _ = Describe("parsing arguments", func() {
var symlinkEvaler SymlinkEvaler
var fileStatReader FileStatReader

var ui *terminalFakes.FakeUI
var interfaceLocator *locatorFakes.FakeInterfaceLocator

var failWasCalled bool
// fake UI helper

var fakeUIBuffer = func() string {
var output string
for i := 0; i < ui.WriteLineCallCount(); i++ {
output = output + ui.WriteLineArgsForCall(i)
}
return output
}

JustBeforeEach(func() {
subject = NewArgumentParser(fail, cwd, symlinkEvaler, fileStatReader)
subject = NewArgumentParser(
fail,
cwd,
symlinkEvaler,
fileStatReader,
ui,
interfaceLocator,
)
parsedArgs = subject.ParseArguments(args...)
})

BeforeEach(func() {
fail = func(_ string, _ ...interface{}) {}
failWasCalled = false
fail = func(_ string, _ ...interface{}) { failWasCalled = true }
cwd = func() string {
return "/home/test-user/workspace"
}

ui = new(terminalFakes.FakeUI)
interfaceLocator = new(locatorFakes.FakeInterfaceLocator)

symlinkEvaler = func(input string) (string, error) {
return input, nil
}
Expand All @@ -41,6 +69,43 @@ var _ = Describe("parsing arguments", func() {
}
})

Describe("when a single argument is provided", func() {
BeforeEach(func() {
args = []string{"some/path"}

interfaceLocator.GetInterfacesFromFilePathReturns([]string{"Foo", "Bar"})
ui.ReadLineFromStdinReturns("1")
})

It("prompts the user for which interface they want", func() {
Expect(fakeUIBuffer()).To(ContainSubstring("Which interface to counterfeit?"))
})

It("shows the user each interface found in the given filepath", func() {
Expect(fakeUIBuffer()).To(ContainSubstring("1. Foo"))
Expect(fakeUIBuffer()).To(ContainSubstring("2. Bar"))
})

It("asks its interface locator for valid interfaces", func() {
Expect(interfaceLocator.GetInterfacesFromFilePathCallCount()).To(Equal(1))
Expect(interfaceLocator.GetInterfacesFromFilePathArgsForCall(0)).To(Equal("/home/test-user/workspace/some/path"))
})

It("yields the interface name the user chose", func() {
Expect(parsedArgs.InterfaceName).To(Equal("Foo"))
})

Describe("when the user types an invalid option", func() {
BeforeEach(func() {
ui.ReadLineFromStdinReturns("garbage")
})

It("invokes its fail handler", func() {
Expect(failWasCalled).To(BeTrue())
})
})
})

Describe("when two arguments are provided", func() {
BeforeEach(func() {
args = []string{"some/path", "MySpecialInterface"}
Expand Down Expand Up @@ -86,10 +151,7 @@ var _ = Describe("parsing arguments", func() {
})

Context("when the file stat cannot be read", func() {
var failWasCalled bool

BeforeEach(func() {
fail = func(_ string, _ ...interface{}) { failWasCalled = true }
fileStatReader = func(_ string) (os.FileInfo, error) {
return fakeFileInfo("", false), errors.New("submarine-shoutout")
}
Expand Down
11 changes: 11 additions & 0 deletions fixtures/multiple_interfaces.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package fixtures

type FirstInterface interface {
DoThings()
}

type SecondInterface interface {
EmbeddedMethod() string
}

type unexportedInterface interface{}
39 changes: 38 additions & 1 deletion integration/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,28 @@ var _ = Describe("The counterfeiter CLI", func() {

BeforeEach(func() {
pathToCLI = tmpPath("counterfeiter")
copyIn("something.go", pathToCLI)
})

Describe("when given a single argument", func() {
It("interactively prompts the user for the interface they want to counterfeit", func() {
reader, writer := io.Pipe()

copyIn("multiple_interfaces.go", pathToCLI)
session := startCounterfeiterWithStdinPipe(pathToCLI, reader, "multiple_interfaces.go")

writer.Write([]byte("1\n"))
writer.Close()

Eventually(session).Should(gexec.Exit(0))
Expect(string(session.Out.Contents())).To(ContainSubstring("Wrote `FakeFirstInterface`"))
})
})

Describe("when given two arguments", func() {
BeforeEach(func() {
copyIn("something.go", pathToCLI)
})

It("writes a fake for the given interface from the provided file", func() {
session := startCounterfeiter(pathToCLI, "something.go", "Something")

Expand All @@ -33,6 +51,10 @@ var _ = Describe("The counterfeiter CLI", func() {
})

Describe("when provided three arguments", func() {
BeforeEach(func() {
copyIn("something.go", pathToCLI)
})

It("writes the fake to stdout", func() {
session := startCounterfeiter(pathToCLI, "something.go", "Something", "-")

Expand Down Expand Up @@ -87,6 +109,21 @@ func startCounterfeiter(workingDir string, args ...string) *gexec.Session {
return session
}

func startCounterfeiterWithStdinPipe(workingDir string, stdin io.Reader, args ...string) *gexec.Session {
fakeGoPathDir := filepath.Dir(filepath.Dir(workingDir))
absPath, _ := filepath.Abs(fakeGoPathDir)
absPathWithSymlinks, _ := filepath.EvalSymlinks(absPath)

cmd := exec.Command(pathToCounterfeiter, args...)
cmd.Stdin = stdin
cmd.Dir = workingDir
cmd.Env = []string{"GOPATH=" + absPathWithSymlinks}

session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter)
Expect(err).ToNot(HaveOccurred())
return session
}

// gexec setup

var tmpDir string
Expand Down
4 changes: 2 additions & 2 deletions integration/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"testing"
)

func TestCounterfeiterCLI(t *testing.T) {
func TestCounterfeiterCLIIntegration(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Counterfeiter CLI Suite")
RunSpecs(t, "Counterfeiter CLI Integration Suite")
}
53 changes: 53 additions & 0 deletions locator/fakes/fake_interface_locator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// This file was generated by counterfeiter
package fakes

import (
"sync"

"github.com/maxbrunsfeld/counterfeiter/locator"
)

type FakeInterfaceLocator struct {
GetInterfacesFromFilePathStub func(string) []string
getInterfacesFromFilePathMutex sync.RWMutex
getInterfacesFromFilePathArgsForCall []struct {
arg1 string
}
getInterfacesFromFilePathReturns struct {
result1 []string
}
}

func (fake *FakeInterfaceLocator) GetInterfacesFromFilePath(arg1 string) []string {
fake.getInterfacesFromFilePathMutex.Lock()
fake.getInterfacesFromFilePathArgsForCall = append(fake.getInterfacesFromFilePathArgsForCall, struct {
arg1 string
}{arg1})
fake.getInterfacesFromFilePathMutex.Unlock()
if fake.GetInterfacesFromFilePathStub != nil {
return fake.GetInterfacesFromFilePathStub(arg1)
} else {
return fake.getInterfacesFromFilePathReturns.result1
}
}

func (fake *FakeInterfaceLocator) GetInterfacesFromFilePathCallCount() int {
fake.getInterfacesFromFilePathMutex.RLock()
defer fake.getInterfacesFromFilePathMutex.RUnlock()
return len(fake.getInterfacesFromFilePathArgsForCall)
}

func (fake *FakeInterfaceLocator) GetInterfacesFromFilePathArgsForCall(i int) string {
fake.getInterfacesFromFilePathMutex.RLock()
defer fake.getInterfacesFromFilePathMutex.RUnlock()
return fake.getInterfacesFromFilePathArgsForCall[i].arg1
}

func (fake *FakeInterfaceLocator) GetInterfacesFromFilePathReturns(result1 []string) {
fake.GetInterfacesFromFilePathStub = nil
fake.getInterfacesFromFilePathReturns = struct {
result1 []string
}{result1}
}

var _ locator.InterfaceLocator = new(FakeInterfaceLocator)
Loading

0 comments on commit 5712b80

Please sign in to comment.