I recently finished Gophercises, a great set of Go practice lessons by Jon Calhoun. I think it took me around a month from start to finish with some stuff in the middle. Most were nice, some were tedious. For example, the last exercise was about PDF generation and went to boring quickly.
After every lesson, I wrote down "Lessons Learned" in the README. This page collects most of them. All code is here:
<-timerVar.C
stop := timerVar.Stop()
stop
will be true
.Stop doesn't unblock the channel. If you stop the timer, the channel will remain blocked.
Here's code based on gobyexample that will dead-lock if executed.
package main
import "time"
import "fmt"
func main() {
timer2 := time.NewTimer(10 * time.Second)
stop2 := timer2.Stop()
if stop2 {
fmt.Println("Timer 2 stopped")
}
<-timer2.C
}
Instead, use timerVar.Reset(0)
. This will stop the timer and unblock the channel.
rnd := rand.New(rand.NewSource(time.Now().Unix()))
Needs a swap function of this type func(i, j int)
.
Inside the swap function (does not need to be named swap
), do the swaps.
func (e *Exam) Shuffle() {
rnd := rand.New(rand.NewSource(time.Now().Unix()))
rnd.Shuffle(len(e.problems), func(i, j int) {
e.problems[i], e.problems[j] = e.problems[j], e.problems[i]
})
}
http.ListenAndServe(":8080", customHandler)
Your best friend:
Maps to a []object
or array of objects:
[
{
"key1": "value1",
"key2": "value2"
},
{
"key1": "value3",
"key2": "value4"
}
]
Maps to a map of [string]object
{
"object1": {
"key1": "value1",
"key2": "value2"
},
"object2": {
"key1": "value3",
"key2": "value4"
}
}
fmt.Sprintf
s.Token struct:
type Token struct {
Type TokenType
DataAtom atom.Atom
Data string
Attr []Attribute
}
Type
can give us information about what kind of token it is. Important ones for this exercise are:
StartTagToken
: <a href>
EndTagToken
: </a>
TextToken
: Text in between. Using text nodes will skip other elements inside the link.Data
contains the data in the node.
a
.Attribute is of type:
type Attribute struct {
Namespace, Key, Val string
}
Key
is the name of the attribute and Value
is the value.
<a href="example.net">
: key
= href
and value
= example.net
.Strings are immutable, use this to append to strings for better efficiency.
Example: https://golang.org/pkg/strings/#example_Builder
var sb strings.Builder // Create the builder.
sb.WriteString("whatever") // Write to it. We can use fmt.Sprintf as param too.
return sb.String() // Get the final string.
We can also pass one as a pointer as io.Writer
. For example, json.NewEncoder(&sb)
.
Get an io.Reader
from a string.
reader: = strings.NewReader("Whatever")
Parse(rawurl string) (*URL, error)
URL will give you tons of utilities:
type URL struct {
Scheme string
Opaque string // encoded opaque data
User *Userinfo // username and password information
Host string // host or host:port
Path string // path (relative paths may omit leading slash)
RawPath string // encoded path hint (see EscapedPath method)
ForceQuery bool // append a query ('?') even if RawQuery is empty
RawQuery string // encoded query values, without '?'
Fragment string // fragment for references, without '#'
}
IsAbs()
returns true if path is absolute.
Hostname()
returns host and port.
Contents are case-sensitive.
Get the complete URL with URL.String()
.
var Discard io.Writer = devNull(0)
break
or continue
to label.
Mainloop:
for {
select {
case whatever:
//
default:
// Do what you want
break Mainloop
}
}
string(ch)
"a"
is a string, 'a'
is a rune.string('a')
.rune("a")
.int("a")
.Copy
slice of results to another variable to use it outside.Read this:
Code: https://github.com/zupzup/boltdb-example
key := []byte(time.Now().Format(time.RFC3339))
And later we can search with seek
.
time.Add
gets a time.Duration
and returns time.Time
:func (t Time) Add(d Duration) Time
time.Sub
gets a time.Time
and returns time.Duration
:func (t Time) Sub(u Time) Duration
Obviously, both support negative values.
You can multiple time.Duration
by a constant (e.g. time.Hours * 2
) but cannot multiply it by an int
variable with value of 2 (e.g. time.Hours * n
).
n
needs to be converted to in64
and then passed to time.Duration(int64)
. For example, to go back n
hours:
past := time.Now().Add(-time.Duration(int64(n)) * time.Hour)
Troubleshooting:
panic: sql: unknown driver "sqlite3" (forgotten import?)
import _ "github.com/mattn/go-sqlite3"
Problem:
# github.com/mattn/go-sqlite3
exec: "gcc": executable file not found in %PATH%
Solution: Install https://sourceforge.net/projects/mingw-w64/.
Good examples:
Some troubleshooting:
Queryx
and StructScan
.db:"table-column"
.Stringer package can generate String()
for types. In this case, we can use it to make one for Suit
and Value
types.
go get golang.org/x/tools/cmd/stringer
deck/card.go
).//go:generate stringer -type=Suit,Value
`go generate
inside the deck
directory.suit_string.go
.Suit.String()
and it will return a string.filepath.Walk can be used to traverse all files in a path recursively.
func Walk(root string, walkFn WalkFunc) error
root
is the starting path and WalkFunc
is a function that is called after visiting each file:
func(path string, info os.FileInfo, err error) error
os.FileInfo has a bunch of methods:
// A FileInfo describes a file and is returned by Stat and Lstat.
type FileInfo interface {
Name() string // base name of the file
Size() int64 // length in bytes for regular files; system-dependent for others
Mode() FileMode // file mode bits
ModTime() time.Time // modification time
IsDir() bool // abbreviation for Mode().IsDir()
Sys() interface{} // underlying data source (can return nil)
}
So to list everything in a directory (main0.go):
func main() {
// Make a list of all files in sample.
err := filepath.Walk("sample", walkWithMe0)
if err != nil {
log.Println(err)
}
}
// walkWithMe0 returns info about files.
func walkWithMe0(file string, info os.FileInfo, err error) error {
// Now we can do what we want with os.FileInfo.
fmt.Printf("Visiting %v\n", info.Name())
return nil
}
walkWithMe
is good for listing things but bad for saving info. The easiest way to pass into out is using an anonymous (or inline) function.
func main() {
// Make a list of all files in sample.
err := filepath.Walk("sample", func(file string, info os.FileInfo, err error) error {
// Now we can do what we want with os.FileInfo.
fmt.Printf("Visiting %v\n", info.Name())
return nil
})
if err != nil {
log.Println(err)
}
}
ext
at: filepath.Ext Notes.[]byte
(remember to convert to string before printing).[]byte
that gets filled.See this: * https://upgear.io/blog/golang-tip-wrapping-http-response-writer-for-middleware/
Embed stuff in structs to use them.
type myRW struct {
http.ResponseWriter
}
https://tour.golang.org/methods/15
t, ok := i.(T)
if ok {
// i.T is implemented and stored in T
}
When doing http.Error
the result will be sent as text and not text/html
.
Use fmt.Fprintf(w, ...)
instead.
Already familiar because it's used in Hugo.
quick.Highlight
sacrifices control but does things quickly:
quick.Highlight(os.Stdout, someSourceCode, "go", "html", "monokai")
For more control use formatter.Format(w io.Writer, s *Style, it Iterator)
:
// Highlighter is a more hands-on version of QuickHighlighter and comes with
// lines highlight support.
func Highlighter(fileName, source, style string, lineno int) (string, error) {
// If styleText does not match any style, it will return "swapoff."
// So we check if we entered "swapoff" and if not, we will change it to
// "solarized-dark."
st := styles.Get(style)
if st.Name == "swapoff" && style != "swapoff" {
st = styles.Get("solarized-dark")
}
// While we already know we are looking at Go code, we are going to
// Chroma's lexer.Analyze to get the lexer based on extension.
// https://github.com/alecthomas/chroma#identifying-the-language
lexer := lexers.Match(fileName)
// lexer is nil if no match could be found.
if lexer == nil {
// So we use the Analyse function.
lexer = lexers.Analyse(source)
}
// Default to Go, if neither process detected a lexer.
if lexer == nil {
lexer = lexers.Get("go")
}
// Create the range variable for highlighting line numbers.
// It's of type [][2]int.
hl := [][2]int{[2]int{lineno, lineno}}
// We are only highlighting one line so both items in the [2]int array
// are the same. If we wanted to highlight a range, we would have used
// start and finish line numbers.
// Create a customized html.Formatter.
// We can also get rid of the 8 tab space now.
formatter := html.New(html.Standalone(), html.WithLineNumbers(),
html.HighlightLines(hl), html.TabWidth(4))
// Get iterator.
it, err := lexer.Tokenise(nil, source)
if err != nil {
return "", err
}
var b bytes.Buffer
wri := bufio.NewWriter(&b)
if err := formatter.Format(wri, st, it); err != nil {
return "", err
}
return b.String(), nil
}
When decoding from or encoding to an io.Reader/Writer
(e.g. file, HTTP response), we can do this:
var []obj MyStruct
// fill in []obj
// File to encode stuff to.
f, _ := os.Create("whatever.txt")
enc := json.NewEncoder(f)
if err := enc.Encode(obj); err != nil {
// Handle error
}
// Now json is saved to file.
To decode, we can do something similar with an io.Reader
(e.g. file).
var []obj2 MyStruct
f, _ := os.Open("whatever.txt")
dec := json.NewDecoder(f)
if err := enc.Decode(&obj2); err != nil {
// Handle error
}
// Now json is populated from file.
Docs: https://developer.twitter.com/en/docs/basics/authentication/overview/application-only
base64(Key:Secret)
.http
package.Send the following POST request to https://api.twitter.com/oauth2/token to get the bearer token.
POST /oauth2/token HTTP/1.1
Host: api.twitter.com
User-Agent: Whatever
Authorization: Basic [token from step 2]
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
Accept-Encoding: gzip
grant_type=client_credentials
Response will have the bearer token if successful (and a 200 OK status)
{"token_type":"bearer","access_token":"AAAAAAAAAAAAAAAAAAAAAAAAAA"}
Use the token in the header of every request Authorization: Bearer AAAAAAAAAAAAAAAAAAAAAAAAAA
???
Profit
GET request to https://api.twitter.com/1.1/statuses/retweets/tweetID.json?count=100.
Results has these fields.
[
{
// ...
"user": {
// ...
"id": 281679947,
"id_str": "281679947",
"is_translation_enabled": false,
"is_translator": false,
"lang": "en",
"listed_count": 43,
"location": "NYC",
"name": "Christine Romo",
// ...
"screen_name": "romoabcnews",
}
}
]
We want to read id_str
and screen_name
so we unmarshal the JSON to []Retweeter
where:
// User represents a user.
type User struct {
Id string `json:"id_str"`
Name string `json:"screen_name"`
}
// Retweeter represents a user who has retweeted the content.
type Retweeter struct {
TwitterUser User `json:"user"`
}
This is pretty cool. You can see it inside Encrypter
and Decrypter
.
In short, you pass an io.Reader
or io.Writer
to another and chain them. Then you write to one (or read from one) and encryption/decryption works. We have already seen this in a previous lesson where we used json.NewDecoder/NewEncoder
on files or buffers.
"TeeReader returns a Reader that writes to w what it reads from r. All reads from r performed through it are matched with corresponding writes to w. There is no internal buffering ..."
I did not use it in this lesson, but seems like a useful thing.
Pass the arguments inside launch.json
like this.
"args": [
"get",
"test",
"yolo",
"key3",
],
We can use something like this
<input type="file"
id="upload" name="upload"
accept="image/jpeg,image/png" />
This only shows files of type jpeg
and png
. We can also do image/*
to show all images.
Assuming we have this enum:
type EnumType int
const (
Zero Enum = iota
One
Two
Three
)
We can convert an int to this type with EnumType(2)
.
Gets the first file in the param (usually POST body).
file, header, err := r.FormFile("upload")
file
can be used like any other file (hint: implements io.Reader
).header
has info about the file like name and size.Response.PostForm is a map of url.Values
(map[string][]string
).
Don't use reflect
I guess. But this is fun. Seems like it will panic if unexported fields are accessed.
In this case, our struct only has string fields so we only check for empty string.
v := reflect.ValueOf(c)
for i := 0; i < v.NumField(); i++ {
if v.Field(i).Interface() == "" {
return fmt.Errorf("Corp not initialized. Set value of %s", v.Type().Field(i).Name)
}
}
We can access the value of a field with v.Field(i).Interface()
and its name with v.Type().Field(i).Name
.
Change 2
to get more floating points.
func FloatToString(f float64) string {
return strconv.FormatFloat(f, 'f', 2, 64)
}