Skip to content
This repository has been archived by the owner on Jul 22, 2024. It is now read-only.

Commit

Permalink
Add Ui interfaces
Browse files Browse the repository at this point in the history
  • Loading branch information
mitchellh committed Nov 5, 2013
1 parent 0f047db commit ce8d361
Show file tree
Hide file tree
Showing 5 changed files with 214 additions and 2 deletions.
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
# Go CLI Library

cli is a library for implementing sub-command based command-line interfaces
in Go. cli is the library that powers the CLI for
cli is a library for implementing powerful command-line interfaces in Go.
cli is the library that powers the CLI for
[Packer](https://github.com/mitchellh/packer) and
[Serf](https://github.com/hashicorp/serf).

## Features

* Easy sub-command based CLIs: `cli foo`, `cli bar`, etc.

* Automatic help generation for listing subcommands

* Automatic help flag recognition of `-h`, `--help`, etc.

* Use of Go interfaces/types makes augmenting various parts of the library a
piece of cake.
76 changes: 76 additions & 0 deletions ui.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package cli

import (
"fmt"
"io"
"log"
)

// Ui is an interface for interacting with the terminal, or "interface"
// of a CLI. This abstraction doesn't have to be used, but helps provide
// a simple, layerable way to manage user interactions.
type Ui interface {
// Output is called for normal standard output.
Output(string)

// Info is called for information related to the previous output.
// In general this may be the exact same as Output, but this gives
// Ui implementors some flexibility with output formats.
Info(string)

// Error is used for any error messages that might appear on standard
// error.
Error(string)
}

// BasicUi is an implementation of Ui that just outputs to the given
// writer.
type BasicUi struct {
Writer io.Writer
}

func (u *BasicUi) Error(message string) {
fmt.Fprint(u.Writer, message)
fmt.Fprint(u.Writer, "\n")
}

func (u *BasicUi) Info(message string) {
u.Output(message)
}

func (u *BasicUi) Output(message string) {
fmt.Fprint(u.Writer, message)
fmt.Fprint(u.Writer, "\n")
}

// PrefixedUi is an implementation of Ui that prefixes messages.
type PrefixedUi struct {
OutputPrefix string
InfoPrefix string
ErrorPrefix string
Ui Ui
}

func (u *PrefixedUi) Error(message string) {
if message != "" {
message = fmt.Sprintf("%s%s", u.ErrorPrefix, message)
}

u.Ui.Error(message)
}

func (u *PrefixedUi) Info(message string) {
if message != "" {
message = fmt.Sprintf("%s%s", u.InfoPrefix, message)
}

u.Ui.Info(message)
}

func (u *PrefixedUi) Output(message string) {
if message != "" {
message = fmt.Sprintf("%s%s", u.OutputPrefix, message)
}

u.Ui.Output(message)
}
39 changes: 39 additions & 0 deletions ui_mock.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package cli

import (
"bytes"
"fmt"
"sync"
)

// MockUi is a mock UI that is used for tests and is exported publicly for
// use in external tests if needed as well.
type MockUi struct {
ErrorWriter *bytes.Buffer
OutputWriter *bytes.Buffer

once sync.Once
}

func (u *MockUi) Error(message string) {
u.once.Do(u.init)

fmt.Fprint(u.ErrorWriter, message)
fmt.Fprint(u.ErrorWriter, "\n")
}

func (u *MockUi) Info(message string) {
u.Output(message)
}

func (u *MockUi) Output(message string) {
u.once.Do(u.init)

fmt.Fprint(u.OutputWriter, message)
fmt.Fprint(u.OutputWriter, "\n")
}

func (u *MockUi) init() {
u.ErrorWriter = new(bytes.Buffer)
u.OutputWriter = new(bytes.Buffer)
}
9 changes: 9 additions & 0 deletions ui_mock_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package cli

import (
"testing"
)

func TestMockUi_implements(t *testing.T) {
var _ Ui = new(MockUi)
}
77 changes: 77 additions & 0 deletions ui_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package cli

import (
"bytes"
"testing"
)

func TestBasicUi_implements(t *testing.T) {
var _ Ui = new(BasicUi)
}

func TestBasicUi_Error(t *testing.T) {
writer := new(bytes.Buffer)
ui := &BasicUi{Writer: writer}
ui.Error("HELLO")

if writer.String() != "HELLO\n" {
t.Fatalf("bad: %s", writer.String())
}
}

func TestBasicUi_Output(t *testing.T) {
writer := new(bytes.Buffer)
ui := &BasicUi{Writer: writer}
ui.Output("HELLO")

if writer.String() != "HELLO\n" {
t.Fatalf("bad: %s", writer.String())
}
}

func TestPrefixedUi_implements(t *testing.T) {
var raw interface{}
raw = &PrefixedUi{}
if _, ok := raw.(Ui); !ok {
t.Fatalf("should be a Ui")
}
}

func TestPrefixedUiError(t *testing.T) {
ui := new(MockUi)
p := &PrefixedUi{
ErrorPrefix: "foo",
Ui: ui,
}

p.Error("bar")
if ui.ErrorWriter.String() != "foobar\n" {
t.Fatalf("bad: %s", ui.ErrorWriter.String())
}
}

func TestPrefixedUiInfo(t *testing.T) {
ui := new(MockUi)
p := &PrefixedUi{
InfoPrefix: "foo",
Ui: ui,
}

p.Info("bar")
if ui.OutputWriter.String() != "foobar\n" {
t.Fatalf("bad: %s", ui.OutputWriter.String())
}
}

func TestPrefixedUiOutput(t *testing.T) {
ui := new(MockUi)
p := &PrefixedUi{
OutputPrefix: "foo",
Ui: ui,
}

p.Output("bar")
if ui.OutputWriter.String() != "foobar\n" {
t.Fatalf("bad: %s", ui.OutputWriter.String())
}
}

0 comments on commit ce8d361

Please sign in to comment.