Download - Building Awesome CLI apps in Go

Transcript
Page 13: Building Awesome CLI apps in Go

– Rob Pike

“Many UNIX programs do quite trivial things in isolation, but, combined with other programs, become general and

useful tools.” 13

Page 14: Building Awesome CLI apps in Go

– Doug McIlroy

“The manual page, which really used to be a manual page, is now a small volume,

with a thousand options” 14

Page 60: Building Awesome CLI apps in Go

PREP PHRASE = ADVERB•consists of a preposition and its

object

•acts as an adverb

•"Speaking at OSCON"60

Page 64: Building Awesome CLI apps in Go

› tar

First option must be a mode specifier:

-c Create -r Add/Replace -t List -u Update -x Extract

FLAG = ACTION

64

Page 68: Building Awesome CLI apps in Go

Create: tar -c [options]

-z, -j, -J, --lzma Compress archive with gzip/bzip2/xz/lzma

FLAGS HAVING SUB FLAGS

68

Page 81: Building Awesome CLI apps in Go

SUB COMMANDS•CLI apps do multiple things

•Apps are groups of commands

•(sub) Commands have flags & args

•All rules still apply81

Page 89: Building Awesome CLI apps in Go

GOPATH AND SETUP•You WILL store your code in a GitHub

user folder (example: $GOPATH/src/github.com/username/helloworld)

•This was a pain in the ass

•I was comfortable with /code /dev/ or /projects 89

Page 90: Building Awesome CLI apps in Go

GOPATH AND SETUP

•By Storing code on github, I was able to share my many problems

•Going from “I’m working on this” to “let’s collaborate”

90

Page 91: Building Awesome CLI apps in Go

GOPATH•The Go toolset uses an environment variable

called GOPATH to find Go source code.

•You can set GOPATH to anything you want, but things will be easier if it’s set in your home directory.

91

Page 92: Building Awesome CLI apps in Go

Windows:

c:\ setx GOPATH %USERPROFILE%

OS X:

❯ echo 'export GOPATH=$HOME\n' >> ~/.bash_profile

Close the terminal, reopen it, and type the following:

❯ echo $GOPATH

GO PATH

92

Page 94: Building Awesome CLI apps in Go

EDITORS•No IDE needed - any text editor will do.

•helpful features like autocomplete and format & import on-save

•If you’re not sure what to use, I recommend Atom — It’s free, cross-platform, and easy to install. 94

Page 95: Building Awesome CLI apps in Go

PLUGINS•Atom: https://github.com/joefitzgerald/go-plus

•Vim: https://github.com/fatih/vim-go

•included in http://vim.spf13.com/

•IntelliJ: https://plugins.jetbrains.com/plugin/?id=5047

•Full list: https://github.com/golang/go/wiki/IDEsAndTextEditorPlugins 95

Page 96: Building Awesome CLI apps in Go

YOU DON’T NEED A FRAMEWORK!

•The standard library has tons!

•Web server

•Templates

•Database

•etc.. 96

Page 98: Building Awesome CLI apps in Go

STANDARD LIBRARYLet’s be honest, you don’t want to write everything from scratch so most programming depends on your ability to interface with existing libraries. i.e. packages. Here are a few core packages you should know about;

•Strings

•Input/Output (io/ioutil)

•Errors

•fmt

98

• Containers and Sort

• path/filepath

• HTTP (net/http)

• math/rand

Page 112: Building Awesome CLI apps in Go

KEYWORDS•break

•default

•func

•interface

•select

•case

•defer

•go 112

•map

•struct

•chan

•else

•goto

•package

•switch

•const

•fallthrough

•if

•range

•type

•continue

•for

•import

•return

•var

Page 114: Building Awesome CLI apps in Go

BRACES

Compile Eror

114

Compile Error

/tmp/sandbox826898458/main.go:6: syntax error: unexpected semicolon or newline before {

Page 117: Building Awesome CLI apps in Go

VARIABLES

117

Error

/tmp/sandbox473116179/main.go:6: one declared and not used /tmp/sandbox473116179/main.go:7: two declared and not used /tmp/sandbox473116179/main.go:8: three declared and not used

Page 124: Building Awesome CLI apps in Go

NUMBERS

•Integers (numbers without a decimal)

•Floating-Point Numbers (numbers that contain a decimal)

• int, int8-64, uint, uint8-64, float32,64124

Page 125: Building Awesome CLI apps in Go

STRINGS•A string is a sequence of

characters

•Go strings are immutable

•Go strings are made up of individual bytes (Usually one for each character)

125

Page 128: Building Awesome CLI apps in Go

ARRAY

•An array is an ordered sequence of elements of a single type

•Fixed length

•Arrays are indexed starting from 0128

Page 130: Building Awesome CLI apps in Go

MAP•Unordered collection of key-value

pairs

•Similar to associative arrays, hash tables, and dictionaries

•Dynamic Length130

Page 132: Building Awesome CLI apps in Go

FUNCTION•Function is a type

•First class citizen in Go

•Can have multiple input values

•Can have multiple return values132

Page 133: Building Awesome CLI apps in Go

POINTERS•Reference a location in memory

where a value is stored

•Represented using '*'

•Memory location referenced using '&'133

Page 137: Building Awesome CLI apps in Go

GO HELPThe Go toolset has many different commands and subcommands. You can pull up a list by typing:

go help

You now have everything you need to get started

137

Page 139: Building Awesome CLI apps in Go

GO TESTWriting code isn’t easy and humans make mistakes so testing is really important, luckily, Go includes a program that makes writing tests easier:

go test

Fun Fact: The Go compiler knows to ignore code in files ending with _test.go

139

Page 145: Building Awesome CLI apps in Go

RESOURCES•Getting Started, official golang page — https://golang.org/doc/

•Parse’s move from Ruby to Golang — http://blog.parse.com/learn/how-we-moved-our-api-from-ruby-to-go-and-saved-our-sanity/

•Tutorial — Creating a Wiki — https://golang.org/doc/articles/wiki/ (this was the first big tutorial that got me to build something useful, take this one slow to get a grasps on its concepts)

•Golang — Concurrency is not Parallelism (Rob Pike), this video I found super informative about go’s concurency and got me excited to keep coding in Golang — https://youtu.be/cN_DpYBzKso

145

Page 156: Building Awesome CLI apps in Go

FEATURES•Add Todo

•List Todos

•Mark "done"

•Search/Filter

•Priorities

•Archive

•Edit

•Create Dates

•Due Dates

•Tags

•Projects

156

Page 168: Building Awesome CLI apps in Go

CONSIDERATIONS•What priority system to use?

•Numeric

•Alpha

•High, Middle, Low

•What's the default priority? 168

Page 174: Building Awesome CLI apps in Go

› tri list

(1) Add Listing

Consider usage behaviors

Add Multi Todo Support

Add Priorities

LISTING OUTPUT

174

Page 175: Building Awesome CLI apps in Go

› tri list

(1) Add Listing

Add Priorities

Add Multi Todo Support

Consider usage behaviors

LISTING OUTPUT

175

Page 176: Building Awesome CLI apps in Go

› tri list

(H) Add Listing

Consider usage behaviors

Add Priorities

(L) Add Multi Todo Support

LISTING OUTPUT

176

Page 177: Building Awesome CLI apps in Go

› tri list

(H) Add Listing

Consider usage behaviors

Add Priorities

(L) Add Multi Todo Support

LISTING OUTPUT

177

Page 178: Building Awesome CLI apps in Go

› tri list

1. (H) Add Listing

2. Consider usage behaviors

3. Add Priorities

4. (L) Add Multi Todo Support

LISTING OUTPUT

178

Page 182: Building Awesome CLI apps in Go

› tri list -p1

› tri list --due June

› tri list --created 12/15

› tri list --done -p1

FILTER BY PROPERTY

182

Page 187: Building Awesome CLI apps in Go

› tri list

1. (H) Add Listing

2. Consider usage behaviors

3. Add Priorities

4. (L) Add Multi Todo Support

LISTING OUTPUT

187

Page 191: Building Awesome CLI apps in Go

› tri edit 1 -p2

› tri list -p2

› tri edit 3 --created 12/15

› tri list --created 12/15

CONSISTENCY

191

Page 195: Building Awesome CLI apps in Go

•A CLI Command Framework

•A tool to generate CLI apps & commands

•Powers Kubernetes, Dropbox, Git Lfs, CoreOS, Docker, Delve ...

195

Page 197: Building Awesome CLI apps in Go

› cobra

Cobra is a Cli library for Go that empowers applications. This application is a tool to generate the needed files to quickly create a Cobra application.

Usage:

cobra [command]

Available Commands:

add Add a command to a Cobra Application

COBRA

197

Page 199: Building Awesome CLI apps in Go

› cobra init \ github.com/<handle>/tri \

-a "<Your Name>"

COBRA INIT

199

Replace with your url, project name & Name

Page 202: Building Awesome CLI apps in Go

› tree

.

├── LICENSE

├── cmd

│   └── root.go

└── main.go

LOOK AT YOUR PROJECT

202

Page 203: Building Awesome CLI apps in Go

› go build

› ./tri

A longer description that spans multiple lines and likely contains examples

and usage of using your application. For example...

BUILD & RUN IT

203

Page 213: Building Awesome CLI apps in Go

package cmdimport ( "fmt" "os" "github.com/spf13/cobra" "github.com/spf13/viper")

CMD/ROOT.GO

213

Notice package name

Matches Dir

Page 214: Building Awesome CLI apps in Go

// This represents the base command when called without any subcommandsvar RootCmd = &cobra.Command{ Use: "tri", Short: "A brief description of your application", Long: `A longer description that spans multiple lines and likely ... application.`,}

CMD/ROOT.GO

214

Page 215: Building Awesome CLI apps in Go

// This represents the base command when called without any subcommandsvar RootCmd = &cobra.Command{ Use: "tri", Short: "Tri is a todo application", Long: `Tri will help you get more done in less time.It's designed to be as simple as possible to help you accomplish your goals.`,

}

CMD/ROOT.GO

215

Page 222: Building Awesome CLI apps in Go

func Execute() { if err := RootCmd.Execute(); err != nil { fmt.Println(err) os.Exit(-1) }}

CMD/ROOT.GO

222

Package level variable

Page 223: Building Awesome CLI apps in Go

>go run main.go

Tri will help you get more done in less time.

It's designed to be as simple as possible to help you accomplish your goals.

RUN IT

223

Page 231: Building Awesome CLI apps in Go

var addCmd = &cobra.Command{ Use: "add", Short: "A brief description of your command", Long: `A longer description that spans multiple lines and likely...`, Run: func(cmd *cobra.Command, args []string) { // TODO: Work your own magic here fmt.Println("add called") },}

CMD/ADD.GO

231

Page 232: Building Awesome CLI apps in Go

var addCmd = &cobra.Command{ Use: "add", Short: "Add a new todo", Long: `Add will create a new todo item to the list`, Run: func(cmd *cobra.Command, args []string) { // TODO: Work your own magic here fmt.Println("add called") },}

CMD/ADD.GO

232

Page 234: Building Awesome CLI apps in Go

var addCmd = &cobra.Command{ Use: "add", Short: "Add a new todo", Long: `Add will create a new todo item to the list`, Run: addRun,}

CMD/ADD.GO

234

Page 236: Building Awesome CLI apps in Go

FOR x, Y := RANGE•Provides a way to iterate over an array,

slice, string, map, or channel.

•Like Foreach or Each in other languages

•x is index/key, y is value

•_ allows you to ignore naming variables236

Page 238: Building Awesome CLI apps in Go

INIT()•Special function

•Called after package variable declarations

•Called prior to main.main()

•Each package may have multiple init()

•init() order un-guaranteed238

Page 244: Building Awesome CLI apps in Go

NAMED TYPES•Can be any known type (struct,

string, int, slice, a new type you’ve declared, etc)

•Methods can be declared on it

•Not an alias - Explicit type244

Page 246: Building Awesome CLI apps in Go

import ( "fmt" "github.com/spf13/cobra" "github.com/<yourname>/tri/todo")

CMD/ADD.GO

246

Replace with yourS

Page 247: Building Awesome CLI apps in Go

func addRun(...) { items := []todo.Item{} for _, x := range args { items = append(items, todo.Item{Text:x}) } fmt.Println(items)}

CMD/ADD.GO

247

Page 248: Building Awesome CLI apps in Go

func addRun(...) { items := []todo.Item{} for _, x := range args { items = append(items, todo.Item{Text:x}) } fmt.Println(items)}

CMD/ADD.GO

248

Page 250: Building Awesome CLI apps in Go

func addRun(...) { items := []todo.Item{} for _, x := range args { items = append(items, todo.Item{Text:x}) } fmt.Println(items)}

CMD/ADD.GO

250

Page 251: Building Awesome CLI apps in Go

func addRun(...) { items := []todo.Item{} for _, x := range args { items = append(items, todo.Item{Text:x}) } fmt.Println(items)}

CMD/ADD.GO

251

Page 253: Building Awesome CLI apps in Go

func addRun(...) { var items = []todo.Item{} for _,x := range args { items = append(items, todo.Item{Text:x}) } fmt.Printf("%#v\n", items)}

CMD/ADD.GO

253

Page 254: Building Awesome CLI apps in Go

>go run main.go add \ "one two" three

[]todo.Item{todo.Item{Text:"one two"}, todo.Item{Text:"three"}}

GO RUN

254

Page 260: Building Awesome CLI apps in Go

func SaveItems(filename string, items []Item) error { b, err := json.Marshal(items) if err != nil { return err } fmt.Println(string(b)) return nil}

TODO/TODO.GO

260

Page 261: Building Awesome CLI apps in Go

func SaveItems(filename string, items []Item) error { b, err := json.Marshal(items) if err != nil { return err } fmt.Println(string(b)) return nil}

TODO/TODO.GO

261

Page 262: Building Awesome CLI apps in Go

func SaveItems(filename string, items []Item) error { b, err := json.Marshal(items) if err != nil { return err } fmt.Println(string(b)) return nil}

TODO/TODO.GO

262

Page 263: Building Awesome CLI apps in Go

ERROR HANDLING•Errors are not exceptional, they

are just values

•No exceptions in Go

•Errors should be handled when they occur

263

Page 264: Building Awesome CLI apps in Go

( DON’T) PANIC

•Only use when:

1. You want to shut down your program AND

2. You need a stack trace

•Packages should never call Panic (only applications)

•Do not use as pseudo-exception handling

•Only recover a panic if you know you can properly recover from it 264

Page 265: Building Awesome CLI apps in Go

func SaveItems(filename string, items []Item) error { b, err := json.Marshal(items) if err != nil { return err } fmt.Println(string(b)) return nil}

TODO/TODO.GO

265

Page 266: Building Awesome CLI apps in Go

func addRun(cmd *cobra.Command, args []string) { var items = []todo.Item{} for _, x := range args { items = append(items, todo.Item{Text: x}) } todo.SaveItems("x", items)}

CMD/ADD.GO

266

Page 270: Building Awesome CLI apps in Go

func SaveItems(filename string, items []Item) error {... err = ioutil.WriteFile(filename, b, 0644) if err != nil { return err } return nil}

TODO/TODO.GO

270

Page 271: Building Awesome CLI apps in Go

func addRun(cmd *cobra.Command, args []string) { var items = []todo.Item{} for _, x := range args { items = append(items, todo.Item{Text: x}) }

err := todo.SaveItems("/Users/spf13/.tridos.json", items); if err != nil { fmt.Errorf("%v", err) }}

CMD/ADD.GO

271

Page 272: Building Awesome CLI apps in Go

› go run main.go add "one two" three

› cat ~/.tridos.json

[{"Text":"one two"},{"Text":"three"}]

CHECK FILE CREATION

272

Page 276: Building Awesome CLI apps in Go

func ReadItems(filename string) ([]Item, error) { b, err := ioutil.ReadFile(filename) if err != nil { return []Item{}, err }

...

TODO/TODO.GO

276

Page 277: Building Awesome CLI apps in Go

func ReadItems(filename string) ([]Item, error) { b, err := ioutil.ReadFile(filename) if err != nil { return []Item{}, err }

...

TODO/TODO.GO

277

Page 278: Building Awesome CLI apps in Go

func ReadItems(filename string) ([]Item, error) {

...var items []Itemif err := json.Unmarshal(b, &items); err != nil { return []Item{}, err}return items, nil

}

TODO/TODO.GO

278

Page 279: Building Awesome CLI apps in Go

func ReadItems(filename string) ([]Item, error) {

...var items []Itemif err := json.Unmarshal(b, &items); err != nil { return []Item{}, err}return items, nil

}

TODO/TODO.GO

279

Page 280: Building Awesome CLI apps in Go

func ReadItems(filename string) ([]Item, error) {

...var items []Itemif err := json.Unmarshal(b, &items); err != nil { return []Item{}, err}return items, nil

}

TODO/TODO.GO

280

Page 281: Building Awesome CLI apps in Go

func ReadItems(filename string) ([]Item, error) {

...var items []Itemif err := json.Unmarshal(b, &items); err != nil { return []Item{}, err}return items, nil

}

TODO/TODO.GO

281

Page 282: Building Awesome CLI apps in Go

func ReadItems(filename string) ([]Item, error) {

...var items []Itemif err := json.Unmarshal(b, &items); err != nil { return []Item{}, err}return items, nil

}

TODO/TODO.GO

282

Page 285: Building Awesome CLI apps in Go

Run: func(cmd *cobra.Command, args []string) { items, err := todo.ReadItems("/Users/spf13/.tridos.json")

if err != nil { log.Printf("%v", err) } fmt.Println(items)},

CMD/LIST.GO

285

Page 286: Building Awesome CLI apps in Go

Run: func(cmd *cobra.Command, args []string) { items, err := todo.ReadItems("/Users/spf13/.tridos.json")

if err != nil { log.Printf("%v", err) } fmt.Println(items)},

CMD/LIST.GO

286

Page 287: Building Awesome CLI apps in Go

Run: func(cmd *cobra.Command, args []string) { items, err := todo.ReadItems("/Users/spf13/.tridos.json")

if err != nil { log.Printf("%v", err) } fmt.Println(items)},

CMD/LIST.GO

287

Page 289: Building Awesome CLI apps in Go

func addRun(cmd *cobra.Command, args []string) { var items = []todo.Item{}

for _, x := range args { items = append(items, todo.Item{Text: x}) }

...

CMD/ADD.GO

289

Page 290: Building Awesome CLI apps in Go

func addRun(cmd *cobra.Command, args []string) { items, err := todo.ReadItems("/Users/spf13/.tridos.json") if err != nil { log.Printf("%v", err) }

for _, x := range args { items = append(items, todo.Item{Text: x}) }

...

CMD/ADD.GO

290

Page 296: Building Awesome CLI apps in Go

func init() { // Here you will define your flags and configuration settings // Cobra supports Persistent Flags which if defined here will be global for your application home, err := homedir.Dir() if err != nil { log.Println("Unable to detect home directory. Please set data file using --datafile.") }

...

CMD/ROOT.GO

296

Page 298: Building Awesome CLI apps in Go

func init() {

...RootCmd.PersistentFlags().StringVar(&dataFile, "datafile", home+string(os.PathSeparator)+".tridos.json", "data file to store todos")

...

CMD/ROOT.GO

298

Page 299: Building Awesome CLI apps in Go

func init() {

...

RootCmd.PersistentFlags().StringVar(&dataFile, "datafile", home+string(os.PathSeparator)+".tridos.json", "data file to store todos")

CMD/ROOT.GO

299

Page 300: Building Awesome CLI apps in Go

func init() {

...

RootCmd.PersistentFlags().StringVar(&dataFile, "datafile", home+string(os.PathSeparator)+".tridos.json", "data file to store todos")

CMD/ROOT.GO

300

Page 301: Building Awesome CLI apps in Go

func init() {

...

RootCmd.PersistentFlags().StringVar(&dataFile, "datafile", home+string(os.PathSeparator)+".tridos.json", "data file to store todos")

CMD/ROOT.GO

301

Page 304: Building Awesome CLI apps in Go

› go build

› ./tri

...

Flags:

--datafile string data file to store todos (default "/Users/spf13/.tridos.json")

-h, --help help for tri

Use "tri [command] --help" for more information about a command.

SEE THE FLAG

304

Page 305: Building Awesome CLI apps in Go

› go build

› ./tri add "Add priorities" \--datafile $HOME/.next.json

› ./tri list --datafile $HOME/.next.json

[{Add priorities}]

USE THE FLAG

305

Page 311: Building Awesome CLI apps in Go

func (i *Item) SetPriority(pri int) { switch pri { case 1: i.Priority = 1 case 3: i.Priority = 3 default: i.Priority = 2 }}

TODO/TODO.GO

311

Page 312: Building Awesome CLI apps in Go

func (i *Item) SetPriority(pri int) { switch pri { case 1: i.Priority = 1 case 3: i.Priority = 3 default: i.Priority = 2 }}

TODO/TODO.GO

312

Page 313: Building Awesome CLI apps in Go

func (i *Item) SetPriority (pri int) { switch pri { case 1: i.Priority = 1 case 3: i.Priority = 3 default: i.Priority = 2 }}

TODO/TODO.GO

313

Page 315: Building Awesome CLI apps in Go

var priority int

...func init() { RootCmd.AddCommand(addCmd) addCmd.Flags().IntVarP(&priority, "priority", "p", 2, "Priority:1,2,3")

...

CMD/ADD.GO

315

Page 316: Building Awesome CLI apps in Go

var priority int

...func init() { RootCmd.AddCommand(addCmd) addCmd.Flags().IntVarP(&priority, "priority", "p", 2, "Priority:1,2,3")

...

CMD/ADD.GO

316

Page 317: Building Awesome CLI apps in Go

› go build

› ./tri help add

Add will create a new todo item to the list

Usage:

tri add [flags]

Flags:

-p, --priority int Priority:1,2,3 (default 2)

HELP ADD

317

Page 319: Building Awesome CLI apps in Go

func addRun(cmd *cobra.Command, args []string) { ... for _, x := range args { item := todo.Item{Text: x} item.SetPriority(priority) items = append(items, item) }

CMD/ADD.GO

319

Page 320: Building Awesome CLI apps in Go

func addRun(cmd *cobra.Command, args []string) { ... for _, x := range args { item := todo.Item{Text: x} item.SetPriority(priority) items = append(items, item) }

CMD/ADD.GO

320

Page 321: Building Awesome CLI apps in Go

› ./tri add "format list" -p1

› ./tri list

[{add priorities 0} {order by priority 0} {format list 1}]

ADD WITH PRIORITY

321

Page 324: Building Awesome CLI apps in Go

// listCmd respresents the list commandvar listCmd = &cobra.Command{ Use: "list", Short: "List the todos", Long: `Listing the todos`, Run: listRun,}func listRun(cmd *cobra.Command, args []string) {

...

}

CMD/LIST.GO

324

Page 326: Building Awesome CLI apps in Go

func listRun(cmd *cobra.Command, args []string) {

...

w := tabwriter.NewWriter(os.Stdout, 3, 0, 1, ' ', 0)for _, i := range items { fmt.Fprintln(w, strconv.Itoa(i.Priority)+"\t"+i.Text+"\t")}w.Flush()

}

CMD/LIST.GO

326

Page 327: Building Awesome CLI apps in Go

func listRun(cmd *cobra.Command, args []string) {

...

w := tabwriter.NewWriter(os.Stdout, 3, 0, 1, ' ', 0)for _, i := range items { fmt.Fprintln(w, strconv.Itoa(i.Priority)+"\t"+i.Text+"\t")}w.Flush()

}

CMD/LIST.GO

327

Page 328: Building Awesome CLI apps in Go

func listRun(cmd *cobra.Command, args []string) {

...

w := tabwriter.NewWriter(os.Stdout, 3, 0, 1, ' ', 0)for _, i := range items { fmt.Fprintln(w, strconv.Itoa(i.Priority)+"\t"+i.Text+"\t")}w.Flush()

}

CMD/LIST.GO

328

Page 329: Building Awesome CLI apps in Go

func listRun(cmd *cobra.Command, args []string) {

...

w := tabwriter.NewWriter(os.Stdout, 3, 0, 1, ' ', 0)for _, i := range items { fmt.Fprintln(w, strconv.Itoa(i.Priority)+"\t"+i.Text+"\t")}w.Flush()

}

CMD/LIST.GO

329

Page 330: Building Awesome CLI apps in Go

func listRun(cmd *cobra.Command, args []string) {

...

w := tabwriter.NewWriter(os.Stdout, 3, 0, 1, ' ', 0)for _, i := range items { fmt.Fprintln(w, strconv.Itoa(i.Priority)+"\t"+i.Text+"\t")}w.Flush()

}

CMD/LIST.GO

330

Page 333: Building Awesome CLI apps in Go

func (i *Item) PrettyP() string { if i.Priority == 1 { return "(1)" } if i.Priority == 3 { return "(3)" } return " "}

TODO/TODO.GO

333

Page 334: Building Awesome CLI apps in Go

func listRun(cmd *cobra.Command, args []string) {

...

w := tabwriter.NewWriter(os.Stdout, 3, 0, 1, ' ', 0)for _, i := range items { fmt.Fprintln(w, i.PrettyP()+"\t"+i.Text+"\t")}w.Flush()

}

CMD/LIST.GO

334

Page 340: Building Awesome CLI apps in Go

func ReadItems(filename string) ([]Item, error) {... for i, _ := range items { items[i].position = i + 1 } return items, nil}

TODO/TODO.GO

340

Page 343: Building Awesome CLI apps in Go

// ByPri implements sort.Interface for []Item based on// the Priority & position field.type ByPri []Itemfunc (s ByPri) Len() int { return len(s) }func (s ByPri) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s ByPri) Less(i, j int) bool { if s[i].Priority == s[j].Priority { return s[i].position < s[j].position } return s[i].Priority < s[j].Priority}

TODO/TODO.GO

343

Page 344: Building Awesome CLI apps in Go

// ByPri implements sort.Interface for []Item based on// the Priority & position field.type ByPri []Itemfunc (s ByPri) Len() int { return len(s) }func (s ByPri) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s ByPri) Less(i, j int) bool { if s[i].Priority == s[j].Priority { return s[i].position < s[j].position } return s[i].Priority < s[j].Priority}

TODO/TODO.GO

344

Page 345: Building Awesome CLI apps in Go

// ByPri implements sort.Interface for []Item based on// the Priority & position field.type ByPri []Itemfunc (s ByPri) Len() int { return len(s) }func (s ByPri) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s ByPri) Less(i, j int) bool { if s[i].Priority == s[j].Priority { return s[i].position < s[j].position } return s[i].Priority < s[j].Priority}

TODO/TODO.GO

345

Page 346: Building Awesome CLI apps in Go

// ByPri implements sort.Interface for []Item based on// the Priority & position field.type ByPri []Itemfunc (s ByPri) Len() int { return len(s) }func (s ByPri) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s ByPri) Less(i, j int) bool { if s[i].Priority == s[j].Priority { return s[i].position < s[j].position } return s[i].Priority < s[j].Priority}

TODO/TODO.GO

346

Page 347: Building Awesome CLI apps in Go

// ByPri implements sort.Interface for []Item based on// the Priority & position field.type ByPri []Itemfunc (s ByPri) Len() int { return len(s) }func (s ByPri) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s ByPri) Less(i, j int) bool { if s[i].Priority == s[j].Priority { return s[i].position < s[j].position } return s[i].Priority < s[j].Priority}

TODO/TODO.GO

347

Page 348: Building Awesome CLI apps in Go

// ByPri implements sort.Interface for []Item based on// the Priority & position field.type ByPri []Itemfunc (s ByPri) Len() int { return len(s) }func (s ByPri) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s ByPri) Less(i, j int) bool { if s[i].Priority == s[j].Priority { return s[i].position < s[j].position } return s[i].Priority < s[j].Priority}

TODO/TODO.GO

348

Page 349: Building Awesome CLI apps in Go

func listRun(cmd *cobra.Command, args []string) {... sort.Sort(todo.ByPri(items)) w := tabwriter.NewWriter(os.Stdout, 3, 0, 1, ' ', 0)

...

CMD/LIST.GO

349

Page 354: Building Awesome CLI apps in Go

// doneCmd represents the done commandvar doneCmd = &cobra.Command{ Use: "done", Aliases: []string{"do"}, Short: "Mark Item as Done", Run: doneRun, }

CMD/DONE.GO

354

Page 358: Building Awesome CLI apps in Go

func doneRun(cmd *cobra.Command, args []string) { items, err := todo.ReadItems(dataFile) i, err := strconv.Atoi(args[0]) if err != nil { log.Fatalln(args[0], "is not a valid label\n", err) }

...

CMD/DONE.GO

358

Break out still

Page 359: Building Awesome CLI apps in Go

func doneRun(cmd *cobra.Command, args []string) {...

if i > 0 && i < len(items) { items[i-1].Done = true fmt.Printf("%q %v\n", items[i-1].Text, "marked done")

sort.Sort(todo.ByPri(items)) todo.SaveItems(dataFile, items) } else { log.Println(i, "doesn't match any items") } }

CMD/DONE.GO

359

Break out slide

Page 360: Building Awesome CLI apps in Go

› go run main.go done a

2016/05/14 22:22:03 a is not a valid label

strconv.ParseInt: parsing "a": invalid syntax

DONE WRONG

360

Page 366: Building Awesome CLI apps in Go

func (s ByPri) Less(i, j int) bool { if s[i].Done == s[j].Done { if s[i].Priority == s[j].Priority { return s[i].position < s[j].position } return s[i].Priority < s[j].Priority } return !s[i].Done}

TODO/TODO.GO

366

Page 372: Building Awesome CLI apps in Go

func init() { RootCmd.AddCommand(listCmd) listCmd.Flags().BoolVar(&doneOpt, "done", false, "Show 'Done' Todos ")

}

CMD/LIST.GO

372

Page 373: Building Awesome CLI apps in Go

func listRun(cmd *cobra.Command, args []string) {...

for _, i := range items { if i.Done == doneOpt { fmt.Fprintln(w, i.Label()...) }}

CMD/LIST.GO

373

Page 378: Building Awesome CLI apps in Go

func listRun(cmd *cobra.Command, args []string) {...

for _, i := range items {if allOpt || i.Done == doneOpt { fmt.Fprintln(w, i.Label()...) }}

CMD/LIST.GO

378

Page 379: Building Awesome CLI apps in Go

› go run main.go list --all --done

1. (1) format list

2. hide done items

3. X order by priority

4. X add priorities

TRY --ALL

379

Page 381: Building Awesome CLI apps in Go

›./tri list --datafile \

$HOME/Dropbox/Sync/tridos.json

›./tri add "Add config/ENV support" \

--datafile $HOME/Dropbox/Sync/tridos.json

USING DIFFERENT DATA FILE

381

Page 384: Building Awesome CLI apps in Go

VIPER SUPPORTS•YAML, TOML, JSON or HCL

•Etcd, Consul

•Config LiveReloading

•default < KeyVal < config < env < flag

•Aliases & Nested values 384

Page 385: Building Awesome CLI apps in Go

var cfgFile string

func init() { cobra.OnInitialize(initConfig)

... RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.tri.yaml)")...

CMD/ROOT.GO

385

Already There

Page 386: Building Awesome CLI apps in Go

// Read in config file and ENV variables if set.func initConfig() { viper.SetConfigName(".tri") viper.AddConfigPath("$HOME") viper.AutomaticEnv() // If a config file is found, read it in. if err := viper.ReadInConfig(); err == nil { fmt.Println("Using config file:", viper.ConfigFileUsed()) }

}

CMD/ROOT.GO

386Already There

Page 393: Building Awesome CLI apps in Go

func addRun(cmd *cobra.Command, args []string) { items, err := todo.ReadItems(dataFile))

...

if err := todo.SaveItems(dataFile), items); err != nil {

CMD/ADD.GO

393

Page 394: Building Awesome CLI apps in Go

func addRun(cmd *cobra.Command, args []string) { items, err := todo.ReadItems(viper.GetString("datafile"))

...

if err := todo.SaveItems(viper.GetString("datafile"), items); err != nil {

CMD/ADD.GO

394

Page 395: Building Awesome CLI apps in Go

func doneRun(cmd *cobra.Command, args []string) {

...

items, err := todo.ReadItems(viper.GetString("datafile"))

...

todo.SaveItems(viper.GetString("datafile"), items)

CMD/DONE.GO

395

Page 396: Building Awesome CLI apps in Go

func listRun(cmd *cobra.Command, args []string) {

items, err := todo.ReadItems(viper.GetString("datafile"))

...

CMD/LIST.GO

396

Page 398: Building Awesome CLI apps in Go

›./tri list --datafile \

$HOME/Dropbox/Sync/tridos.json

›./tri add "Add config/ENV support" \

--datafile $HOME/Dropbox/Sync/tridos.json

USING DIFFERENT DATA FILE

398

Page 399: Building Awesome CLI apps in Go

› DATAFILE=$HOME/Dropbox/Sync/trido.json \

./tri list

› DATAFILE=$HOME/Dropbox/Sync/trido.json \

./tri add "Add config/ENV support"

USING ENV

399

Page 400: Building Awesome CLI apps in Go

› export DATAFILE=$HOME/Dropbox/Sync/trido.json

› ./tri list

1. Add config/ENV support

2. Add config/ENV support

ExPORT FTW

400

Page 402: Building Awesome CLI apps in Go

// Read in config file and ENV variables if set.func initConfig() { viper.SetConfigName(".tri") viper.AddConfigPath("$HOME") viper.AutomaticEnv()

viper.SetEnvPrefix("tri") // If a config file is found, read it in. if err := viper.ReadInConfig(); err == nil { fmt.Println("Using config file:", viper.ConfigFileUsed()) }

CMD/ROOT.GO

402

Page 403: Building Awesome CLI apps in Go

› export TRI_DATAFILE=$HOME/Dropbox/Sync/trido.json

› ./tri list

1. Add config/ENV support

2. Add config/ENV support

ExPORT FTW

403

Page 407: Building Awesome CLI apps in Go

› go run main.go list

Using config file: /Users/spf13/.tri.yaml

1. Add config/ENV support

2. Add config/ENV support

USE CONFIG FILE

407