Download - Building Awesome CLI apps in Go
BUILDING AN AWESOME CLI APP IN GO
1
2
ASHLEY MAC
•Dev Advocate at Rackspace
•Go newbie
•Slightly sarcastic3
–Ashley Mac
“I have no idea what i’m doing”
4
SPF13
•Willfully Unemployed
•Builds Cool Things With Go AKA Gopher
•Irritatingly Specific5
–Steve
“I Kinda Know What I'm Doing”
6
MAKE SURE YOU HAVE GO INSTALLED
AND WORKING + AN EDITOR
7
UX OF CLI
8
HUMAN INTERFACE GUIDELINES
FOR CLIS9
UNIx PHILOSOPHY
10
KEN'S "UNIx PHILOSOPHY"11
UNIx PHILOSOPHY
•Simple
•Clear
•Composable
•Extensible
•Modular
•Small12
– Rob Pike
“Many UNIX programs do quite trivial things in isolation, but, combined with other programs, become general and
useful tools.” 13
– Doug McIlroy
“The manual page, which really used to be a manual page, is now a small volume,
with a thousand options” 14
POSIx + GNU
15
COMMANDS
16
WHAT ARE COMMANDS
?17
› ls
c:\dir
COMMANDS
18
› ls
› cp
› cat
› cd
list
copy
concatenate
change directory
ABBREVIATED
19
› ls
› cp
› cat
› cd
SHORT
20
Short
Clear&
› lsCONTExT
21
operates in current Directory
THE LANGUAGE OF COMMANDS
22
› lsHAS A LANGUAGE
23
List the contentsof the directory I'm in
› ls
› rm
› zip
› find
COMMANDS
24
Verbs
c:\dir
COMMANDS
25
Noun?
ARGS26
WHAT ARE ARGS?
27
› rm [file]
› cp [file] [newfile]
c:\copy [file] [newfile]
INPUT
28
> rm [file] ... [fileN]
MANY INPUTS
29
Arg0 Argn
> cp [file] [newfile]
ORDER MATTERS
30
From To
> cp [file] [newfile]
SEPARATED
31
Space separates
THE LANGUAGE OF ARGS
32
> cp [file] [newfile]
DIRECT OBJECT
33
Verb Noun Noun
> cp [file] [newfile]
IS PROUNCABLE
34
Copy this file to here
OPTIONS35
FLAGS36
› rm [options] [file]
c:\del [options] [file]
MODIFY ACTIONS
37
› rm -f badfile.txt
SEPARATORS
38
Space separates
› rm --force
PREFIxED
39
prefix
› rm -f
PREFIxED
40
prefix
c:\ del /F
PREFIxED
41
prefix
-f == --force
SHORT VS LONG
42
Common Options shortened
› rm -r -f rm -rf
STACKABLE
43
Short options stack
THE LANGUAGE OF FLAGS
44
› rm --force [file]
ADVERB
45
Verb Adverb Noun
› rm --force [file]
PRONOUNCEABLE
46
Forcefully remove this file
› rm --force [file]
PRONOUNCEABLE
47
remove this file with Force
› ls --color /home/spf13
FLAG INPUT
48
Verb Adverb Noun
› ls --color /home/spf13
PRONOUNCEABLE
49
Colorfully list my home directory
› ls -a
MODIFY BEHAVIOR
50
› ls -a
MODIFY BEHAVIOR
51
List all the things
ALL ISN'T AN ADVERB
52
ADVERBS ExPRESS•manner
•place
•time
•frequency
•degree
•level of certainty
•etc.53
ADVERBS ANSWER•How? •In what way?
•When?
•Where? •To what extent?
54
› ls -a
MODIFY BEHAVIOR
55
List completely
to what extent?
› ls --width=40
IMPUTABLE
56
Input
to what extent?
› ls --width 40
IMPUTABLE
57
Input
to what extent?
› ls --width 40
PREPOSITION
58
Verb Preposition Obj
› ls --width 40
PRONOUNCEABLE
59
list the directory with a width of 40 cols
PREP PHRASE = ADVERB•consists of a preposition and its
object
•acts as an adverb
•"Speaking at OSCON"60
BAD FLAG DESIGN
61
FLAGS AS ACTIONS
62
› tar -xcvf
FLAG = ACTION
63
› tar
First option must be a mode specifier:
-c Create -r Add/Replace -t List -u Update -x Extract
FLAG = ACTION
64
› zip
› unzip
BETTER
65
FLAGS WITH FLAGS
66
Extract: tar -x [options]
-k Keep existing files
-m Don't restore mod times
FLAGS HAVING SUB FLAGS
67
Create: tar -c [options]
-z, -j, -J, --lzma Compress archive with gzip/bzip2/xz/lzma
FLAGS HAVING SUB FLAGS
68
INCOMPATIBLE FLAGS
69
ls [options]
-S sort by file size
-t sort by modification time
-U do not sort
...
INCOMPATIBLE FLAGS
70
ls -StU INCOMPATIBLE FLAGS
71
What should this do?
> ls
--sort=[size,modtime,none]
BETTER
72
DOUBLE FLAGS
73
› git pull --stat --no-stat FLAG --NO-FLAG
74
› git pull --stat
› git pull --stat=false
BETTER
75
CLI APPS76
› httpd
› vi
› emacs
› git
CLI APPS
77
Noun
APPS
•Launch something
•Do more than one thing
•Collection of commands78
SUB COMMANDS
79
› svn add
› brew install
› npm search
› apt-get upgrade
› git clone
SUB COMMANDS
80
SUB COMMANDS•CLI apps do multiple things
•Apps are groups of commands
•(sub) Commands have flags & args
•All rules still apply81
› brew fetch -v hugo
ExPANDED RESOURCES
82
Cmd ArgApp Flag
› brew install hugo
NOUN
83
Verb ObjectNoun
› brew install hugo
PRONOUNCABLE
84
Brew, install hugo
› brew fetch -v hugo
PRONOUNCABLE
85
Verb ObjectNoun adVerb
› brew fetch -v hugo
PRONOUNCABLE
86
Brew, verbosely fetch hugo
GO INTRODUCTION
87
MY ExPERIENCE
88
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
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
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
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
GOPATH AND SETUP
•Typing go get github.com/user/project will download and build a project.
93
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
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
YOU DON’T NEED A FRAMEWORK!
•The standard library has tons!
•Web server
•Templates
•Database
•etc.. 96
YOU DON’T NEED A FRAMEWORK!
•Many Packages work well together
•Very modular & Composable
97
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
GO99
STATICALLY TYPED
100
STRONGLY TYPED
101
COMPILED102
MEMORY MANAGED
103
POWERFUL CONCURRENCY
BAKED IN104
OPEN SOURCE
105
CROSS PLATFORM
106
LANGUAGE DESIGN
107
108
•Ken Thompson (B,C, Unix, UTF-8)
•Rob Pike (Unix, UTF-8)
•Robert Griesmier (Hotspot, JVM, V8)
MAJOR INFLUENCES
•C & derivatives
•Pascal & derivatives
•CSP109
IDEALS
•Expressibility •Edibility •Powerful
•Simple •Scalable
110
SPEC
•Java Spec : 670 pages
•YAML Spec : 84 pages
•Go Spec : 82 pages111
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
BRACES
113
Fail
BRACES
Compile Eror
114
Compile Error
/tmp/sandbox826898458/main.go:6: syntax error: unexpected semicolon or newline before {
BRACES
What works
115
Works
VARIABLES
116
Fail
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
WHAT GO DOESN’T HAVE
118
CLASSES ( * SHOCKED
EXPRESSION *)119
OBJECTS120
COMPLExITY
121
TYPES122
SINGLE VALUE TYPES
123
NUMBERS
•Integers (numbers without a decimal)
•Floating-Point Numbers (numbers that contain a decimal)
• int, int8-64, uint, uint8-64, float32,64124
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
MULTI VALUE TYPES
126
type person struct { name string age int }
STRUCT
127
ARRAY
•An array is an ordered sequence of elements of a single type
•Fixed length
•Arrays are indexed starting from 0128
SLICE
•Segment of an array
•Dynamic length
•Can be used without thinking about array underneath
129
MAP•Unordered collection of key-value
pairs
•Similar to associative arrays, hash tables, and dictionaries
•Dynamic Length130
SOME OTHER TYPES
131
FUNCTION•Function is a type
•First class citizen in Go
•Can have multiple input values
•Can have multiple return values132
POINTERS•Reference a location in memory
where a value is stored
•Represented using '*'
•Memory location referenced using '&'133
DEFINE YOUR OWN TYPES
•Composed of other types
•Not aliases
134
METHOD
•Function defined on a type
135
TOOLING136
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
GO FMT
•Formats your go code for you
•Awesome to do "on save"
•End of all stylistic debates138
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
CLOSING THOUGHTS
140
I THINK GO IS AWESOME
141
IT HAS STRONG OPINIONS
142
COMPILATION IS VERY FAST (USUALLY A
SECOND OR TWO)143
AWESOME COMMUNITY
144
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
LET'S BUILD AN APP
146
147
HTTP://WWW.SLIDESHARE.NET/SPF13
148
HTTPS://GITHUB.COM/SPF13/TRI
149
1. DESIGNING OUR APP
150
WHAT'S IN A NAME?
151
SHOCKING AMOUNT OF TODO APPS
152
LET'S MAKE A BUNCH MORE
153
I'M CALLING MINE "TRI"
154
FEATURES155
FEATURES•Add Todo
•List Todos
•Mark "done"
•Search/Filter
•Priorities
•Archive
•Edit
•Create Dates
•Due Dates
•Tags
•Projects
156
INTERFACE DESIGN
157
ADDING158
› tri add "Add Priorities"ADD
159
› tri add Add PrioritiesADD
160
› tri add "Add Priorities"
ADD
161
Verb ObjectNoun
› tri add \ "Add Multi Todo Support" \ "Consider usage behaviors"
ADD
162
PRIORITY163
› tri add -p1 "Add listing"
ADD WITH PRIORITY
164
› tri add -p1 "Add listing"
ADD WITH PRIORITY
165
Verb ObjectNoun adVerb
› tri add -p1 "Add listing"
PRONOUNCEABLE
166
Tri, add "Add listing" Todo with a pri of 1
› tri add "Add listing P:1"
ALTERNATE SYNTAx
167
CONSIDERATIONS•What priority system to use?
•Numeric
•Alpha
•High, Middle, Low
•What's the default priority? 168
MY TODO
•High, Middle, Low
•H=1, L=3, M/_=2
•Default is 2169
LISTING170
› tri ls
LISTING
171
› tri list
LISTING
172
› tri
LISTING
173
› tri list
(1) Add Listing
Consider usage behaviors
Add Multi Todo Support
Add Priorities
LISTING OUTPUT
174
› tri list
(1) Add Listing
Add Priorities
Add Multi Todo Support
Consider usage behaviors
LISTING OUTPUT
175
› tri list
(H) Add Listing
Consider usage behaviors
Add Priorities
(L) Add Multi Todo Support
LISTING OUTPUT
176
› tri list
(H) Add Listing
Consider usage behaviors
Add Priorities
(L) Add Multi Todo Support
LISTING OUTPUT
177
› tri list
1. (H) Add Listing
2. Consider usage behaviors
3. Add Priorities
4. (L) Add Multi Todo Support
LISTING OUTPUT
178
FILTERING179
› tri list done
TOKENS
180
› tri list -p1
FLAGS
181
› tri list -p1
› tri list --due June
› tri list --created 12/15
› tri list --done -p1
FILTER BY PROPERTY
182
› tri list -p2
2. Consider usage behaviors
3. Add Priorities
FILTER BY PRIORITY
183
› tri list "Add"
1. (H) Add Listing
3. Add Priorities
4. (L) Add Multi Todo Support
SEARCHING
184
› tri list "Add" "Pri"
3. Add Priorities
SEARCHING
185
UPDATING186
› tri list
1. (H) Add Listing
2. Consider usage behaviors
3. Add Priorities
4. (L) Add Multi Todo Support
LISTING OUTPUT
187
› tri done 2
COMPLETING
188
› tri edit 1 "Improve Listing"
EDITING
189
› tri edit 1 -p2
› tri edit 2 --due 05/13/15
› tri edit 3 --created 12/15
EDITING
190
› tri edit 1 -p2
› tri list -p2
› tri edit 3 --created 12/15
› tri list --created 12/15
CONSISTENCY
191
› tri edit 1 2 3 -p2
BATCH EDIT?
192
2. CREATING A PROJECT
193
COBRA194
•A CLI Command Framework
•A tool to generate CLI apps & commands
•Powers Kubernetes, Dropbox, Git Lfs, CoreOS, Docker, Delve ...
195
› go get -u \
github.com/spf13/cobra/cobra
GET & INSTALL COBRA
196
› 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
COBRA APP
BUILDER198
› cobra init \ github.com/<handle>/tri \
-a "<Your Name>"
COBRA INIT
199
Replace with your url, project name & Name
› cobra init ...
Your Cobra application is ready at
/Users/spf13/gopath/src/github.com/spf13/tri
COBRA
200
› cd $GOPATH/src/github.com/<name>/tri
CD TO PROJECT
201
Replace with your url, project name & Name
› tree
.
├── LICENSE
├── cmd
│ └── root.go
└── main.go
LOOK AT YOUR PROJECT
202
› 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
YOU'VE JUST CREATED YOUR
1ST GO APP204
IT'S ALL UPHILL FROM
HERE205
LET'S WRITE SOME CODE
206
OPEN THE PROJECT IN AN EDITOR
207
208
LiteIDE
209
HTTPS://SOURCEFORGE.NET/PROJECTS/LITEIDE/FILES/X29/ 210
WORKING WITH THE
ROOT211
package cmdimport ( "fmt" "os" "github.com/spf13/cobra" "github.com/spf13/viper")
CMD/ROOT.GO
212
package cmdimport ( "fmt" "os" "github.com/spf13/cobra" "github.com/spf13/viper")
CMD/ROOT.GO
213
Notice package name
Matches Dir
// 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
// 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
var RootCmd = &cobra.Command{ Use: "tri",...
CMD/ROOT.GO
216
Package level variable & Exported
MAIN217
MAIN MAIN MAIN•Go programs are all about "main"
•main.go (convention)
•main package
•main()218
package main import "github.com/<yours>/tri/cmd" func main() { cmd.Execute()}
MAIN.GO
219
package main import "github.com/<yours>/tri/cmd"func main() { cmd.Execute()}
MAIN.GO
220
Package Name
package main import "github.com/<yours>/tri/cmd" func main() { cmd.Execute()}
MAIN.GO
221
Function Name
func Execute() { if err := RootCmd.Execute(); err != nil { fmt.Println(err) os.Exit(-1) }}
CMD/ROOT.GO
222
Package level variable
>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
3. CREATING OUR ADD COMMAND
224
ADD "ADD"
225
> cd $GOPATH/src/github.com/spf13/tri
CD TO PROJECT
226
Replace with your url, project name & Name
> cobra add add
add created at $GOPATH/src/github.com/spf13/tri/cmd/add.go
COBRA ADD ADD
227
> go run main.go add
add called
RUN "ADD"
228
Bill wants me to replace go run with
go build./tri
MAKE "ADD" ADD
229
package cmdCMD/ADD.GO
230
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
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
func addRun(cmd *cobra.Command, args []string){
fmt.Println("add called")
}
CMD/ADD.GO
233
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
func addRun(cmd *cobra.Command, args []string) { for _, x := range args { fmt.Println(x) }}
CMD/ADD.GO
235
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
func init() { RootCmd.AddCommand(addCmd)}
CMD/ADD.GO
237
INIT()•Special function
•Called after package variable declarations
•Called prior to main.main()
•Each package may have multiple init()
•init() order un-guaranteed238
> go run main.go add \
"one two" three
one two
three
RUNNING ADD
239
4. CREATING OUR DATA
MODEL240
CREATE A NEW
PACKAGE241
242
New Folder New File
package todo type Item struct { Text string }
TODO/TODO.GO
243
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
USING OUR NEW TYPE
245
import ( "fmt" "github.com/spf13/cobra" "github.com/<yourname>/tri/todo")
CMD/ADD.GO
246
Replace with yourS
func addRun(...) { items := []todo.Item{} for _, x := range args { items = append(items, todo.Item{Text:x}) } fmt.Println(items)}
CMD/ADD.GO
247
func addRun(...) { items := []todo.Item{} for _, x := range args { items = append(items, todo.Item{Text:x}) } fmt.Println(items)}
CMD/ADD.GO
248
APPEND
•Append adds new values to a slice
•Append will grow a slice as needed
249
func addRun(...) { items := []todo.Item{} for _, x := range args { items = append(items, todo.Item{Text:x}) } fmt.Println(items)}
CMD/ADD.GO
250
func addRun(...) { items := []todo.Item{} for _, x := range args { items = append(items, todo.Item{Text:x}) } fmt.Println(items)}
CMD/ADD.GO
251
>go run main.go add \ "one two" three
[{one two} {three}]
GO RUN
252
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
>go run main.go add \ "one two" three
[]todo.Item{todo.Item{Text:"one two"}, todo.Item{Text:"three"}}
GO RUN
254
5. PERSISTING OUR DATA
255
SAVING DATA
256
func SaveItems(filename string, items []Item) error { return nil }
TODO/TODO.GO
257
JSON258
import ( "encoding/json")
TODO/TODO.GO
259
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
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
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
ERROR HANDLING•Errors are not exceptional, they
are just values
•No exceptions in Go
•Errors should be handled when they occur
263
( 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
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
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
>go run main.go add \
"one two" three
[{"Text":"one two"},{"Text":"three"}]
CHECK JSON CREATION
267
WRITING TO FILES
268
import ( "io/ioutil" "encoding/json")
TODO/TODO.GO
269
func SaveItems(filename string, items []Item) error {... err = ioutil.WriteFile(filename, b, 0644) if err != nil { return err } return nil}
TODO/TODO.GO
270
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
› go run main.go add "one two" three
› cat ~/.tridos.json
[{"Text":"one two"},{"Text":"three"}]
CHECK FILE CREATION
272
READING DATA
273
func ReadItems(filename string) ([]Item, error) { return []Item{}, nil}
TODO/TODO.GO
274
func ReadItems(filename string) ([]Item, error) { return []Item{}, nil}
TODO/TODO.GO
275
func ReadItems(filename string) ([]Item, error) { b, err := ioutil.ReadFile(filename) if err != nil { return []Item{}, err }
...
TODO/TODO.GO
276
func ReadItems(filename string) ([]Item, error) { b, err := ioutil.ReadFile(filename) if err != nil { return []Item{}, err }
...
TODO/TODO.GO
277
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
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
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
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
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
LIST COMMAND
283
› cobra add list
list created at $GOPATH/src/github.com/spf13/tri/cmd/list.go
ADD LIST COMMAND
284
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
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
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
› go run main.go list
[{one two} {three}]
RUN LIST
288
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
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
6. ADDING ROOT (GLOBAL)
FLAGS291
USING $HOME
292
› go get github.com/mitchellh/go-homedir
GO GET
293
package cmd import ( ... "github.com/mitchellh/go-homedir" )
CMD/ROOT.GO
294
var dataFile string
CMD/ROOT.GO
295
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
ADDING A FLAG
297
func init() {
...RootCmd.PersistentFlags().StringVar(&dataFile, "datafile", home+string(os.PathSeparator)+".tridos.json", "data file to store todos")
...
CMD/ROOT.GO
298
func init() {
...
RootCmd.PersistentFlags().StringVar(&dataFile, "datafile", home+string(os.PathSeparator)+".tridos.json", "data file to store todos")
CMD/ROOT.GO
299
func init() {
...
RootCmd.PersistentFlags().StringVar(&dataFile, "datafile", home+string(os.PathSeparator)+".tridos.json", "data file to store todos")
CMD/ROOT.GO
300
func init() {
...
RootCmd.PersistentFlags().StringVar(&dataFile, "datafile", home+string(os.PathSeparator)+".tridos.json", "data file to store todos")
CMD/ROOT.GO
301
items, err := todo.ReadItems(dataFile)
...err := todo.SaveItems(dataFile, items)
CMD/ADD.GO
302
items, err := todo.ReadItems(dataFile)
CMD/LIST.GO
303
› 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
› go build
› ./tri add "Add priorities" \--datafile $HOME/.next.json
› ./tri list --datafile $HOME/.next.json
[{Add priorities}]
USE THE FLAG
305
7. ADDING PRIORITIES
306
› go run main.go add \
"add priorities" \ "order by priority"
ADD SOME TODOS
307
ADJUSTING OUR TYPE
308
type Item struct { Text string Priority int}
TODO/TODO.GO
309
VALIDATING INPUT
310
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
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
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
ADDING FLAG
314
var priority int
...func init() { RootCmd.AddCommand(addCmd) addCmd.Flags().IntVarP(&priority, "priority", "p", 2, "Priority:1,2,3")
...
CMD/ADD.GO
315
var priority int
...func init() { RootCmd.AddCommand(addCmd) addCmd.Flags().IntVarP(&priority, "priority", "p", 2, "Priority:1,2,3")
...
CMD/ADD.GO
316
› 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
SETTING PRIORITY
318
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
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
› ./tri add "format list" -p1
› ./tri list
[{add priorities 0} {order by priority 0} {format list 1}]
ADD WITH PRIORITY
321
8. MAKING OUR LIST PRETTY
322
BREAK OUT LISTRUN
323
// 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
TAB-WRITER
325
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
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
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
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
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
› go run main.go list
0 add priorities
0 order by priority
1 format list
LIST W/PRIORITY
331
PRETTIER PRIORITY PRINTING
332
func (i *Item) PrettyP() string { if i.Priority == 1 { return "(1)" } if i.Priority == 3 { return "(3)" } return " "}
TODO/TODO.GO
333
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
› go run main.go list
add priorities
order by priority
(1) format list
LIST W/PRIORITY
335
LABELS336
type Item struct { Text string Priority int position int}
TODO/TODO.GO
337
type Item struct { Text string Priority int position int}
TODO/TODO.GO
338
Notice Lowercase p
func (i *Item) Label() string { return strconv.Itoa(i.position) + "."}
TODO/TODO.GO
339
func ReadItems(filename string) ([]Item, error) {... for i, _ := range items { items[i].position = i + 1 } return items, nil}
TODO/TODO.GO
340
› go run main.go list
1. add priorities
2. order by priority
3. (1) format list
LIST W/PRIORITY
341
SORT342
// 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
// 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
// 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
// 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
// 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
// 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
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
› go run main.go list
3. (1) format list
1. add priorities
2. order by priority
LIST W/PRIORITY
350
9. DONE-INGTODOS
351
ADD DONE
352
› cobra add done
done created at $GOPATH/src/github.com/spf13/tri/cmd/done.go
COBRA ADD DONE
353
// doneCmd represents the done commandvar doneCmd = &cobra.Command{ Use: "done", Aliases: []string{"do"}, Short: "Mark Item as Done", Run: doneRun, }
CMD/DONE.GO
354
ADD .DONE
355
type Item struct { Text string Priority int position int Done bool }
TODO/TODO.GO
356
TRI DONE SET
.DONE357
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
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
› 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
› go run main.go done 13
2016/05/14 22:44:06 13 doesn't match any items
DONE WRONG
361
› go run main.go done 1 DONE RIGHT
362
UPDATE LISTING
363
func (i *Item) PrettyDone() string { if i.Done { return "X" } return ""}
TODO/TODO.GO
364
fmt.Fprintln(w, i.Label()+"\t"+i.PrettyDone()+"\t"+i.PrettyP()+"\t"+i.Text+"\t")
CMD/LIST.GO
365
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
› go run main.go list
1. (1) format list
2. X order by priority
3. X add priorities
LIST
367
ADD --DONE FLAG
368
› go run main.go add "hide done items"
ADD NEW TODO
369
fmt.Fprintln(w, i.Label()+"\t"+i.PrettyDone()+"\t"+i.PrettyP()+"\t"+i.Text+"\t")
CMD/LIST.GO
370
var ( doneOpt bool)
CMD/LIST.GO
371
func init() { RootCmd.AddCommand(listCmd) listCmd.Flags().BoolVar(&doneOpt, "done", false, "Show 'Done' Todos ")
}
CMD/LIST.GO
372
func listRun(cmd *cobra.Command, args []string) {...
for _, i := range items { if i.Done == doneOpt { fmt.Fprintln(w, i.Label()...) }}
CMD/LIST.GO
373
› go run main.go list --done
3. X order by priority
4. X add priorities
TRY --DONE
374
SHOW ALL
375
var ( doneOpt bool allOpt bool)
CMD/LIST.GO
376
func init() {...
listCmd.Flags().BoolVar(&allOpt, "all", false, "Show all Todos")
}
CMD/LIST.GO
377
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
› 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
10. CONFIG
380
›./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
CONFIG FTW
382
•A configuration manager
•Registry for application settings
•Works well with Cobra383
VIPER SUPPORTS•YAML, TOML, JSON or HCL
•Etcd, Consul
•Config LiveReloading
•default < KeyVal < config < env < flag
•Aliases & Nested values 384
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
// 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
12 FACTOR APPS
387
STRICT SEPARATION OF CONFIG FROM
CODE388
CONFIG IS STUFF THAT VARIES IN
DIFFERENT ENVIRONMENTS
389
CODE IS STUFF THAT STAYS THE
SAME EVERYWHERE
390
THE 12 FACTOR APP STORES
CONFIG IN ENV VARS
391
READ FROM VIPER
392
func addRun(cmd *cobra.Command, args []string) { items, err := todo.ReadItems(dataFile))
...
if err := todo.SaveItems(dataFile), items); err != nil {
CMD/ADD.GO
393
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
func doneRun(cmd *cobra.Command, args []string) {
...
items, err := todo.ReadItems(viper.GetString("datafile"))
...
todo.SaveItems(viper.GetString("datafile"), items)
CMD/DONE.GO
395
func listRun(cmd *cobra.Command, args []string) {
items, err := todo.ReadItems(viper.GetString("datafile"))
...
CMD/LIST.GO
396
ENV397
›./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
› DATAFILE=$HOME/Dropbox/Sync/trido.json \
./tri list
› DATAFILE=$HOME/Dropbox/Sync/trido.json \
./tri add "Add config/ENV support"
USING ENV
399
› export DATAFILE=$HOME/Dropbox/Sync/trido.json
› ./tri list
1. Add config/ENV support
2. Add config/ENV support
ExPORT FTW
400
PLAYING NICE WITH
OTHERS401
// 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
› export TRI_DATAFILE=$HOME/Dropbox/Sync/trido.json
› ./tri list
1. Add config/ENV support
2. Add config/ENV support
ExPORT FTW
403
CONFIG FILES
404
GOOD FOR APPS
405
› echo "datafile: /Users/spf13/Dropbox/Sync/trido.json" > $HOME/.tri.yaml
CREATE CONFIG FILE
406
› 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
11. WORKING AHEAD
408
TRY TO IMPLEMENT
ExTRA FEATURES 409
EDITING410
SEARCH411
FOLLOW US ON TWITTER
@ASHLEYMCNAMARA @SPF13
412
ADDITIONAL RESOURCES
•Ashley's Learn to Code Resources
•Steve's Blog
•Go Girls Who Code413