{"id":607,"date":"2022-05-09T09:52:00","date_gmt":"2022-05-09T09:52:00","guid":{"rendered":"https:\/\/andrejacobs.org\/?p=607"},"modified":"2022-05-27T16:04:15","modified_gmt":"2022-05-27T16:04:15","slug":"learning-go-day-12","status":"publish","type":"post","link":"https:\/\/andrejacobs.org\/study-notes\/learning-go-day-12\/","title":{"rendered":"Learning Go – Day 12"},"content":{"rendered":"\n
Featured image by Egon Elbre<\/a><\/p>\n\n\n\n One of the topics that you will encounter and even try to understand in your own projects, is just how exactly to structure Go projects.<\/p>\n I will be creating two new GitHub repositories in order to get a better understanding of how package imports work and just how I might open source some code later that can be used by others.<\/p>\n You can find the two repositories here: go-test1<\/a> and go-test2<\/a><\/p>\n HANDY TIP:<\/strong> You can pass Now that I have two working packages in two different git repositories it is time to add functionality to go-test1 and see how versioning will work. From the previous \u201cgo get\u201d we can see that version v0.0.0 was used.<\/p>\n NOTE:<\/strong> And now the penny drops! Go doesn\u2019t seem to have consensus yet on how to do versioning and dependency management. There are a number of tools to manage versions for you and also the concept of vendoring. I will have to dig deeper into this later, for now it is safe to say you really have to consider how you structure your API.<\/p>\n <\/p>\n Notice how it still states v0.0.0, however inspecting the files in my GOPATH I can see it grabbed the latest copy from the git repo. My guess is it always grabs latest master and not actually based on the github release \/ tag.<\/p>\n All in all this was a very worthwhile experiment and I have learned a lot upfront about how I will be tackling my own initial applications, modules and packages.<\/p>\n I certainly need to brush up a bit on what the best versioning and dependency management is for golang.<\/p>\n<\/div>\n","protected":false},"excerpt":{"rendered":" Featured image by Egon Elbre […]<\/p>\nPackage dependencies<\/h2>\n
\n
go-test1<\/code> and cloned it locally. This will provide some code to be consumed by another repository.<\/li>\n
$ cd go-test1\n$ go mod init github.com\/andrejacobs\/go-test1\n<\/code><\/pre>\n
\n
go-test2<\/code> and clone and set it up as well.<\/li>\n<\/ul>\n
Package 1 – Simple quote generator<\/h3>\n
\n
go-test1<\/code> is going to be a simple random quote generator.\n
\n
quote<\/code> provides a type to represent a list of quotes along with a method to generate some random quotes.<\/li>\n
internal<\/code> is used by the generate.go file to generate the random people and quote lines. Also this was a great test to see how Go handles the special
internal<\/code> package importing.<\/li>\n
$ tree\n.\n\u251c\u2500\u2500 LICENSE\n\u251c\u2500\u2500 README.md\n\u251c\u2500\u2500 cmd\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 quote-cli\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 main.go\n\u251c\u2500\u2500 go.mod\n\u251c\u2500\u2500 internal\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 random.go\n\u2514\u2500\u2500 quote\n \u251c\u2500\u2500 generate.go\n \u251c\u2500\u2500 generate_test.go\n \u251c\u2500\u2500 list.go\n \u2514\u2500\u2500 list_test.go\n\n4 directories, 9 files\n<\/code><\/pre>\n
\n
package quote\n\ntype Quote struct {\n\tLine string\n\tWho string\n}\n\ntype QuoteList struct {\n\tQuotes []Quote\n}\n\nfunc (ql *QuoteList) Add(who string, line string) {\n\tq := Quote{\n\t\tWho: who,\n\t\tLine: line,\n\t}\n\n\tql.Quotes = append(ql.Quotes, q)\n}\n<\/code><\/pre>\n
\n
package quote_test\n\nimport (\n\t"testing"\n\n\t"github.com\/andrejacobs\/go-test1\/quote"\n)\n\nfunc TestListAdd(t *testing.T) {\n\tql := "e.QuoteList{}\n\n\tif len(ql.Quotes) > 0 {\n\t\tt.Fatal("QuoteList not initialized correctly")\n\t}\n\n\tql.Add("Person 1", "The quick brown fox")\n\tql.Add("Person 2", "Jumped over the lazy dog")\n\n\tif len(ql.Quotes) != 2 {\n\t\tt.Fatalf("Expected 2 quotes in the list, instead there were: %d", len(ql.Quotes))\n\t}\n}\n<\/code><\/pre>\n
\n
package quote\n\nimport (\n\t"github.com\/andrejacobs\/go-test1\/internal"\n)\n\nfunc (ql *QuoteList) Generate(count int) {\n\n\tfor i := 0; i < count; i++ {\n\t\tperson := internal.RandomPerson()\n\t\tline := internal.RandomLine()\n\t\tql.Add(person, line)\n\t}\n}\n<\/code><\/pre>\n
\n
package quote_test\n\nimport (\n\t"testing"\n\n\t"github.com\/andrejacobs\/go-test1\/quote"\n)\n\nfunc TestGenerate(t *testing.T) {\n\tql := "e.QuoteList{}\n\n\texpectedCount := 5\n\tql.Generate(expectedCount)\n\tif len(ql.Quotes) != expectedCount {\n\t\tt.Fatalf("Expected %d quotes to be generated, instead we got %d", expectedCount, len(ql.Quotes))\n\t}\n}\n<\/code><\/pre>\n
\n
package internal\n\nimport "math\/rand"\n\nfunc RandomPerson() string {\n\tvar people = []string{\n\t\t"Jannie",\n\t\t"Sannie",\n\t\t"Pieter",\n\t\t"Stephan",\n\t\t"Kobus",\n\t}\n\n\tperson := people[rand.Intn(len(people))]\n\treturn person\n}\n\nfunc RandomLine() string {\n\tvar lines = []string{\n\t\t"Lorem ipsum dolor sit amet, consectetur adipiscing elit.",\n\t\t"Mauris at metus in lectus cursus placerat.",\n\t\t"Quisque gravida mauris vel ante luctus, eget congue est ornare.",\n\t\t"Sed semper mauris nec commodo pharetra.",\n\t\t"Fusce nec elit sagittis, sodales nulla a, vehicula libero.",\n\t\t"Mauris dignissim libero nec neque tristique, nec fringilla nunc pretium.",\n\t\t"Maecenas fermentum diam eget justo euismod bibendum.",\n\t\t"Nam euismod sapien vitae quam dapibus molestie.",\n\t\t"Sed ac ligula eget ipsum semper pharetra.",\n\t\t"Morbi vestibulum augue quis nulla pharetra, at finibus leo consectetur.",\n\t}\n\n\tline := lines[rand.Intn(len(lines))]\n\treturn line\n}\n<\/code><\/pre>\n
\n
package main\n\nimport (\n\t"flag"\n\t"fmt"\n\t"io"\n\t"os"\n\n\t"github.com\/andrejacobs\/go-test1\/quote"\n)\n\nfunc main() {\n\tcountFlag := flag.Int("count", 1, "The number of quotes to print on STDOUT")\n\tflag.Parse()\n\n\tif err := run(os.Stdout, *countFlag); err != nil {\n\t\tfmt.Fprintln(os.Stderr, err)\n\t\tos.Exit(1)\n\t}\n}\n\nfunc run(out io.Writer, count int) error {\n\tql := "e.QuoteList{}\n\tql.Generate(count)\n\n\tfor _, quote := range ql.Quotes {\n\t\t_, err := fmt.Fprintf(out, "%q - %s\\n", quote.Line, quote.Who)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n<\/code><\/pre>\n
\n
$ cd quote\n$ go test -v\n=== RUN TestGenerate\n--- PASS: TestGenerate (0.00s)\n=== RUN TestListAdd\n--- PASS: TestListAdd (0.00s)\nPASS\nok github.com\/andrejacobs\/go-test1\/quote 0.225s\n<\/code><\/pre>\n
\n
$ cd cmd\/quote-cli\n$ go run .\n"Nam euismod sapien vitae quam dapibus molestie." - Sannie\n\n$ go run . -count 5\n"Nam euismod sapien vitae quam dapibus molestie." - Sannie\n"Morbi vestibulum augue quis nulla pharetra, at finibus leo consectetur." - Pieter\n"Sed ac ligula eget ipsum semper pharetra." - Sannie\n"Lorem ipsum dolor sit amet, consectetur adipiscing elit." - Jannie\n"Lorem ipsum dolor sit amet, consectetur adipiscing elit." - Sannie\n<\/code><\/pre>\n
Package 2 – Sorted quote generator<\/h3>\n
\n
go-test2<\/code> is going to use
go-test1<\/code> to generate random quotes and extend it by adding the ability to have the quotes sorted alphabetically.<\/li>\n
go-test1<\/code> by using
go get<\/code><\/li>\n<\/ul>\n
$ go get -u -x github.com\/andrejacobs\/go-test1@latest\n...\ngo: downloading github.com\/andrejacobs\/go-test1 v0.0.0-20220509114742-d186c44a2d88\n<\/code><\/pre>\n
-x<\/code> to
go get<\/code> so that it prints out all the commands being run. This is handy for debugging why git or ssh is failing.<\/p>\n
\n
$(go env GOPATH)\/pkg\/mod\/github.com\/andrejacobs\/go-test1@v0.0.0-20220509114742-d186c44a2d88<\/code><\/li>\n
$ cat go.mod\nmodule github.com\/andrejacobs\/go-test2\n\ngo 1.18\n\nrequire github.com\/andrejacobs\/go-test1 v0.0.0-20220509114742-d186c44a2d88 \/\/ indirect\n<\/code><\/pre>\n
\n
\n
sorted<\/code> will provide a new sorted QuoteList that wraps the quote.QuoteList type from go-test1 module.<\/li>\n
$ tree\n.\n\u251c\u2500\u2500 LICENSE\n\u251c\u2500\u2500 README.md\n\u251c\u2500\u2500 cmd\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 sorted-quote-cli\n\u2502\u00a0\u00a0 \u2514\u2500\u2500 main.go\n\u251c\u2500\u2500 go.mod\n\u251c\u2500\u2500 go.sum\n\u2514\u2500\u2500 sorted\n \u251c\u2500\u2500 quoteList.go\n \u2514\u2500\u2500 sort_test.go\n\n3 directories, 7 files\n<\/code><\/pre>\n
\n
\n
package sorted\n\nimport (\n\t"sort"\n\n\t"github.com\/andrejacobs\/go-test1\/quote"\n)\n\ntype QuoteList struct {\n\tquote.QuoteList\n}\n\nfunc (ql *QuoteList) Sort() {\n\tsort.SliceStable(ql.Quotes, func(i, j int) bool {\n\t\treturn ql.Quotes[i].Line < ql.Quotes[j].Line\n\t})\n}\n\nfunc (ql *QuoteList) Add(who string, line string) {\n\tql.QuoteList.Add(who, line)\n\tql.Sort()\n}\n<\/code><\/pre>\n
\n
package sorted_test\n\nimport (\n\t"testing"\n\n\t"github.com\/andrejacobs\/go-test2\/sorted"\n)\n\nfunc TestSort(t *testing.T) {\n\tql := &sorted.QuoteList{}\n\n\tql.Add("p1", "Bravo")\n\tql.Add("p2", "Zebra")\n\tql.Add("p3", "Alpha")\n\n\tif ql.Quotes[0].Line != "Alpha" &&\n\t\tql.Quotes[1].Line != "Bravo" &&\n\t\tql.Quotes[2].Line != "Zebra" {\n\t\tt.Fatal("Expected the quotes to be sorted")\n\t}\n}\n<\/code><\/pre>\n
\n
package main\n\nimport (\n\t"flag"\n\t"fmt"\n\t"io"\n\t"os"\n\n\t"github.com\/andrejacobs\/go-test2\/sorted"\n)\n\nfunc main() {\n\tcountFlag := flag.Int("count", 1, "The number of quotes to print on STDOUT")\n\tflag.Parse()\n\n\tif err := run(os.Stdout, *countFlag); err != nil {\n\t\tfmt.Fprintln(os.Stderr, err)\n\t\tos.Exit(1)\n\t}\n}\n\nfunc run(out io.Writer, count int) error {\n\tql := &sorted.QuoteList{}\n\tql.Generate(count)\n\tql.Sort()\n\n\tfor _, quote := range ql.Quotes {\n\t\t_, err := fmt.Fprintf(out, "%q - %s\\n", quote.Line, quote.Who)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n<\/code><\/pre>\n
\n
$ cd sorted\n$ go test -v\n=== RUN TestSort\n--- PASS: TestSort (0.00s)\nPASS\nok github.com\/andrejacobs\/go-test2\/sorted 0.185s\n<\/code><\/pre>\n
\n
$ cd cmd\/sorted-quote-cli\n$ go run . -count 5\n"Lorem ipsum dolor sit amet, consectetur adipiscing elit." - Jannie\n"Lorem ipsum dolor sit amet, consectetur adipiscing elit." - Sannie\n"Morbi vestibulum augue quis nulla pharetra, at finibus leo consectetur." - Pieter\n"Nam euismod sapien vitae quam dapibus molestie." - Sannie\n"Sed ac ligula eget ipsum semper pharetra." - Sannie\n<\/code><\/pre>\n
Versioning<\/h3>\n
\n
jsonquote<\/code> that will simply be used to encode the QuoteList into JSON.<\/li>\n<\/ul>\n
jsonquote\n\u251c\u2500\u2500 json.go\n\u2514\u2500\u2500 json_test.go\n<\/code><\/pre>\n
\n
package jsonquote\n\nimport (\n\t"encoding\/json"\n\t"io"\n\n\t"github.com\/andrejacobs\/go-test1\/quote"\n)\n\nfunc Encode(out io.Writer, ql *quote.QuoteList) {\n\tencoder := json.NewEncoder(out)\n\tencoder.Encode(ql)\n}\n<\/code><\/pre>\n
\n
package jsonquote_test\n\nimport (\n\t"bytes"\n\t"testing"\n\n\t"github.com\/andrejacobs\/go-test1\/jsonquote"\n\t"github.com\/andrejacobs\/go-test1\/quote"\n)\n\nfunc TestJsonEncoding(t *testing.T) {\n\tql := "e.QuoteList{}\n\tql.Add("p1", "line1")\n\tql.Add("p2", "line2")\n\tql.Add("p3", "line3")\n\n\tvar result bytes.Buffer\n\tjsonquote.Encode(&result, ql)\n\n\texpected := "{\\"Quotes\\":[{\\"Line\\":\\"line1\\",\\"Who\\":\\"p1\\"},{\\"Line\\":\\"line2\\",\\"Who\\":\\"p2\\"},{\\"Line\\":\\"line3\\",\\"Who\\":\\"p3\\"}]}\\n"\n\tif result.String() != expected {\n\t\tt.Fatalf("Expected json:\\n%q\\n\\nInstead we received:\\n%q", expected, result.String())\n\t}\n}\n<\/code><\/pre>\n
\n
package main\n\nimport (\n\t"flag"\n\t"fmt"\n\t"io"\n\t"os"\n\n\t"github.com\/andrejacobs\/go-test1\/jsonquote"\n\t"github.com\/andrejacobs\/go-test1\/quote"\n)\n\nfunc main() {\n\tcountFlag := flag.Int("count", 1, "The number of quotes to print")\n\tjsonFlag := flag.Bool("json", false, "Encode the quotes in JSON")\n\tflag.Parse()\n\n\tif err := run(os.Stdout, *countFlag, *jsonFlag); err != nil {\n\t\tfmt.Fprintln(os.Stderr, err)\n\t\tos.Exit(1)\n\t}\n}\n\nfunc run(out io.Writer, count int, outputJson bool) error {\n\tql := "e.QuoteList{}\n\tql.Generate(count)\n\n\tif outputJson {\n\t\tjsonquote.Encode(out, ql)\n\t} else {\n\t\tfor _, quote := range ql.Quotes {\n\t\t\t_, err := fmt.Fprintf(out, "%q - %s\\n", quote.Line, quote.Who)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n<\/code><\/pre>\n
\n
$ cd jsonquote\n$ go test -v\n=== RUN TestJsonEncoding\n--- PASS: TestJsonEncoding (0.00s)\nPASS\nok \tgithub.com\/andrejacobs\/go-test1\/jsonquote\t0.122s\n\n$ cd ..\/cmd\/quote-cli\n$ go run . -count 3 -json\n{"Quotes":[{"Line":"Nam euismod sapien vitae quam dapibus molestie.","Who":"Sannie"},{"Line":"Morbi vestibulum augue quis nulla pharetra, at finibus leo consectetur.","Who":"Pieter"},{"Line":"Sed ac ligula eget ipsum semper pharetra.","Who":"Sannie"}]}\n<\/code><\/pre>\n
\n
\n
$ go get -u -x github.com\/andrejacobs\/go-test1@latest\n<\/code><\/pre>\n
\n
-json<\/code> argument support to go-test2\u2019s CLI and tested it.<\/li>\n<\/ul>\n
$ go run . -count 3 -json\n{"Quotes":[{"Line":"Morbi vestibulum augue quis nulla pharetra, at finibus leo consectetur.","Who":"Pieter"},{"Line":"Nam euismod sapien vitae quam dapibus molestie.","Who":"Sannie"},{"Line":"Sed ac ligula eget ipsum semper pharetra.","Who":"Sannie"}]}\n<\/code><\/pre>\n
Finishing up<\/h3>\n