Repository: softchris/golang-book Branch: main Commit: f9411a8145cb Files: 121 Total size: 206.1 KB Directory structure: gitextract_u3bn0g8o/ ├── .gitignore ├── .nojekyll ├── 01-basics/ │ ├── 01-hello/ │ │ ├── README.md │ │ ├── assignment.md │ │ ├── go.mod │ │ └── main.go │ ├── 02-variables/ │ │ ├── README.md │ │ ├── exercise.go │ │ └── main.go │ ├── 03-if-and-else/ │ │ ├── README.md │ │ └── main.go │ ├── 04-conversions/ │ │ ├── README.md │ │ ├── assignment.go │ │ ├── main.go │ │ └── reflecting.go │ ├── 05-loops/ │ │ ├── README.md │ │ └── main.go │ ├── 06-user-input/ │ │ ├── README.md │ │ ├── go.mod │ │ └── main.go │ ├── 07-functions/ │ │ ├── README.md │ │ ├── main.go │ │ └── test.go │ └── 08-error-handling/ │ ├── README.md │ ├── logs │ ├── main.go │ └── panic.go ├── 02-data-types/ │ ├── 01-arrays/ │ │ ├── README.md │ │ ├── assignment.go │ │ ├── main.go │ │ └── slice.go │ ├── 02-structs / │ │ ├── README.md │ │ ├── assignment.go │ │ ├── go.mod │ │ └── main.go │ ├── 03-maps/ │ │ ├── README.md │ │ ├── assignment.go │ │ └── main.go │ └── 04-interfaces/ │ ├── README.md │ ├── assignment.go │ ├── cast.go │ ├── main.go │ ├── shape.go │ └── test.go ├── 03-projects/ │ ├── 01-first-project/ │ │ ├── README.md │ │ ├── go.mod │ │ ├── helper/ │ │ │ └── helper.go │ │ └── main.go │ ├── 02-consume-external/ │ │ ├── README.md │ │ ├── go.mod │ │ ├── go.sum │ │ ├── log-tester/ │ │ │ ├── go.mod │ │ │ ├── go.sum │ │ │ ├── helper/ │ │ │ │ └── helper.go │ │ │ └── main.go │ │ └── main.go │ ├── 03-create-shared-module/ │ │ └── README.md │ └── 04-testing/ │ ├── README.md │ ├── go.mod │ ├── main.go │ └── math/ │ ├── c.out │ ├── math.go │ └── math_test.go ├── 04-webdev/ │ ├── 01-json/ │ │ ├── README.md │ │ ├── main.go │ │ ├── orders.go │ │ ├── orders.json │ │ └── person.json │ └── 02-web-dev/ │ ├── README.md │ └── main.go ├── 05-misc/ │ ├── 01-logs/ │ │ ├── README.md │ │ ├── batch.go │ │ ├── logfile │ │ ├── main.go │ │ ├── records.csv │ │ └── testlogfile │ ├── 02-strings/ │ │ ├── README.md │ │ ├── contains.go │ │ ├── presentation.go │ │ └── strings.go │ ├── 03-regex/ │ │ ├── README.md │ │ ├── regex.go │ │ └── regex2.go │ ├── 04-goroutines/ │ │ ├── README.md │ │ ├── channel.go │ │ ├── channel1.go │ │ ├── file-search.go │ │ ├── first.go │ │ ├── main.go │ │ ├── other/ │ │ │ ├── test.txt │ │ │ └── test3.txt │ │ └── test/ │ │ ├── test.txt │ │ └── test2.txt │ └── 05-sqlite/ │ ├── README.md │ ├── go.mod │ ├── go.sum │ └── main.go ├── 06-io/ │ ├── 01-read-write-files/ │ │ ├── README.md │ │ ├── invoices.csv │ │ └── main.go │ ├── 02-file-directories/ │ │ ├── README.md │ │ ├── main.go │ │ └── tmp/ │ │ ├── a.txt │ │ └── b.txt │ ├── 03-compress-files/ │ │ └── README.md │ └── fix/ │ ├── README.md │ ├── dir/ │ │ └── dir.go │ ├── file/ │ │ └── file.go │ ├── go.mod │ ├── go.sum │ ├── main.go │ ├── products.json │ ├── test.txt │ └── test2.txt ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── _config.yml ├── _layouts/ │ └── default.html ├── _sidebar.md └── index.html ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ logger .DS_Store ================================================ FILE: .nojekyll ================================================ ================================================ FILE: 01-basics/01-hello/README.md ================================================ # Your first program This lesson covers some history of Go and also teaches you how to build your first Go app. > Watch the video > [![your first Go program](https://img.youtube.com/vi/1825FjiewWs/0.jpg)](https://www.youtube.com/watch?v=1825FjiewWs) ## Introduction In this lesson we'll cover: - The history of Go - Why use Go for your apps - The anatomy of a Go app - Authoring and running your first app ## A history of Go The language is called Go but is sometimes known as Golang as the first website for it was golang.org. Go was created in 2009 by Robert Griesemer, Rob Pike and Ken Thompson. It's hard to estimate the number of Go developers but it's somewhere between 1.1 and 2.7 million, quite a sizeable amount. More than 2500 companies are using Go including, Google, Pinterest and Uber. So, you see, used by a lot of folks by big companies. > Why was Go created? As is often the case, a programming language is created to deal with the shortcomings of other languages. In this case, the creators wanted this new language to have the following capabilities: - **Static typing** and run-time efficiency from C. - **Readability** from JavaScript and Python. - **High-performance** networking and multi-processing. It seems the creators agreed on disliking C++ :) ## What is it used for though? Here's some areas where you are likely to find a Go being used: - Cloud based and server-side apps. - DevOps, automation. - Command-line tools. - AI and data science. ## References There are many great resources out there for learning the Go programming language like: - - - - - - ## Features So, what features makes Go compelling? Well, there are some features worth mentioning: - **Static typing**, I like my types :) - **Package system**. You can consume and create your own packages. Go to [pkg.go.dev](https://pkg.go.dev/) to read more on what packages there are. - **Command-line tools**, there's a set of executables that are installed when you install Go. With these executables, you can run, build, install packages, run tests and much more. - **Standard library**. Go has a powerful standard library that will help you with most things you might need. You can read more about what's in the [standard library](https://pkg.go.dev/std) here. - **Built-in testing**. Having a testing library that just works out of the box is something you shouldn't take for granted. - Concurrency. Go is great at handling concurrency. It uses concepts like goroutines and channels. - **Garbage collection**. You can read more about that [here](https://medium.com/safetycultureengineering/an-overview-of-memory-management-in-go-9a72ec7c76a8#:~:text=Go%20has%20all%20goroutines%20reach,the%20collector%20to%20run%20simultaneously). I like when I don't have to deal with that myself and just focusing on solving problems. ## Install Go Ok then, hope you are intrigued at this point and just want to see some code? Of course, you are :) Make sure you've followed the instructions for installing Go on your machine. > ## A Go program Here's what a first program can look like: ```go package main import "fmt" func main() { fmt.Println("hello") } ``` ### The program in detail - `package main`, the entry point module needs to have this instruction. - `import "fmt"`, fmt is standard package for input and output. - `func main`, entry point function, where your program starts. ## Commands Now that you have a program, there's two things you might want to do: - **Run it**, to see if it compiles and runs. - **Create executable**, an executable is no longer Go code but like any executable program on your machine. ### Run your app To run your app, type `go run .go`, for example: ```bash go run main.go ``` ### Build your app To produce an executable, run `go build .go`, for example: ```bash go build main.go ``` It produces an executable, on MacOS and Linux that's a file with -X as permission, on Windows, it's a .exe file. Congrats, you've created your first Go application. ## Summary In this article, you learned about the programming language Go, some features it has and how to write your first program. ## 🚀 Challenge Compare Go to other programming languages, can you list some differences between them? ## Review & Self Study Select one of the resources below and try do a tutorial. - - - - - - ## Assignment Create a file *main.go*. Use the `fmt` library to print out to the console. Remember that your run programs with `go run .go`. ## Solution Create a file *main.go* ```go package main import "fmt" func main() { fmt.Println("printing to the console") } ``` ================================================ FILE: 01-basics/01-hello/assignment.md ================================================ # Build an app ## Instructions Create a file *main.go*. Use the `fmt` library to print out to the console. Remember that your run programs with `go run .go`. ================================================ FILE: 01-basics/01-hello/go.mod ================================================ module hello go 1.17 ================================================ FILE: 01-basics/01-hello/main.go ================================================ package main import "fmt" func main() { fmt.Println("hello") fmt.Println("hey Chris") var name = "Chris" var age = 20 fmt.Printf("%s again, %d", name, age) } ================================================ FILE: 01-basics/02-variables/README.md ================================================ # Using variables With variables, we can remember values and later refer to them via named references. using variables will make our code easier to read. ## Introduction In this lesson we'll cover: - The usage of variables in Go. - How to create them. - Assign different types and values. ## Declare variables In Go, there are many ways to declare variables: - **Define a name and type**. Here, you declare a variable with the keyword `var`, give it a name and lastly a type `string`. Below is an example: ```golang var firstName string ``` - **Define a group** of variables. It's possible to define a group of variables. Using this way of declaring means you only type the `var` keyword once. The group is defined using parenthesis `()`: ```golang var ( firstName = "Chris" age = 20 ) ``` Note how each variable is on a new row. - **Define and assign a value**. Within functions, you can use the `:=` operator, it declares and assigns at the same time. The below code shows the creation of the `firstName` variable. The data type is inferred to be a string: ```go firstName := "Chris" ``` ## Assign variables To assign a new value to a variable, it needs to exist first. You use the assignment operator, `=`. Here's an example: ```go firstName = "Mike" ``` ## Data types There are many data types you can use with Go. They are divided into different categories: - **Basic types**. In this category, we find types like integers, floats (numbers with decimals) and other types like Booleans (for true/false), strings (for text) and more. - **Composite types**. We will talk about composite types in a separate article, but they are more complex, and examples of composite types are arrays, structs and interfaces. ### Declare a variable with a type There are two ways you can declare a variable and give it a type: - **explicitly**, by specifying its type, for example: ```go var name string ``` - **implicitly**, by assigning it a value and having it been inferred: ```go name := "chris" ``` In the preceding code, the data type is inferred by the value you give it. In this case, the data type becomes `string` based on the value "chris". ## String interpolation Sometimes, you want to be able to write things to the screen and mix different data types doing so. For example, you might want to write, "Customer: Adam has 20$ in his bank account". Let's say then that this information is represented by these two variables: ```go var ( customerName = "Adam" accountBalance = 20 ) ``` How can you print out the text above? For this purpose, you can use the `Printf()` function that takes formatters. The idea is that a formatter is an instruction to what a certain type is. By providing this information to `Printf()`, it's able to print the type correctly. Here's how you can print the example string from before: ```go fmt.Printf("Customer %s has %d$ on their bank account", customerName, accountBalance) ``` Above, the `%s` represents a string and `%d` represents a number. By using these formatters as placeholders, the variables are correctly implemented, and the output becomes: ```output Customer Adam has 20$ on their bank account ``` ## Assignment - define some variables and print them out Define some variables you might need for the card game Texas Holdem and print them out. Create a file *main.go* and give it the following content: ```go package main import "fmt" func main () { } ``` 1. Add the following variables after the import section: ```go var ( players = 3 replay = false namePlayerOne = "chris" ) ``` Now you have: - `players`, to represent the number of players in the game. - `replay`, a boolean stating whether to start a new game session when the old one has ended. - `namePlayerOne`, a string representing the name of the first player. All of these variables help describe essential information in a Texas Holdem game. Next, let's run our app to make sure it works. 1. Add the following code to the `main()` function to print out the variables: ```go fmt.Println(players) fmt.Println(replay) fmt.Println(namePlayerOne) ``` 1. Run `go run main.go` in the terminal: ```go go run main.go ``` You should see the following output: ```output 3 false chris ``` Great, you now have a starting point for an app you can keep building on. ## 🚀 Challenge See if you can come up with more variables to represent the state in a Texas Holdem card game, like for example, other players, the card deck etc. What data type would you give those variables? ## Review & Self Study Have a look at this [official tutorial on variables](https://go.dev/tour/basics/8) using a Go sandbox ## Solution ```go package main import "fmt" var ( players = 3 replay = false namePlayerOne = "chris" ) func main () { fmt.Println(players) fmt.Println(replay) fmt.Println(namePlayerOne) } ``` ================================================ FILE: 01-basics/02-variables/exercise.go ================================================ // defining variables // with and without type // the other way with := // package main // import "fmt" // var ( // name string // ) // func main() { // name = "chris" // fmt.Println(name) // } ================================================ FILE: 01-basics/02-variables/main.go ================================================ package main import "fmt" var ( players = 3 replay = false namePlayerOne = "chris" PI = 3.14 customerName = "Adam" accountBalance = 20 ) func main() { fmt.Println(players) fmt.Println(replay) fmt.Println(namePlayerOne) fmt.Println(PI) fmt.Printf("Customer %s has %d on his bank account", customerName, accountBalance) } ================================================ FILE: 01-basics/03-if-and-else/README.md ================================================ # Flow control In this chapter, we're looking to learn about constructs `if` and `else` to control the flow of your application. ## Introduction This chapter will cover: - Working with Boolean logic. - Create Boolean data. - Use constructs like `if`, `else if` and `else`. ## What is flow control Using Boolean logic in your program is about creating different execution paths through your code? > What does that mean? It means there's more than one way that your program can run depending on what data you feed it. > Ok, can you show me? Sure, consider this code: ```go printMessage := true if printMessage { fmt.Println("Message") } ``` If `printMessage` is `true`, the string "Message" will print. If the value is `false`, nothing will print. > Ok, I think I get it. ## The `if` construct You've seen an example already about code that runs or doesn't run depending on a value. The `if` construct is what makes that possible. An `if` take a Boolean expression like so: ```go if true { // statements here will always run } ``` ### Using a Boolean variable When you use a Boolean value as part of your Boolean expression, it needs to be evaluated. Here's code showing just that: ```go accountBalance = 100 accountCredit = 200 if accountBalance + accountCredit > 0 { fmt.Println("You have money to spend") } ``` The program above does the job, meaning it correctly evaluates whether you have money to spend. However, you might want to print something out if the condition is not met, for that you have `else`. ### Introducing `else` You would like to improve the preceding code. The `else` clause is run when `if` is evaluated to false. Here's how you can add it to the program: ```go accountBalance = 100 accountCredit = 200 if accountBalance + accountCredit > 0 { fmt.Println("You have money to spend") } else { fmt.Println("No money left, please add more funds") } ``` ## Using `else if` `if` and `else` take you far. Sometimes, it's not enough. You might need to grade a course at different levels depending on the points achieved on the exam. For this situation, you need an `else if` construct, a construct that will be evaluated if the `if` construct evaluates to false. It differs from `else` in that it also takes an expression. Here's an example where it's used: ```go if testScore >= testScoreGrade5 { fmt.Println("Top mark") } else if testScore >= testScoreGrade4 { fmt.Println("Pass with distinction") } else if testScore >= testScoreGrade3 { fmt.Println("Pass with distinction") } else { fmt.Println("Failed") } ``` ## Multiple expressions Your expression can examine more than one variable or condition. There are Boolean operators you can use to help you. Here are some operators you are likely to encounter: - `&&`, evaluates to true if values on the left and right side are both true. Here's an example of this operator in use: ```go hasGas := true hasKeyInIgnition := true if hasGas && hasKeyInIgnition { fmt.Println("Can drive car") } ``` In the preceding code, the expression will evaluate to true as both `hasGas` and `hasKeyInIgnition` is true. - `||` , evaluates to true if either left or right value is true. Here's an example of this operator in use: ```go hasBurger := true hasSandwich := false if hasBurger || hasSandwich { fmt.Println("Can eat") } ``` In the preceding code, `hasBurger` is true and that's enough for this expression to become true. - `!`, also known as NOT, it will negate the expression. Here's an example: ```go hasSandwich := false if !hasSandwich { mt.Println("No sandwiches, then I will starve, I only eat sandwiches") } ``` Above, the expression will evaluate to true, thanks to the negation with `!`. ## Assignment - create a program that tests your Boolean logic In this assignment, you are creating a program that tests out various Boolean logic. 1. Create a file *main.go* and give it the following content: ```go package main import "fmt" func main() { testScoreGrade5 := 80 testScoreGrade4 := 60 testScoreGrade3 := 50 testScore := 49 hasGas := true hasKeyInIgnition := true hasBurger := true hasSandwich := false printMessage := true if printMessage { fmt.Println("Message") } if testScore >= testScoreGrade5 { fmt.Println("Top mark") } else if testScore >= testScoreGrade4 { fmt.Println("Pass with distinction") } else if testScore >= testScoreGrade3 { fmt.Println("Pass with distinction") } else { fmt.Println("Failed") } if hasGas && hasKeyInIgnition { fmt.Println("Can drive car") } if hasBurger || hasSandwich { fmt.Println("Can eat") } if !hasSandwich { fmt.Println("No sandwiches, then I will starve, I only eat sandwiches") } } ``` 1. Run the command `go run main.go`, to run the program ```bash go run main.go ``` You should see the following output: ```output Message Failed Can drive car Can eat No sandwiches, then I will starve, I only eat sandwiches ``` 1. Try playing around with the code, how does the output change if you change `testScore` value to 51, 62, 3 or 90? ## 🚀 Challenge A test score shouldn't be negative, how can you add a check for that? ## Solution ```go package main import "fmt" func main() { testScoreGrade5 := 80 testScoreGrade4 := 60 testScoreGrade3 := 50 testScore := 49 hasGas := true hasKeyInIgnition := true hasBurger := true hasSandwich := false printMessage := true if printMessage { fmt.Println("Message") } if testScore >= testScoreGrade5 { fmt.Println("Top mark") } else if testScore >= testScoreGrade4 { fmt.Println("Pass with distinction") } else if testScore >= testScoreGrade3 { fmt.Println("Pass with distinction") } else { fmt.Println("Failed") } if hasGas && hasKeyInIgnition { fmt.Println("Can drive car") } if hasBurger || hasSandwich { fmt.Println("Can eat") } if !hasSandwich { fmt.Println("No sandwiches, then I will starve, I only eat sandwiches") } } ``` ## Review & Self Study Have a look at this [official tutorial on flow control](https://go.dev/tour/flowcontrol/6) using a Go sandbox. ================================================ FILE: 01-basics/03-if-and-else/main.go ================================================ package main import "fmt" func main() { testScoreGrade5 := 80 testScoreGrade4 := 60 testScoreGrade3 := 50 testScore := 49 hasGas := true hasKeyInIgnition := true hasBurger := true hasSandwich := false printMessage := true if printMessage { fmt.Println("Message") } if testScore >= testScoreGrade5 { fmt.Println("Top mark") } else if testScore >= testScoreGrade4 { fmt.Println("Pass with distinction") } else if testScore >= testScoreGrade3 { fmt.Println("Pass with distinction") } else { fmt.Println("Failed") } if hasGas && hasKeyInIgnition { fmt.Println("Can drive car") } if hasBurger || hasSandwich { fmt.Println("Can eat") } if !hasSandwich { fmt.Println("No sandwiches, then I will starve, I only eat sandwiches") } } ================================================ FILE: 01-basics/04-conversions/README.md ================================================ # Converting between types This chapter covers how to convert between strings and numbers. ## Introduction This chapter will: - Introduce uses cases where data conversion makes sense. - Showcase how to use `strconv` library. ## Why convert between types There are different data types and a need to convert between them. For example, we often need to convert between text and numbers for presentational and other reasons. We also need to convert between numbers and decimals without losing information in the process. The main package for dealing with conversions in Go is `strconv`. ## Use case - command-line arguments Let's show a common case where you start off with strings and you need to make it into numbers, command-line arguments. To use command-line arguments in a program, you need the `os` package. `os.Args` points to an array representing your command line arguments. To access a specific argument, you would use the index operator `[]` like so: ```go arg := os.Args[1] ``` You can then start your program like so: ```bash go run main.go 1 ``` The 1 would then be stored in `arg`. ### Finding the type What type is `arg` in our code above? There are some ways to find out: - **IDE**, if you use for example Visual Studio Code and the Go plugin, hovering over the code, it will tell you that `os.Args` is a string array, `string[]`. - **PrintF() and %T**. One of the easiest ways to find the type is typing like so: ```go Printf("%T", os.Args[1]) Printf("%T", 1) ``` You would get an output like so: ```output string int ``` - **Type coercion**, you could try to modify that code and coerce it to be an integer like so, now what? ```go var no int = os.Args[1] ``` You get an error: ```output cannot use os.Args[1] (type string) as type int in assignment ``` - **Use reflection**. Another way to find the above is by using the `reflect` package like so: ```go package main import ( "reflect" "fmt" "os" ) func main () { arg := os.Args[1] fmt.Println(reflect.TypeOf(arg)) } ``` Now, the program will print "string" as the type. ### Addressing the problem with `strconv` Ok, so we know what type something is, what if we need to use these command-line arguments, which are of type `string`, and feed them into let's say a calculator program? Consider the below code, that at present WOULDN'T compile: ```go package main import ( "fmt" "os" ) func add(first int, second int) int { return first + second } func main() { add(os.Args[1], os.Args[2]) // this would NOT compile } ``` The reason is that the values on `os.Args[1]` and `os.Args[2]` are `string` not `int`. To fix this issue, we need to use the conversion package `strconv`. ## Convert from string to int with `strconv` To convert strings to integers, we need to use `strconv` and call the `Atoi()` (stands for Ascii to integer) function like so: ```go package main import ( "fmt" "os" "strconv" ) func add(first int, second int) int { return first + second } func main() { no1, _ := strconv.Atoi(os.Args[1]) no2, _ := strconv.Atoi(os.Args[2]) var sum = add(no1, no2) fmt.Println(sum) } ``` Note `_`, this is a *don't care* symbol. What happens when you call `Atoi()` is that it returns two things, the number and an error if it fails. ### Handling conversion error To handle an error, we need to store it in a variable, `err` and inspect it. If it's not `nil`, then we have an error. Here's how we could encode that behaviour below: ```go package main import ( "fmt" "os" "strconv" ) func main() { no, err := strconv.Atoi(os.Args[1]) fmt.Println(no) if err != nil { fmt.Println(err) fmt.Println("Couldn't convert: " + os.Args[1]) } else { fmt.Println(no) } } ``` Try to compile the above program and run it like so: ```bash main 1 # 1 main hi # trconv.Atoi: parsing "hi": invalid syntax, Couldn't convert: hi ``` ## Parse string to int There's another way to convert a string to an int. That's by using the `ParseInt()` method. It does more than converting though, it does two things in fact: - **base**, you can select according to what base to interpret the number as. - **size**, bit size, from 0 to 64. The syntax for the method looks like so: ```go ParseInt(, , ) (int64, error) ``` Here's some examples: ```go package main import ( "fmt" "reflect" "strconv" ) func main() { var no int = 100 fmt.Println(reflect.TypeOf(no)) var intStr string = "100" fourBaseEightBitInt, _ := strconv.ParseInt(intStr, 4, 8) // becomes no 16 and int64 tenBaseSixteenBitInt, _ := strconv.ParseInt(intStr, 10, 16) // no 100, and int64 fmt.Println(reflect.TypeOf(fourBaseEightBitInt)) fmt.Println(reflect.TypeOf(tenBaseSixteenBitInt)) } ``` ## Integer to string You might be dealing with the opposite; you have an integer, and you want it to be a string. In this case, you can use the `Itoa()` function, integer to ascii. Here's an example: ```go var noOfPlayers = 8 str := strconv.Itoa(noOfPlayers) ``` ## Additional parsing The `strconv` library is what you want if you start with a string, and you want to convert to and from another format. Learn more about [strconv library here](https://pkg.go.dev/strconv) ## Assignment Create an app that adds two numbers together. The values should come from the command line. Here's how the program should run: ```bash go run main.go 2 4 6 ``` ## Solution ```go package main import ( "fmt" "os" "strconv" ) func add(no int, secondNumber int) int { return no + secondNumber } func main() { no1, _ := strconv.Atoi(os.Args[1]) no2, _ := strconv.Atoi(os.Args[2]) var sum = add(no1, no2) fmt.Println(sum) } ``` ## 🚀 Challenge What happens if run the program like so? ```bash go run main.go one two ``` Handle any conversion error and call `panic()` if there's a conversion error. ================================================ FILE: 01-basics/04-conversions/assignment.go ================================================ package main import ( "fmt" "os" "strconv" ) func add(no int, secondNumber int) int { return no + secondNumber } func main() { no1, err := strconv.Atoi(os.Args[1]) if err != nil { fmt.Errorf("Error converting %s to a number", os.Args[1]) panic(err) } no2, err := strconv.Atoi(os.Args[2]) if err != nil { fmt.Errorf("Error converting %s to a number", os.Args[2]) panic(err) } var sum = add(no1, no2) fmt.Println(sum) } ================================================ FILE: 01-basics/04-conversions/main.go ================================================ package main import ( "fmt" "os" "reflect" "strconv" ) func add(first int, second int) int { return first + second } func main() { no := os.Args[1] fmt.Println(reflect.TypeOf(no)) no1, _ := strconv.Atoi(os.Args[1]) no2, _ := strconv.Atoi(os.Args[2]) var sum = add(no1, no2) fmt.Println(sum) fmt.Printf("%T", os.Args[1]) fmt.Printf("%T", 1) } ================================================ FILE: 01-basics/04-conversions/reflecting.go ================================================ package main import ( "fmt" "reflect" "strconv" ) func main() { var no int = 100 fmt.Println(reflect.TypeOf(no)) var intStr string = "100" fourBaseEightBitInt, _ := strconv.ParseInt(intStr, 4, 8) // becomes no 16 and 8bit tenBaseSixteenBitInt, _ := strconv.ParseInt(intStr, 10, 16) // no 100, 16 bit fmt.Println(fourBaseEightBitInt) fmt.Println(tenBaseSixteenBitInt) fmt.Println(reflect.TypeOf(fourBaseEightBitInt)) fmt.Println(reflect.TypeOf(tenBaseSixteenBitInt)) } ================================================ FILE: 01-basics/05-loops/README.md ================================================ # Working with loops This chapter covers working with loops in Go. Loops are used to repeat statements in your code. ## Introduction This chapter will cover: - Loop statements with `for`. - Device conditions on when to break a loop. - Apply `range` to iterate over an array. - Control the loop with `continue` and `break`. ## The case for looping statements You are likely to want to repeat a set of instructions. For example, you might have a list of orders where you need to process each order. Or you have a file that you need to read line by line or there might be some other calculation. Regardless of your situation, you are likely to need a looping construct, so what are your options in Go? You are using the `for` loop. There are three major ways you can use it: - **increment steps**. With the help of a variable, you define a start point, a condition when to stop and an incrementation step. This is your "classic" for-loop. Here's what it looks like: ```go for i := 0; i < 10; i++ { // run these statements } ``` - **while**. In many programming languages you have a `while` keyword. Go doesn't have that, but what you can do is use the for-loop similarly. You omit the initialization step and increment step and get this code: ```go for { // run these statements } ``` - **for each**. lastly, you have the `for-each` like construct that operates on an array-like sequence. It uses the `range` keyword to function: ```go for i,s := range array { // run these statements } ``` ## The `for` loop The conventional for-loop has three different parts: - **initialization**, here you want to create a variable and assign it a starter value like so: ```go for i:= 0; ``` Note the use of `;`, you usually don's use semicolons, but for this construct, you need it. - **condition**. The next step is evaluating whether you should continue incrementing or not. You define a Boolean expression here, that if `true`, continues to loop: ```go for i := 0; i< 10 ``` `i < 10`, as long as a value is between 0 and 10 (becomes 10, then loop breaks), then it returns true, and the loop continues. - **increment**, in this step, the loop variable `i` is updated, updating it by 1 is common but you can add any increment size, negative or positive. ```go for i := 0; i< 10; i++ { } ``` Here, `i` is updated by 1. This loop will run ten times. ## Repeat until the condition is met with `while` A simplified version of this loop can omit the initialization and increment steps. You are then left with the condition step only. This step tests whether a variable is `true` or `false` and the loop exits on false. Here's an example: ```go i := 1 for i < 10 { i++ // do something } ``` In this case, we are declaring `i` outside of the loop. Within the loop, we need to change the value to something that will make the loop expression evaluate to false or it will loop forever. Here's another code, using the same idea, but this time we ask for input from the user: ```go var keepGoing = true answer := "" for keepGoing { fmt.Println("Type command: ") fmt.Scan(&answer) if answer == "quit" { keepGoing = false } } fmt.Println("program exit") ``` An example run of the program could look like so: ```bash Type command: test Type command: something Type command: quit program exit ``` ## Using for-each over a range For this next loop construct, the idea is to operate over an array or known sequence. For each iteration you can get the index as well as the next item in the loop. Here's some example code: ```go arr := []string{"arg1", "arg2", "arg3"} for i, s := range arr { fmt.Printf("index: %d, item: %s \n", i, s) } ``` `arr` is defined as an array and then the `range` construct is used to loop over the array. For each iteration, the current index is assigned to `i` and the array item is assigned to `s`. An output of the above code will look like so: ```output index: 0, item: arg1 index: 1, item: arg2 index: 2, item: arg3 ``` ## Controlling the loop with `continue` and `break` So far, you've seen three ways you can use the `for` construct. There are also ways to control the loop. You can control the loop with the following keywords: - `break`, this will exit the loop construct ```go arr = []int{-1,2} for i := 0; i< 2; i++ { fmt.Println(arr[i]) if arr[i] < 0 { break; } } ``` The output will be: ```output -1 ``` it won't output `2` as the loop exits after the first iteration. - `continue`, this will move on to the next iteration. If `break` exits the loop, `continue` skips the current iteration: ```go arr = []int{-1,2,-1, 3} for i := 0; i< 4; i++ { if arr[i] < 0 { break; } fmt.Println(arr[i]) } ``` The output will be: ```output 2 3 ``` ## Assignment - create a command line loop When creating console apps, you often want to read user input. The user input could be data used in the program or it can be the user typing a command to do something like "save", "print", "backup" etc. We will build a program for the latter case. 1. Create a file *main.go* and give it the following content: ```go package main import "fmt" func main() { } ``` 1. Add the following code to the `main()` method: ```go var keepGoing = true answer := "" for keepGoing { fmt.Println("Type command: ") fmt.Scan(&answer) if answer == "quit" { keepGoing = false } } fmt.Println("program exit") ``` 1. Run the code by typing `go run main.go`: ```bash go run main.go ``` You should see an output like so: ```output Type command: command Type command: quit program exit ``` ## 🚀 Challenge - Add a command "print" that ends up outputting "printing file". - See if you can use a `break` instead of the variable `keepGoing` to break the loop when the user types"quit". ## Solution ```go package main import "fmt" func main() { var keepGoing = true answer := "" for keepGoing { fmt.Println("Type command: ") fmt.Scan(&answer) if answer == "quit" { keepGoing = false } } fmt.Println("program exit") } ``` ================================================ FILE: 01-basics/05-loops/main.go ================================================ package main import ( "fmt" ) func main() { // // for i := 1; i < 10; i++ { // fmt.Println(i) // } // while var keepGoing = true answer := "" for keepGoing { fmt.Println("Type command: ") fmt.Scan(&answer) if answer == "quit" { keepGoing = false } } fmt.Println("program exit") // for each arr := []string{"arg1", "arg2", "arg3"} for i, s := range arr { fmt.Printf("index: %d, item: %s \n", i, s) } } ================================================ FILE: 01-basics/06-user-input/README.md ================================================ # Reading user input You will learn how to read user input, both a simpler technique and a more advanced one using formatters. ## Introduction This chapter will cover: - The `Scan()` method to read user input. - Reading one input. - Reading multiple inputs. - Working with formattters. ## User input It's an important thing to be able to read user input from the console. It gives the user a chance to interact with the program. Things to consider are how you are asking the user to input, is it one word, several inputs. Will the user separate input by space or newline? Regardless of what approach you go with, try to communicate the chosen approach to the user. ## Managing user input with `fmt` So far, you've seen how the `fmt` package can be used to print to the console. It can also be used to read user input. The `fmt` library has a built-in `Scan()` method that we will use to capture the user input. ## Reading one input You might start out wanting to read one input from the user. That's what the `Scan()` method does by default. Here's some code showing how to collect user input: ```go package main import "fmt" func main() { var response string fmt.Scan(&response) fmt.Println("User typed: ", response) } ``` Note how you send in the string `response` as a reference, using the `&` operator. It's done this way as the `Scan()` method will modify the variable you send in. When you run this code, you will see the below output: ```output hello User typed: hello ``` ## Reading multiple inputs You can provide several arguments to the `Scan()` method. By providing several arguments, you can collect more than one user input and separate each input, in the `Scan()` function, with a comma. Here's how to apply this technique: ```go var a1, a2 string // multiple input fmt.Scan(&a1, &a2) // formatted string to print out the user input str := fmt.Sprintf("a1: %s a2: %s", a1, a2) ``` Note how `a1` and `a2` is sent into `Scan()` and they are comma-separated. So how will those codes run? When you run this code, there are two ways for the user to input. The user can either separate the values by space like so: ```output hi you a1: hi a2: you ``` or by newline: ```output hi you a1: hi a2: you ``` ## `Scanf()`, scan the input with formatters So far, you've seen how you can collect input with spaces and endlines as separators. You can also be specific in how you collect input. Imagine that the user wants to type in an invoice number like so "INV200" or "INV114" and what you are interested in is the number part, or you are interested in the prefix as well? The answer to solving this is in the `Scanf()` function. It takes formatters. With formatters, you're able to pick the part of the user input you are interested in and place that into the variable you want. In the above-mentioned case, you can construct code looking like so: ```go var prefix string var no int // inv110 fmt.Scanf("%3s%d", &prefix, &no) fmt.Printf("prefix: %s, invoice no: %d", prefix, no) ``` The interesting part lies in the first argument of `Scanf()`, namely `3s%d`. What this means is, take the first 3 characters, `%3s` and interpret them as a string. Then interpret and place the remaining as a number, `%d`. Running this program, you will get an output like so: ```output inv200 prefix: inv, invoice no: 200 ``` "inv" is placed in `prefix` and 200 in `no` variable. ## Learn more To learn more about this area, check out this link ## Assignment - read user input In this assignment, you will read user input for a card game. The objective is to capture the names of the players. 1. Create a file *main.go* with the following content: ```go package main import "fmt" func main() { } ``` 1. Add the following code to the `main()` method: ```go fmt.Println("Enter player names (separated by space or newline):") var player1, player2 string fmt.Scan(&player1, &player2) fmt.Println("Players are: ", player1, player2) ``` 1. Run the program using `go run main.go`: ```bash go run main.go ``` You should see the following output: ```output Enter player names (separated by space or newline): Alice Bob Players are: Alice Bob ``` ## 🚀 Challenge Try modifying the program so it takes several players first and then ensures all players get a name. Here's an example output of such a program: ```output Enter number of players: 3 Enter Player 1 name: Alice Enter Player 2 name: Bob Enter Player 3 name: Jean Players are: Alice Bob Jean ``` ## Solution ```go package main import "fmt" func main() { fmt.Println("Enter player names (separated by space):") var player1, player2 string fmt.Scan(&player1, &player2) fmt.Println("Players are: ", player1, player2) } ``` ================================================ FILE: 01-basics/06-user-input/go.mod ================================================ module input go 1.16 ================================================ FILE: 01-basics/06-user-input/main.go ================================================ package main import "fmt" func Advanced() { var no int names := "" var name string fmt.Println("Enter number of players:") fmt.Scan(&no) for i := 0; i < no; i++ { fmt.Printf("Enter Player %d name:", i+1) fmt.Scan(&name) names += name + " " } fmt.Println("Players are:", names) } func main() { // var response string fmt.Println("hi") // // one input // fmt.Scan(&response) // fmt.Println("User typed ", response) // var a1, a2 string // // multiple input // fmt.Scan(&a1, &a2) // // formatted string // str := fmt.Sprintf("a1: %s a2: %s", a1, a2) // fmt.Println(str) // var prefix string // var no int // // in110 // fmt.Scanf("%3s%d", &prefix, &no) // fmt.Printf("prefix: %s, invoice no: %d", prefix, no) // fmt.Println("Enter player names (separated by space):") // var player1, player2 string // fmt.Scan(&player1, &player2) // fmt.Println("Players are: ", player1, player2) Advanced() } ================================================ FILE: 01-basics/07-functions/README.md ================================================ # Using functions In this chapter, we will discuss how you can define and use functions. Functions are great when you have the same type of code used in many places. By using functions, you thereby reduce repetition. ## Introduction This chapter will cover: - What a function is and why use it. - How to define and call a function. - Returning multiple values. ## Why functions As soon as you have a set of statements you repeat in many places, it's a good use case for creating a function. Typical things you put in functions are logging to a file, performing a calculation or talking to a data source. ## Your first function `main()` So far, you've seen the function `main()`, define like so: ```go func main(){ } ``` There's only one such function, it's called an entry point and represents the start of the program. You can however define other functions. ## The anatomy of a function A function consists of various parts. By incorporating all these parts, you ensure you have a reusable piece of code you can use in many places. Here are the parts you need to care about: - `func`, the keyword `func`. - **parameters**, 0 to many parameters - **a function body**, i.e. statements that say what the function does. - **a return construct**, if the function returns something. Here's an example: ```go func add(first int, second int) int { return first + second } ``` In the preceding code, the function is named `add()`. It has the parameters `first` and `second`. The function body, what the function does, consists of this code: ```go return first + second ``` ## Adding a return type To add a return type, we add that after the function parenthesis in form of a type. Here's an example: ```go add(firstNumber int, secondNumber int) int { ... } ``` Because we've added a return type of `int`, our function must return something. A way to return a value is by using the keyword `return`, like so: ```go add(firstNumber int, secondNumber int) int { return firstNumber + secondNumber } ``` ### Named return We can also name the return parameter like so: ```go add(firstNumber int, secondNumber int) (sum int) { sum = firstNumber + secondNumber return } ``` - Note how `sum` is part of function prototype declaration `(sum int)` and then assigned a value `sum = firstNumber + secondNumber`. - There's also a `return` on its own row, this code will compile as there's a notion of a return variable. ## Multiple returns It's possible to return more than one value. Just like you returned a named parameter via `(sum int)`, you can comma separate like so `(sum int, product int)`. When returning multiple values, you can type like so: ```go sum = first + second product = first * second return ``` Both `sum` and `product` are assigned values and you have a closing `return`. Putting it altogether you get a function that looks like so: ```go func calc(first int, second int) (sum int, product int) { sum = first + second product = first * second return } ``` To call the function, you type like so: ```go sum, product := calc(1, 2) fmt.Println(sum) fmt.Println(product) ``` Note how you assign the two returned values to variables `sum` and `product`. ## Assignment - adding a function to a program 1. Create a file *main.go* and give it the following content: ```go package main import "fmt" func main() { } ``` 1. Add a function `log()`, that we can use to print messages. Added to the program, your code should now look like so: ```go package main import "fmt" func log() { fmt.Println("message") } func main() { log() } ``` At this point, the `log()` function isn't very flexible, it prints "message" every time it's invoked. To make the `log()` function more flexible, lets add a parameter. ### Adding a parameter A parameter needs a data type, in this case, we will make it of type `string`. 1. Add the parameter within the parenthesis `()`, like so: ```go func log(message string) { fmt.Println(message) } // to use log("hi") log("there") ``` Note how the `log()` function takes the parameter `message` that is of type string. Our code is more flexible. ## Solution ```go package main import "fmt" func log(message string) { fmt.Println(message) } func main() { log("hi") log("there") } ``` ================================================ FILE: 01-basics/07-functions/main.go ================================================ package main import ( "fmt" "os" "strconv" ) func add(first int, second int) int { return first + second } func add2(first int, second int) (sum int) { sum = first + second return } func add3(first string, second string) int { int1, _ := strconv.Atoi(first) int2, _ := strconv.Atoi(second) return int1 + int2 } func calc(first int, second int) (sum int, product int) { sum = first + second product = first * second return } func changeName(name *string) { *name = "sara" } func main() { var name = "chris" sum := add(1, 2) fmt.Println(sum) fmt.Println(add2(2, 2)) fmt.Println(add3(os.Args[1], os.Args[2])) sum, mult := calc(1, 2) fmt.Println(sum, mult) fmt.Println(name) changeName(&name) fmt.Println(name) } ================================================ FILE: 01-basics/07-functions/test.go ================================================ package main import ( "fmt" "os" "strconv" ) func main() { no, err := strconv.Atoi(os.Args[1]) fmt.Println(no) if err != nil { fmt.Println(err) fmt.Println("Couldn't convert: " + os.Args[1]) } else { fmt.Println(no) } } ================================================ FILE: 01-basics/08-error-handling/README.md ================================================ # Handling errors In this chapter, we will cover error handling. ## Introduction This chapter will cover: - Causing and handling errors with `panic` and `recover`. - Use the error pattern to use a more idiomatic approach to managing errors. ## Errors Our apps aren't perfect, they will throw errors. Either they throw errors because of code we wrote or because of code written in a package we are using. Sometimes, that's even what the code is supposed to do when we feed it bad input. Regardless, our users expect us to handle these errors. Also, for our own sake, we need to log these errors in such a way that we can fix those errors if they are unexpected. There are two major ways to handle errors in Go: - **panic/recover**. `panic` and `recover` are language constructs. If you know another programming language, they function like throw and catch. What does it mean though? It means there's an error, with an error message and stack trace that we can catch and recover from or let it crash the program. - **error pattern**. This is referred to as the idiomatic Go way of doing things. The idea is to return a value and an error object from a function call. If an error occurred, it contains an error object or `nil`, if no error. ## Crash the program with `panic()` Let's take this function `Divide()`: ```go func Divide(nominator int, divider int) float32 { if divider == 0 { panic("can't divide by 0") } return float32(nominator) / float32(divider) } ``` It has an `if` check. If `divider` is `0` then it calls `panic()`. So what happens then? You see something like: ```output panic: can't divide by 0 goroutine 1 [running]: main.Divide(...) //panic.go:20 main.main() //panic.go:33 +0x96 exit status 2 ``` At the top is the error string you sent to `panic()`, the string "can't divide by 0". Then you have the stack trace, entries that indicate where the error started. Read it from the bottom, the error started on line 33, then you have line 20, which is this row: ```go panic("can't divide by 0") ``` Ok, so we have a way to protect ourselves from input values that we don't want. But crashing the program, is that necessary? In some cases, it is, in some cases, it isn't. For the latter cases, we have `recover()`. ### Capture the error with `recover()` Using `recover()` is about capturing an error so our program can continue. We need to learn about a concept before proceeding. That concept is `defer`. `defer` is a language construct that delays the execution of a function until the nearby function returns. Here's an example: ```go defer fmt.Println("second") fmt.Println("first") ``` Running this program, you should see: ```go first second ``` > See `defer` as the last thing that happens. How is this useful in the context of capturing an error? Capturing errors, if you want to capture it, is something you want to do as the last thing that happens. Take our `Divide()` function again: ```go func Divide(nominator int, divider int) float32 { defer errorHandler() if divider == 0 { panic("can't divide by 0") } return float32(nominator) / float32(divider) } ``` Note how it now has a line in it that says `defer errorHandler()`. It will be run the last thing that happens. Depending on the value of `divider`, it will either call `panic()` or call the `return` statement as the last thing. Ok, so what does `errorHandler()` look like? ```go func errorHandler() { if r := recover(); r != nil { fmt.Println("Recovered ", r) } } ``` In `errorHandler()`, we invoke `recover()` and assign it to variable `r` and then we test it for `nil`. If it's NOT `nil` then we have an error and we print it out. If it is `nil` then the user notices nothing. ## Improve the error handling There are steps we can take to improve the error handling. So far, we're printing back an error message. Imagine if someone read this error from a log file, would they be able to understand where things went wrong and fix the input data or the code itself? There are a couple of things we can do: - **Inspect the error**. Our error didn't only come with an error message, there's a stack trace as well. - **Logging**. If we want someone to work with these errors, we should look at logging them to a file. ### Inspect the error with `runtime/debug` There's a library `runtime/debug`. With this library, you can find out more about an error when it's thrown, information like stack trace, where the error originated. There's a function `Stack()` that produces the stack trace. Here's how to use it: ```go debug.Stack() ``` ### Log the error with `log` While `runtime/debug` can produce a stack trace, what would be useful is logging all this error information to a file. To log to a file, use the `os` and `log` package like so: ```go f, err := os.OpenFile("logs", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) if err != nil { log.Println(err) } log.SetOutput(f) ``` ## Use the error pattern with the `errors` package Other languages tend to use Exceptions to signal that something is wrong. Go has a different and idiomatic approach. It wants you to create errors as return values to a function, next to the actual value being returned. You are then expected to inspect a function and see if it returns an error. There's an `errors` package that can help us with the above approach. ### Define an error To define an error, we call the `New()` function with a string describing the error, here's an example: ```go var NoTooSmall = errors.New("the number is too small") ``` Next, let's look at how to add the error to a function. ### Return an error Let's start with function that uses a `panic()` as error handling: ```go func ReturnPositive(no int) int { if no > 0 { return no } else { panic("No too small") } } ``` We can improve this function, by ensuring it always returns the result and an error, like so: ```go func ReturnPositive(no int) (int, error) { if no > 0 { return no, nil } else { return 0, NoTooSmall } } ``` Note in the `if` clause that it returns `no` and `nil` when everything is fine. For the `else`, it returns a bogus value and our error `NoTooSmall`. ### Inspect the result Let's see how we would call the `ReturnPositive()` function and use this new pattern we established: ```go no, err := ReturnPositive(-2) if err != nil { fmt.Println("error: ", err) } else { fmt.Println("value:", no) } ``` What you are seeing above is how we use an `if` clause to check for errors, if so, print out. On the `else`, we have our actual value. ## Assignment I - add error handling In this exercise, we'll add error handling to our program. 1. Create a file *panic.go* and give it the following content: ```go package main import ( "fmt" ) // func errorHandler() { // if r := recover(); r != nil { // fmt.Println("Recovered ", r) // } // } func Divide(nominator int, divider int) float32 { // defer errorHandler() if divider == 0 { panic("can't divide by 0") } return float32(nominator) / float32(divider) } func main() { no := Divide(10, 0) fmt.Println(no) no = Divide(10, 1) fmt.Println(no) } ``` 1. Run the program `go run panic.go` ```bash go run panic.go ``` You should see output similar to: ```output panic: can't divide by 0 goroutine 1 [running]: main.Divide(...) //panic.go:20 main.main() /path/panic.go:33 +0x96 exit status 2 ``` Note how these two statements was never run as the program crashed: ```go no = Divide(10, 1) fmt.Println(no) ``` 1. Uncomment the commented out part and run the app again. You should now see the following output: ```output Recovered can't divide by 0 0 10 ``` Congrats, you've managed to implement error handling with `panic()` and `recover()`. ## Solution I ```go package main import ( "fmt" ) func errorHandler() { if r := recover(); r != nil { fmt.Println("Recovered ", r) } } func Divide(nominator int, divider int) float32 { defer errorHandler() if divider == 0 { panic("can't divide by 0") } return float32(nominator) / float32(divider) } func main() { no := Divide(10, 0) fmt.Println(no) no = Divide(10, 1) fmt.Println(no) } ``` ## Assignment II - improve error logging Let's improve our *panic.go* file by adding error logging: 1. Locate the `errorHandler()` and change it to the following: ```go func errorHandler() { if r := recover(); r != nil { log.Println(r, string(debug.Stack())) } } ``` 1. Ensure that *panic.go* looks like so: ```go package main import ( "fmt" "log" "os" "runtime/debug" ) func errorHandler() { if r := recover(); r != nil { log.Println(r, string(debug.Stack())) } } func Divide(nominator int, divider int) float32 { defer errorHandler() if divider == 0 { panic("can't divide by 0") } return float32(nominator) / float32(divider) } func main() { f, err := os.OpenFile("logs", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) if err != nil { log.Println(err) } log.SetOutput(f) log.Println("starting program") no := Divide(10, 0) fmt.Println(no) no = Divide(10, 1) fmt.Println(no) f.Close() } ``` 1. Run this program: ```go go run panic.go ``` You should see this output: ```output 0 10 ``` It was able to run all statements without being affected by the error that was thrown. 1. Inspect the *logs* file that was just created, it should have content like the below: ```output 2022/03/11 15:03:59 starting program 2022/03/11 15:03:59 can't divide by 0 goroutine 1 [running]: runtime/debug.Stack(0xc000111d30, 0x10b1b40, 0x10eae78) /usr/local/Cellar/go/1.16/libexec/src/runtime/debug/stack.go:24 +0x9f main.errorHandler() //panic.go:14 +0x5b panic(0x10b1b40, 0x10eae78) /usr/local/Cellar/go/1.16/libexec/src/runtime/panic.go:965 +0x1b9 main.Divide(0xa, 0x0, 0x0) //panic.go:21 +0xa5 main.main() //panic.go:34 +0x115 ``` ## Solution II ```go package main import ( "fmt" "log" "os" "runtime/debug" ) func errorHandler() { if r := recover(); r != nil { log.Println(r, string(debug.Stack())) } } func Divide(nominator int, divider int) float32 { defer errorHandler() if divider == 0 { panic("can't divide by 0") } return float32(nominator) / float32(divider) } func main() { f, err := os.OpenFile("logs", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) if err != nil { log.Println(err) } log.SetOutput(f) log.Println("starting program") no := Divide(10, 0) fmt.Println(no) no = Divide(10, 1) fmt.Println(no) f.Close() } ``` ================================================ FILE: 01-basics/08-error-handling/logs ================================================ 2022/03/11 15:03:59 starting program 2022/03/11 15:03:59 can't divide by 0 goroutine 1 [running]: runtime/debug.Stack(0xc000111d30, 0x10b1b40, 0x10eae78) /usr/local/Cellar/go/1.16/libexec/src/runtime/debug/stack.go:24 +0x9f main.errorHandler() /Users/chnoring/Documents/dev/projects/go-projects/book/01-basics/08-error-handling/panic.go:14 +0x5b panic(0x10b1b40, 0x10eae78) /usr/local/Cellar/go/1.16/libexec/src/runtime/panic.go:965 +0x1b9 main.Divide(0xa, 0x0, 0x0) /Users/chnoring/Documents/dev/projects/go-projects/book/01-basics/08-error-handling/panic.go:21 +0xa5 main.main() /Users/chnoring/Documents/dev/projects/go-projects/book/01-basics/08-error-handling/panic.go:34 +0x115 ================================================ FILE: 01-basics/08-error-handling/main.go ================================================ package main import ( "errors" "fmt" ) var NoTooSmall = errors.New("the number is too small") func ReturnPositive(no int) (int, error) { if no > 0 { return no, nil } else { return 0, NoTooSmall } } func main() { fmt.Println("hi") no, err := ReturnPositive(-2) if err != nil { fmt.Println(err) } else { fmt.Println(no) } } ================================================ FILE: 01-basics/08-error-handling/panic.go ================================================ package main import ( "fmt" "log" "os" "runtime/debug" ) func errorHandler() { if r := recover(); r != nil { // fmt.Println("Recovered ", r) // fmt.Println(string(debug.Stack())) log.Println(r, string(debug.Stack())) } } func Divide(nominator int, divider int) float32 { defer errorHandler() if divider == 0 { panic("can't divide by 0") } return float32(nominator) / float32(divider) } func main() { f, err := os.OpenFile("logs", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) if err != nil { log.Println(err) } log.SetOutput(f) log.Println("starting program") no := Divide(10, 0) fmt.Println(no) no = Divide(10, 1) fmt.Println(no) f.Close() } ================================================ FILE: 02-data-types/01-arrays/README.md ================================================ # Arrays and slices In this chapter, we will cover arrays and slices. ## Introduction This chapter will cover: - Declaring and inspecting an array. - Accessing elements in the array. - Working with slices. ## Arrays An array is a group of elements that are connected. You want to use an array when you have a group of something, like many orders, cars, and rows in a file. The idea with an Array is to collect all that data in one structure. You also want to be able to iterate over it and carry out an operation on it as a group. ## Declare an array To declare an array, you need to specify the following properties: - **capacity**, how many elements it holds. - **type**, what type of elements it holds. - **array content**, you can assign it elements at creation or do so later. Here's the syntax: ```go []{...element} ``` It starts with the square brackets, `[]`. Within the square brackets, you set the capacity, and how many elements it can hold. and here's a more real example: ```go cities := [5]string{"NY", "LA"} ``` In the preceding code, an array of strings is declared. It has a capacity for 5 elements and two of the places are filled with "NY" and "LA". Note also because we set the capacity to 5 and the number of elements it's assigned is 2, there are 3 spaces free. ### Capacity by inference You don't have to set capacity to an explicit number, you can set it to `...`, in which case the capacity will be set to the number of elements you assign to it, like so: ```go ids := [...]int{1, 2, 3, 4} ``` The preceding code has 4 elements, and that's also its capacity. ## Accessing elements The way to access an element is by using its index. The index is 0-based, meaning the first index is 0 and its last is the length -1. ```go ids := [...]int{1, 2, 3, 4} ids[0] // 1 ids[3] // 4 ``` ## Length and capacity Imagine we have the following array declared: ```go cities := [5]string{"NY", "LA"} ``` - **length**. The length is defined as the number of elements in the array. You can use the `len()` method to find this out: ```go len(cities) // 2 ``` - **capacity**. The capacity is how many elements the array can hold. `cap()` is the method you use to find the capacity: ```go cap(cities) // 5 ``` ## Slices A slice is a part of an array. A slice is created when the slice operator is being used. Here's the syntax for the slice operator: ```go s[i:p] ``` - `s`, the array - `i`, the first index of the array to take elements from - `p`, The variable p corresponds to the last element in the underlying array that can be used in the new slice. I.e. cut right before this index. ```go items := [5]int{1,2,3,4,5} part = items[1:3] // 2,3 ``` ### Adding elements A slice differs from an array, you can add items to it. The `append()` method lets you add elements to it. The syntax for `append()` is as follows: ```go append(slice, element) ``` Here's how you can append to a slice: ```go var numbers []int numbers = append(numbers, 1) numbers = append(numbers, 2) // 1,2 ``` ### Removing elements Remove an element by constructing a new slice. ```go letters := []string{"A", "B", "C", "D", "E"} remove := 2 // remove index // 0 - remove index, remove +1 to end letters = append(letters[:remove], letters[remove+1:]...) // [A B D E] ``` ### Create a slice with `make()` You can use the `make()` method to create a slice. Here's how: ```go slice := make([]int, 5) // creates a slice with length 5 and capacity 5 ``` You can set these to different values: ```go slice2 := make([]int, 2, 5) fmt.Println(slice2) fmt.Println(len(slice2)) fmt.Println(cap(slice2)) ``` Here, the slice has a length of 2, and a capacity of 5. ### Copy elements ```go arr := [3]int{1, 2, 3} dest := make([]int, 5) copy(dest, arr[0:2]) // copies slice {1,2} into dest fmt.Println(dest) // [1 2 0 0 0] ``` ## Assignment - store log entries Create an array meant for log entries. It can be used in the following way: ```console command> new here's a new entry command> new here's another entry command> list here's a new entry here's another entry command> quit bye ``` So, you need to have a way to store multiple strings and list them when asked for. Here's some starter code to deal with console input: ```go package main import ( "fmt" ) func main() { // create array var response string for { fmt.Print("command> ") fmt.Scan(&response) if response == "quit" { break } else if response == "new" { fmt.Print("Entry:") fmt.Scan(&response) // save entry to list fmt.Println("Saving entry") } else if response == "list" { // list entries fmt.Println("Listing entries") } else { fmt.Println("Unknown command", response) } } fmt.Println("bye") } ``` Add your own code where the comments are ## Solution ```go package main import ( "fmt" ) func main() { // create array arr := make([]string, 0) var response string for { fmt.Print("command> ") fmt.Scan(&response) if response == "quit" { break } else if response == "new" { fmt.Print("Entry:") fmt.Scan(&response) // save entry to list arr = append(arr, response) fmt.Println("Saving entry") } else if response == "list" { // list entries fmt.Println("Listing entries") for i := 0; i < len(arr); i++ { fmt.Println(arr[i]) } } else { fmt.Println("Unknown command", response) } } fmt.Println("bye") } ``` ================================================ FILE: 02-data-types/01-arrays/assignment.go ================================================ package main import ( "fmt" ) func main() { // create array arr := make([]string, 0) var response string for { fmt.Print("command> ") fmt.Scan(&response) if response == "quit" { break } else if response == "new" { fmt.Print("Entry:") fmt.Scan(&response) arr = append(arr, response) // save entry to list fmt.Println("Saving entry") } else if response == "list" { // list entries fmt.Println("Listing entries") for i := 0; i < len(arr); i++ { fmt.Println(arr[i]) } } else { fmt.Println("Unknown command", response) } } fmt.Println("bye") } ================================================ FILE: 02-data-types/01-arrays/main.go ================================================ // create an array // inferred length // get element package main import "fmt" var array [3]int func main() { cities := [5]string{"NY", "LA"} ids := [...]int{1, 2, 3, 4} array[0] = 1 var matrix [2][2]int matrix[0][0] = 1 fmt.Println(array[0]) fmt.Println(cities[0]) fmt.Println(ids[3]) fmt.Println(matrix[0][0]) items := [5]int{1, 2, 3, 4, 5} part := items[2:4] // 2,3 fmt.Println("part", part) var numbers []int numbers = append(numbers, 1) fmt.Println("num:", numbers) numbers = append(numbers, 2) fmt.Println("num:", numbers) months := []string{"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"} quarter1 := months[0:3] quarter2 := months[3:6] quarter3 := months[6:9] quarter4 := months[9:12] fmt.Println(quarter1, len(quarter1), cap(quarter1)) fmt.Println(quarter2, len(quarter2), cap(quarter2)) fmt.Println(quarter3, len(quarter3), cap(quarter3)) fmt.Println(quarter4, len(quarter4), cap(quarter4)) } ================================================ FILE: 02-data-types/01-arrays/slice.go ================================================ package main import "fmt" func main() { arr := [3]int{1, 2, 3} slice := make([]int, 5) fmt.Println(slice) fmt.Println(len(slice)) fmt.Println(cap(slice)) slice2 := make([]int, 2, 5) fmt.Println(slice2) fmt.Println(len(slice2)) fmt.Println(cap(slice2)) dest := make([]int, 5) copy(dest, arr[0:2]) // copies slice into dest fmt.Println(dest) } ================================================ FILE: 02-data-types/02-structs /README.md ================================================ # Structs In this chapter, we will learn about structs. A struct is a complex data type capable of holding many fields. It can also be extended to hold behaviour. ## Introduction This chapter will cover: - Declaring and inspecting a struct. - Embedding a struct within another struct. - Adding implementations to structs. ## Why structs Let's start with a simple scenario, you have an account balance. You might store it in a variable like so: ```go accountBalance int32 ``` Now that's great, but if you want to describe something more complex, like a bank account? A bank account consists of a variety of information like an ID, balance, account owner and so on. You could try representing each one of those properties as integers like so: ```go var accountBalance int32 var owner string var id int ``` However, what happens if you need to operate on more than one bank account, I mean you could try to store it like so: ```go var accountBalance int32 var owner string var id int var accountBalance2 int32 var owner2 string var id2 int ``` It doesn't scale though, what you need is a more complex type, like a `struct` that's able to group all this information like so: ```go type Account struct { accountBalance int32 owner string id int } ``` ## Defining a struct Ok, so we understand why we need a struct, to gather related information, and we've seen one example so far `Account`. But let's try breaking the parts down and see how we go about defining a struct. Here's what the syntax looks like: ```go type struct { ... fields } ``` Let's show another example but this time we create a struct for an address: ```go type Address struct { city string street string postal string } ``` ### Create a struct instance To create an instance from a struct, we can use one of two approaches: - **define a variable**, and set the fields after the variable declaration: ```go var address Address address.city = "London" address.street = "Buckingham palace" address.postal = "SW1" ``` - **define all at once**, we can set all the values in one go as well: ```go address2 := Address{"New York", "Central park", "111"} ``` ## Embedding a struct We can also embed a struct in another struct. Let's see we have our `Address` struct, an address is something that a higher level struct like `Person` can use. Here's how that can look: ```go type Person struct { name string address Address } ``` In this code, the `Person` struct has a field `address` of type `Address`. To instantiate a struct, we can type like so: ```go person := Person{ name: "chris", address: Address{ city: "Stockholm", }, } ``` ### Relying on default naming Note how we created a field `address`, we can skip typing a few characters by defining it like so instead: ```go type Employee struct { Address company string } ``` Note how we omit the name for the field and just type `Address`, this means the field name and field type will be the same name. Creating an instance from it is similar: ```go employee := Employee{ Address: Address{ city: "LA", }, company: "Microsoft", } ``` ## Adding implementation to structs Structs are by their very nature just data fields that describe something complex. You can add behaviour to it though by creating functions that operate on a struct. Here's an example: ```go func (a Address) string() string { return fmt.Sprintf("City: %s, Street: %s, Postal address: %s", a.city, a.street, a.postal) } ``` We've added a `string()` method. The method *belongs* to `Address` and we can see that with `(...)` right after the `func` keyword that takes `a Address`. The rest of the implementation returns a formatted string via `Sprintf()`. Given the following code: ```go var address Address address.city = "London" address.street = "Buckingham palace" address.postal = "SW1" fmt.Println(address.string()) ``` We would get the following output when calling `string()`: ```output City: London, Street: Buckingham palace, Postal address: SW1 ``` ## Assignment - defining a struct Define a struct representing a row in a shopping basket for an e-commerce store. Here's example data: ```output Title, Description, Quantity, Price per unit, Total LEGO set, 4000 pieces, 1, 600GBP, 600GBP ``` ### Write a program representing the shopping basket Write a program that iterates over the shopping basket and calculates the total: ```output Title, Description, Quantity, Price per unit, Total LEGO set, 4000 pieces, 1, 600GBP, 600GBP Plushy, plush toy, 3, 5 GBP, 15GBP Total: 615 GBP ``` ## Solution Part I ```go package main import ( "fmt" ) type Row struct { Title string Description string Quantity int UnitPrice float32 } func main() { row := Row{ Title: "LEGO set", Description: "4000 pieces", Quantity: 1, UnitPrice: 600, } fmt.Println(row) } ``` Part II ```go package main import ( "fmt" ) type Row struct { Title string Description string Quantity int UnitPrice float32 } func main() { row := Row{ Title: "LEGO set", Description: "4000 pieces", Quantity: 1, UnitPrice: 600, } row2 := Row{ Title: "Plushy", Description: "plush toy", Quantity: 3, UnitPrice: 5, } basket := make([]Row, 0) basket = append(basket, row) basket = append(basket, row2) var sum int = 0 for i := 0; i < len(basket); i++ { current := basket[i] fmt.Println(current) sum += current.Quantity * int(current.UnitPrice) } fmt.Println("Total", sum) } ``` ================================================ FILE: 02-data-types/02-structs /assignment.go ================================================ package main import ( "fmt" ) type Row struct { Title string Description string Quantity int UnitPrice float32 } func main() { row := Row{ Title: "LEGO set", Description: "4000 pieces", Quantity: 1, UnitPrice: 600, } row2 := Row{ Title: "Plushy", Description: "plush toy", Quantity: 3, UnitPrice: 5, } basket := make([]Row, 0) basket = append(basket, row) basket = append(basket, row2) var sum int = 0 for i := 0; i < len(basket); i++ { current := basket[i] fmt.Println(current) sum += current.Quantity * int(current.UnitPrice) } fmt.Println("Total", sum) } ================================================ FILE: 02-data-types/02-structs /go.mod ================================================ module struct go 1.16 ================================================ FILE: 02-data-types/02-structs /main.go ================================================ package main import "fmt" // normal struct type Address struct { city string street string postal string } // embedded with different name type Person struct { name string address Address } func (a *Address) setAddress(copy Address) { a.city = copy.city a.street = copy.street a.postal = copy.postal } func (a Address) string() string { return fmt.Sprintf("City: %s, Street: %s, Postal address: %s", a.city, a.street, a.postal) } // embedded with same name type Employee struct { Address company string } func main() { var address Address address.city = "London" address.street = "Buckingham palace" address.postal = "SW1" fmt.Println(address.city) fmt.Println(address.string()) address2 := Address{"New York", "Central park", "111"} address3 := Address{city: "LA", street: "Hollywood Boulevard", postal: "123"} fmt.Println(address2.city) address2.setAddress(address3) fmt.Println(address2.city) fmt.Println(address3.city) person := Person{ name: "chris", address: Address{ city: "Stockholm", }, } fmt.Println(person.address.city) employee := Employee{ Address: Address{ city: "LA", }, company: "Microsoft", } // employee.company = "test" fmt.Println(employee.company) } ================================================ FILE: 02-data-types/03-maps/README.md ================================================ # Using maps A map is a complex data structure that enables you to store things in a key-value fashion. This lets you implement scenarios like phone books, translation dictionaries and more. ## Introduction This chapter will cover: - Defining a map. - Reading the values of map by key but also iterating over it. - Change the content of a map. ## The use case for a map You will have scenarios when you code that there are things you need to look up. If you use a dictionary for example you might look up how you can translate a word from English to Spanish or vice versa. In programming, you have similar situations, maybe you want to know what service is run on a certain port for example. There are also databases that are based on the concept of having a unique key that points to a certain value. How all this is implemented is via map structure. The idea is that you define a key and value and collect all those in a group, a map. ## Creating a map To create a map in Go, we need to use the following syntax: ```go map[]{ ... entries } ``` Here's an example of creating a map structure that could hold a phone book: ```go phonebook := map[int]string{ 555123: "Robin Hood", 555404: "Sheriff of Nottingham"} ``` We define a map structure with key type `int` and value type string. Then we assign it a value with `{}`. Each entry is defined according to `: ` and separated by a comma. So how do we read a value? ### Create a map with `make()` Another way to create a map us by using the `make()` function. `make()` returns a initialized map if you give it a type like so: ```go dictionaryEnSv = make(map[string]string) ``` ### Adding entries To add entries to the map, you need to provide it with a key and value entry like so: ```go dictionaryEnSv["hello"] = "hej" ``` ## Read a value by key Imagine now that we have these two entries, and you want the value given that you have in entry 555404, how would we do that? We use the square brackets like so `[]`: ```go phonebook[555404] // "Sheriff of Nottingham" ``` ### Check for existing entry So, you learned that `phonebook[555404]` gives you a value back. What if it doesn't exist? If you give it a key that's not stored in the map then you get nothing back as a result: ```go phonebook[888] // this prints as empty in the console ``` There's a better way to check this because accessing an entry with a key returns two values, the value, and a Boolean. The Boolean indicates if this key exists in the map. See this code: ```go _, exist := phonebook[888] fmt.Println(exist) // false ``` Here you get the value back, but you choose to ignore for this one-time occasion to only focus on `exist`, a Boolean that tells you if the entry exists. You can even use this construct in an if statement: ```go if _, exist := phonebook[888] { // number exist, call person } ``` ## Iterate over a map We can iterate over a map with a `for` construct and a `range`. Here's how you can iterate: ```go for key, value := range phonebook { fmt.Println(key, value) } ``` ## Delete an entry To remove an entry from a map, you can use the `delete()` method. The `delete()` method takes the map and the key to delete as parameters, like so: ```go delete(phonebook, 555404) ``` ## Assignment - build a phone book Here are your contacts: ```output Alice 555-123 Bob 555-124 Jean 555-125 ``` ```console Welcome to your phonebook. Command> store Enter contact: Rob 555-126 Contact saved Command> list Alice 555-123 Bob 555-124 Jean 555-125 Rob 555-126 Command> lookup Enter name: Alice Alice has number: 555-123 ``` HINT: you might need to use both a map and a slice. ## Solution ```go package main import "fmt" func main() { var command string contacts := make(map[string]string) fmt.Println("Welcome to your phonebook") for { fmt.Print("Command> ") fmt.Scan(&command) if command == "store" { fmt.Print("Enter contact: ") var contact string var no string fmt.Scan(&contact, &no) contacts[contact] = no fmt.Println("Contact saved") } else if command == "list" { for key, value := range contacts { fmt.Println(key, value) } } else if command == "lookup" { fmt.Print("Enter name: ") var contact string fmt.Scan(&contact) fmt.Println(contacts[contact]) } else if command == "quit" { break } else { fmt.Println("Unknown command: ", command) } } fmt.Println("Bye") } ``` ## Challenge Right now, there's no error checking. Add a check so that if you look up a contact that doesn't exist, you should get an error message. Here's how it could work: ```console command> lookup Enter name: Jane Contact doesn't exist, do you want to add it? y/n: y Enter contact: Jane 123 Contact saved ``` ================================================ FILE: 02-data-types/03-maps/assignment.go ================================================ package main import "fmt" func main() { var command string contacts := make(map[string]string) fmt.Println("Welcome to your phonebook") for { fmt.Print("Command> ") fmt.Scan(&command) if command == "store" { fmt.Print("Enter contact: ") var contact string var no string fmt.Scan(&contact, &no) contacts[contact] = no fmt.Println("Contact saved") } else if command == "list" { for key, value := range contacts { fmt.Println(key, value) } } else if command == "lookup" { fmt.Print("Enter name: ") var contact string fmt.Scan(&contact) fmt.Println(contacts[contact]) } else if command == "quit" { break } else { fmt.Println("Unknown command: ", command) } } fmt.Println("Bye") } ================================================ FILE: 02-data-types/03-maps/main.go ================================================ package main import "fmt" func main() { cars := map[string]string{"make": "Ferrari", "model": "F40"} fmt.Println(cars["make"]) fmt.Printf("cars\t%v\n", cars) _, exist := cars["model"] fmt.Println(exist) val, exist := cars["m"] fmt.Println(val) fmt.Println(exist) } ================================================ FILE: 02-data-types/04-interfaces/README.md ================================================ # Interfaces This chapter covers what an interface is and what to use it for. ## Introduction This chapter will cover: - What is an interface and how does it differ from a struct. - How to add behaviour. - Implement an interface. - Type assertions. - Changing a value. ## Interface To describe what an interface is, let's start by talking about structs and how they are different from an interface. With structs, we can define properties we want a concept to have, like for example a car: ```go type Car struct { make string model string } ``` An interface is meant to communicate something different, a behaviour. Instead of describing the car itself, as a struct does, it describes what a car can do. ## Interface - describing a behaviour Now that we've described how an interface differs from a struct, let's talk about the motivation for using an interface. There are a couple of good reasons for when to use an interface: - **Adding behaviour**. When you want your types to have a behaviour, that's when you want an interface - **Communicate via contract**. Often, when you call other code, you want to reveal as little of your concrete implementation as possible. Instead of saying, here's a car, you might want to say, here's something that can run. It enables your code to be flexible and you don't have to implement specific code for each type but can instead write code that deals with a certain behaviour. ### Define an interface To define an interface, you need the keywords `type` and `interface` and you need a set of methods, one or many that a type should implement. Here's an example interface: ```go type Describable interface { describe() string } ``` Here's another example: ```go type Point struct { x int y int } type Shape interface { area() int location() Point } ``` ## Implement an interface Everything that's a type can implement an interface. More than one type can implement the same interface. Let's look at how a type `Rectangle` can implement the `Shape` interface: ```go type Rectangle struct { x int y int } func (r Rectangle) area() int { return r.x * r.y } func (r Rectangle) location() Point { return P{ x: r.x, y: r.y } } ``` So, what's going on here? Let's look at the first method `area()`: ```go func (r Rectangle) area() int { return r.x * r.y } ``` It looks like a regular function but there's this `(r Rectangle)` right before the function name. That's a signal to Go that you are implementing a certain function on the type `Rectangle`. There's also a second implementation for `location()`. By implementing both these methods, `Rectangle` has now fully implemented the `Shape` interface. ### Pass an interface Ok, so we've fully implemented an interface, what does it allow me to do? Well, there are two things you can do: - **Call properties and behaviour**. At this point, you are ready to create an instance and call both properties and methods (its new behaviour): ```go var rectangle Rectangle = Rectangle{x: 5, y: 2} fmt.Println(rectangle.area()) // prints 10 ``` Great, our `Rectangle` type has both the properties `x` and `y` as well as the behaviour from `Shape`. - **Pass an interface**. Imagine you wanted to pass the behaviour to a function to make it flexible: ```go func printArea(shape Shape) { fmt.Println(shape.area()) } ``` To make that happen, lets change slightly how we construct our `Rectangle instance`: ```go var shape Shape = Rectangle{x: 5, y: 2} printArea(rectangle) // prints 10 ``` ### Implement `Square` To see the power in what we just created, let's create another struct `Square` and have it implement `Shape`: ```go type Square struct { side int } func (s Square) area() int { return s.square * s.square } func (s Square) location() Point { return Point{x: s.side, y: s.side} } func main() { var shape Shape = Rectangle{x: 5, y: 2} var shape2 Shape = Square{side: 5} printArea(shape) // prints 10 printArea(shape2) // prints 25 } ``` The power lies in the fact that `printArea()` doesn't have to deal with the internals of `Rectangle` or `Shape`, it just needs the parameter to implement `Shape`, a behaviour. ### Full code Here's the full code: ```go package main import "fmt" type Rectangle struct { x int y int } type Point struct { x int y int } type Square struct { side int } type Shape interface { area() int location() Point } func printArea(shape Shape) { fmt.Println(shape.area()) } func (r Rectangle) area() int { return r.x * r.y } func (r Rectangle) location() Point { return Point{x: r.x, y: r.y} } func (s Square) area() int { return s.side * s.side } func (s Square) location() Point { return Point{x: s.side, y: s.side} } func main() { var shape Shape = Rectangle{x: 5, y: 2} var shape2 Shape = Square{side: 5} printArea(shape) // prints 10 printArea(shape2) // prints 25 } ``` ## Type assertions So far, a `Rectangle` or `Square` implements the `Shape` interface Let's have a closer look at this code: ```go var shape Shape = Rectangle{x: 5, y: 2} var shape2 Shape = Square{side: 5} printArea(shape) // prints 10 printArea(shape2) // prints 25 ``` We've said for `shape` and `shape2` to be of type `Shape`. That's great for being sent to the `printArea()` method. What if we need to access a `Rectangle` property on `shape`, can we? Let's try: ```go var shape Shape = Rectangle{x: 5, y: 2} fmt.Println(shape.x) // shape.x undefined (type Shape has no field or method x) ``` Ok, not working, we need to find a way to reach the underlying fields. We can use something called *type assertion* like so: ```go var shape Shape = Rectangle{x: 5, y: 2} fmt.Println(shape.(Rectangle).x) // 5 ``` Ok, that works, so `.()` works, if the underlying type is the correct type. ## Change a value So, one thing about our approach so far is that we have implemented interfaces with methods that read data from the underlying struct instances. What if we want to change data, can we do that? Let's look at an example: ```go package main import "fmt" type Car struct { speed int model string make string } type Runnable interface { run() } func (c Car) run() { c.speed = 10 } func main() { c := Car{make: "Ferrari", model: "F40", speed: 0} c.run() fmt.Println(c.speed) // ? } ``` Running this code, it returns `0`. So, looking at our `run()` method: ```go func (c Car) run() { c.speed = 10 } ``` shouldn't this work? Well, no, because you are not changing the instance. For that, you need to send a reference. A slight alteration to the `run()` method, with `*`: ```go func (c *Car) run() { c.speed = 10 } ``` and your code now does what it's supposed to. ## Assignment Start with the following code: ```go package main type Point struct { x float32 y float32 } type Vehicle struct { velocity float32 Point } func main() { v := Vehicle{ velocity: 0, Point: Point{ x: 0, y: 0, }, } v.fly() fmt.Println(v.velocity) v.land() fmt.Println(v.velocity) } ``` Implement the following interface: ```go type Spaceship interface { fly() land() position() Point } ``` The output from running the program should be: ```output 10 0 ``` ## Solution ```go package main import "fmt" type Point struct { x float32 y float32 } type Vehicle struct { velocity float32 Point } type Spaceship interface { fly() land() position() Point } func (v *Vehicle) fly() { v.velocity = 10 } func (v *Vehicle) land() { v.velocity = 0 } func (v Vehicle) position() Point { return v.Point } func main() { v := Vehicle{ velocity: 0, Point: Point{ x: 0, y: 0, }, } v.fly() fmt.Println(v.velocity) v.land() fmt.Println(v.velocity) } ``` ================================================ FILE: 02-data-types/04-interfaces/assignment.go ================================================ package main import "fmt" type Point struct { x float32 y float32 } type Vehicle struct { velocity float32 Point } type Spaceship interface { fly() land() position() Point } func (v *Vehicle) fly() { v.velocity = 10 } func (v *Vehicle) land() { v.velocity = 0 } func (v Vehicle) position() Point { return v.Point } func main() { v := Vehicle{ velocity: 0, Point: Point{ x: 0, y: 0, }, } v.fly() fmt.Println(v.velocity) v.land() fmt.Println(v.velocity) } ================================================ FILE: 02-data-types/04-interfaces/cast.go ================================================ package main import ( "errors" "fmt" "log" ) func elementAt(elements interface{}, index int) (interface{}, error) { switch t := elements.(type) { case []int: return t[index], nil case []string: return t[index], nil default: fmt.Println(t) return nil, errors.New("unsupported") } } func main() { intArr := []int{1, 2, 3} stringArr := []string{"one", "two", "three"} value, err := elementAt(intArr, 2) if err != nil { log.Fatal(err) } fmt.Println(value) value, err = elementAt(stringArr, 1) if err != nil { log.Fatal(err) } fmt.Println(value) } ================================================ FILE: 02-data-types/04-interfaces/main.go ================================================ package main import ( "fmt" ) type Runnable interface { run() } type Describable interface { description() string } type Car struct { speed int model string make string } type Hero struct { name string } func DescribeThings(describable Describable) { fmt.Println(describable.description()) } func RunThings(car *Car) { car.run() } func (c *Car) run() { c.speed = 10 } func (c Car) description() string { return fmt.Sprintf("Model: %s, Make: %s", c.model, c.make) } func (hero Hero) description() string { return fmt.Sprintf("The heroes name is: %s", hero.name) } func main() { var describable Describable describable = Car{speed: 0, make: "Ferrari", model: "F40"} DescribeThings(describable) var describable2 Describable describable2 = Hero{name: "Conan"} DescribeThings(describable2) c := Car{speed: 0, make: "Porsche", model: "911"} p := &c RunThings(p) fmt.Println(c.speed) } ================================================ FILE: 02-data-types/04-interfaces/shape.go ================================================ package main import "fmt" type Rectangle struct { x int y int } type Point struct { x int y int } type Square struct { side int } type Shape interface { area() int location() Point } func printArea(shape Shape) { fmt.Println(shape.area()) } func (r Rectangle) area() int { return r.x * r.y } func (r Rectangle) location() Point { return Point{x: r.x, y: r.y} } func (s Square) area() int { return s.side * s.side } func (s Square) location() Point { return Point{x: s.side, y: s.side} } func main() { var shape Shape = Rectangle{x: 5, y: 2} var shape2 Shape = Square{side: 5} printArea(shape) // prints 10 printArea(shape2) // prints 25 fmt.Println(shape.(Rectangle).x) } ================================================ FILE: 02-data-types/04-interfaces/test.go ================================================ package main import "fmt" type Car struct { speed int model string make string } type Runnable interface { run() } func (c *Car) run() { c.speed = 10 } func main() { c := Car{make: "Ferrari", model: "F40", speed: 0} c.run() fmt.Println(c.speed) // ? } ================================================ FILE: 03-projects/01-first-project/README.md ================================================ # Your first project In this chapter, we will cover how to create your first project in Go. ## Introduction This chapter will cover: - Creating a project. - Organize your files. ## Module use cases There are two interesting use cases with modules: - **Consuming a module**, you will use a combination of core modules and external 3rd party modules - **Creating a module**, in some cases you will create code that you or someone else will be able to use. For this scenario, you can create a module and upload it to GitHub. ## Consume internal files You want to split up your app in many different files. Let's say you have the following files: ```output /app main.go /helper helper.go ``` What you are saying above is that your program consists of many files and that you want code in the fiile *main.go* to use code from *helper.go* for example. To handle such a case, you need the following: - **a project**. By creating a project, you create a top-level reference that you can use in the `import` directive. - **an import** that points to the project root name as well as the path to the module you want to import. You can use `go mod init`, this will initialize your project. ## Creating a project To create a project, you run `go mod init` and a name for a project, for example, "my-project": ```bash go mod init my-project ``` You end up with a *go.mod* file looking something like so: ```go module my-project go 1.16 ``` The *go.mod* file tells you the name of your project and the currently used version of Go. It can contain other things as well like libraries you are dependent on. ## The import statement Imagine now we have this file structure in our project: ```output /app main.go /helper helper.go ``` with *helper.go* looking like so: ```go package helper import "fmt" func Help() { fmt.Println("This is a helper function") } ``` to use the public `Helper()` function from *main.go*, we need to import it. In *main.go* we need an import statement like so: ```go import ( "my-project/helper" ) ``` We are now able to invoke the `Help()` function from *main.go* like so: ```go helper.Help() ``` ## Assignment - create a project In this assignment, you will create a project. 1. Create a project like so: ```go go mod init my-project ``` 1. create the **helper** directory and *helper.go* file and give it the following content: ```go // helper.go package helper import "fmt" func Help() { fmt.Println("This is a helper function") } ``` 1. Create the *main.go* file and give it the following content: ```go package main import ( "my-project/helper" ) func main() { helper.Help() } ``` Note this import `"my-project/helper"`, it ensures the `helper` package is in scope. 1. Compile and run ```bash go run main.go ``` ## Solution helper/helper.go ```go package helper import "fmt" func Help() { fmt.Println("help") } ``` main.go ```go package main import "my-project/helper" func main() { helper.Help() } ``` ## Challenge See if you can create another function in *helper.go*, this time, make the function name lowercase, what happens if you try to import it? ================================================ FILE: 03-projects/01-first-project/go.mod ================================================ module my-project go 1.16 ================================================ FILE: 03-projects/01-first-project/helper/helper.go ================================================ package helper import "fmt" func Help() { fmt.Println("help") } ================================================ FILE: 03-projects/01-first-project/main.go ================================================ package main import "my-project/helper" func main() { helper.Help() } ================================================ FILE: 03-projects/02-consume-external/README.md ================================================ # Consume an external module In this chapter, we are looking at downloading and using external modules. ## Introduction This chapter will cover: - Creating a project. - Adding an external module to your project. - Use the external library in your app. ## External module To consume an external module, you need to: - **Import it**, involves using the `import` instruction and fully qualifying the address to the module's location. - **Use it in code**, call the code from the module that you mean to use - **Ensure it's downloaded**, so your code can be run. ## Import module To import a module, you can do one of two things: - `go get `, this will fetch the module and download it and make it available for your project to use. - `go mod tidy`, this command checks the imports used in your program and fetches the module if not fetched already. ## Use it in code To use your module in code, you need to add it to the `import` section and then invoke it where you need it in the application code. ```go import ( "github.com//" ) func main() { .() } ``` Here's an example: ```go package main import ( "fmt" "github.com/softchris/math" ) func main() { math.Add(1,1) // 2 } ``` ## Assignment - consume an external module Let's create a new project 1. Run `go mod init`: ```go go mod init hello ``` Note how *go.mod* was created with the following content: ```go module hello go 1.16 ``` ### Add reference to an external lib Next, lets create some code that will use the external library: 1. Create the file *main.go* ```go package main import ( "fmt" "github.com/softchris/math" ) ``` 1. To the same file, add a `main()` function and call the external `Add` method from the `math` package: ```go func main() { sum := math.Add(1,2) fmt.Println(sum) } ``` ### Fetch the lib Now, we need to resolve the external library. 1. Run `go mod tidy`: ```bash go mod tidy ``` Your *go.mod* is updated: ```go require github.com/softchris/math v0.2.0 ``` There's also *go.sum* file with the following content: ```go github.com/softchris/math v0.2.0 h1:88L6PLRBGygS3LY5KGIJhyw9IZturmd2ksU+p13OPa4= github.com/softchris/math v0.2.0/go.mod h1:v8WzhjKC+ipuH+i9IZ0Ta2IArniTP53gc5TgCINCqAo= ``` This is Go's way of keeping track of how to build the app by referencing to the go module in question. 1. Run `go run`: ```go go run main.go ``` Running the program gives you the following output: ```output 3 ``` ## Solution go.sum ```go github.com/softchris/math v0.2.0 h1:88L6PLRBGygS3LY5KGIJhyw9IZturmd2ksU+p13OPa4= github.com/softchris/math v0.2.0/go.mod h1:v8WzhjKC+ipuH+i9IZ0Ta2IArniTP53gc5TgCINCqAo= ``` go.mod ```go module hello go 1.16 require github.com/softchris/math v0.2.0 ``` main.go ```go package main import ( "fmt" "github.com/softchris/math" ) func main() { sum := math.Add(1,2) fmt.Println(sum) } ``` ## Challenge See if you can find another module you want to use in your project. Add it to the project and use it in your code. ================================================ FILE: 03-projects/02-consume-external/go.mod ================================================ module hello go 1.16 require github.com/softchris/math v0.2.0 ================================================ FILE: 03-projects/02-consume-external/go.sum ================================================ github.com/softchris/math v0.2.0 h1:88L6PLRBGygS3LY5KGIJhyw9IZturmd2ksU+p13OPa4= github.com/softchris/math v0.2.0/go.mod h1:v8WzhjKC+ipuH+i9IZ0Ta2IArniTP53gc5TgCINCqAo= ================================================ FILE: 03-projects/02-consume-external/log-tester/go.mod ================================================ module log-tester go 1.16 require github.com/softchris/logger v0.1.0 ================================================ FILE: 03-projects/02-consume-external/log-tester/go.sum ================================================ github.com/softchris/logger v0.1.0 h1:Kqw7t9C3Y7BtHDLTx/KXEqHy5x8EJxrLian742S0di0= github.com/softchris/logger v0.1.0/go.mod h1:rrzWjMsM3tqjetDBDyezI8mFCjGucF/b5RSAqptKF/M= ================================================ FILE: 03-projects/02-consume-external/log-tester/helper/helper.go ================================================ package helper import "fmt" func Help() { fmt.Println("This is a helper function") } ================================================ FILE: 03-projects/02-consume-external/log-tester/main.go ================================================ package main import ( "log-tester/helper" "github.com/softchris/logger" ) func main() { logger.Log("hey there") helper.Help() } ================================================ FILE: 03-projects/02-consume-external/main.go ================================================ package main import ( "fmt" "github.com/softchris/math" ) func main() { sum := math.Add(1, 2) fmt.Println(sum) } ================================================ FILE: 03-projects/03-create-shared-module/README.md ================================================ # Create a module meant for sharing In this chapter, we will cover how you can create a module you can share with others. ## Introduction This chapter will cover: - Creating a project. - Testing the module locally. - Tag the module with different versions. - Try consuming the module as an external library. ## Create a module When you build a module meant for sharing, there's some gotchas: - You need to create a package. - Your package won't be called main. - There's the concept of public and private parts of your code. - You can test it locally. - Upload your package to GitHub for wide distribution. ## Assignment - create a module meant for sharing and consume it To create a module meant for wider use you need to first initialize a module. 1. Create a directory *logger* for your new package: ```go mkdir logger cd logger ``` 1. Run `go mod init
`, for example: ```bash go mod init github.com/softchris/logger ``` This will create a *go.mod* file in your directory. ```output logger/ go.mod ``` The file looks like so: ```go module github.com/softchris/logger go 1.16 ``` It contains the package name and the version of Go it means to use. 1. Create a file to host your package code, for example *log.go* and give it the following content: ```go package logger import ( "fmt" ) var Version string = "1.0" func Log(mess string) { fmt.Println("[LOG] " + mess) } ``` - Note `package logger` instead of `main`. - The uppercase variables and methods makes the publicly available. Anything named with lowercase will be private for the package. ### Test it locally You can test your package locally. To do so you need a separate package that you can import your package from. 1. Move up a directory: ```bash cd .. ``` 1. Create a new directory **logger-test**: ```bash mkdir logger-test cd logger-test ``` 1. Create a package, it will be used for testing only: ```bash go mod init logger-test ``` 1. Create a file *main.go* and add the following code: ```go package main import "github.com/softchris/logger" func main() { logger.Log("hey there") } ``` At this point, you are consuming the "logger" package but it's pointing to GitHub and your package doesn't live there yet. However, you can repoint to a local address on your machine, let's do that next. 1. Open *go.mod* and add the following: ```go require github.com/softchris/logger v0.0.0 replace github.com/softchris/logger => ../logger ``` Two things are happening here: - you are asking for the "logger" package: ```go require github.com/softchris/logger v0.0.0 ``` - you are making it point to your local system instead of GitHub ```go replace github.com/softchris/logger => ../logger ``` 1. Run the package with `go run`: ```bash go run main.go ``` You should see: ```output [LOG] hey there ``` ### Publish a package To publish your package, you can put it on GitHub. 1. Create a git repo with `git init`: ```bash git init ``` 1. Create the repo on GitHub. 1. Make you do at least one commit: ```bash git add . git commit -m "first commit" ``` 1. Do the following to upload your package to GitHub: ```bash git remote add origin https://github.com/softchris/logger.git git branch -M main git push -u origin main ``` 1. Tag your package with `git tag`: ```bash git tag v0.1.0 git push origin v0.1.0 ``` Now your package has the tag 0.1.0 ### Test it out 1. Go to your project "logger-test": ```bash cd .. cd logger-test ``` 1. Open up *go.mod* and remove these lines: ```go require github.com/softchris/logger v0.0.0 replace github.com/softchris/logger => ../logger ``` 1. Run `go mod tidy`, this will force Go to go look for the package: Your *go.mod* should now contain: ```go require github.com/softchris/logger v0.1.0 ``` Also, your *go.sum* should contain: ```go github.com/softchris/logger v0.1.0 h1:Kqw7t9C3Y7BtHDLTx/KXEqHy5x8EJxrLian742S0di0= github.com/softchris/logger v0.1.0/go.mod h1:rrzWjMsM3tqjetDBDyezI8mFCjGucF/b5RSAqptKF/M= ``` 1. Run the program with `go run`: ```bash go run main.go ``` You should see: ```output [LOG] hey there ``` ## Challenge See if you can add a feature to your new package. Give it a new tag via Git. Then ensure your app is using this new version. ================================================ FILE: 03-projects/04-testing/README.md ================================================ # Test your code in Go In this chapter, we will look at creating and running unit tests. ## Introduction This chapter will cover: - Why you should test your code. - The testing library in Go. - Authoring and running a test. - Controlling how to run your tests. - Produce coverage reports. ## Why we test It's good to test your code to ensure it works as intended. In this chapter, we're looking at unit tests specifically. ## What Go provides Go has a package `testing` that gives us two things to start out with: - a parameter for the test. The `testing` library exposes a `t *testing.T` parameter. By putting it as a parameter to a function, said function becomes a test. ```go func TestAdd(t *testing.T) {} ``` - a way to assert the result. `testing` also exposes `t.Errorf()`. By invoking it with a string, the test counts as failed. To pass a test you do nothing: ```go t.Errorf("Sum was incorrect, Actual: %d, Expected: %d", total, 4) ``` Here's an example test function: ```go func TestAdd(t *testing.T) { total := Add(2, 2) if total != 4 { t.Errorf("Sum was incorrect, Actual: %d, Expected: %d", total, 4) } t.Log("running TestAdd") } ``` - First, the code to test is called: ```go total := Add(2, 2) ``` - Secondly, the assertion is made, to see if it succeeded or failed: ```go if total != 4 { t.Errorf("Sum was incorrect, Actual: %d, Expected: %d", total, 4) } ``` if the result is not the expected, then `t.Errorf()` is called to state what's gone wrong. ## Your first test Make sure you've created a project with `go mod init`. Then create a file structure like so: ```output main.go math/ math.go ``` What you want to do next is to create a test file. You want to keep the test file as close to the code you want to test as possible. Because you want to test *math.go* you create `math_test.go` file in the math/ directory like so: ```output main.go math/ math.go math_test.go ``` ### Authoring and running your first test Now that you have the file structure above, ensure the *math_test.go* file has the following content: ```go package math import ( "testing" ) func TestAdd(t *testing.T) { total := Add(2, 2) if total != 4 { t.Errorf("Sum was incorrect, Actual: %d, Expected: %d", total, 4) } t.Log("running TestAdd") } ``` To run a test, you invoke the `go test` command. Here are different ways to run your tests: - `go test`, runs the test in the current working directory. Here's what it looks like: ```output ok test-example/math 0.258s ``` - `go test -v`, runs a verbose version. Here's what it can look like: ```output === RUN TestAdd math_test.go:12: running TestAdd --- PASS: TestAdd (0.00s) PASS ok test-example/math 0.422s ``` In the verbose version, you see the name of the test and if it failed. - `go test ./..`, recursive run. If you run the command like so it will run all the tests in the subfolders as well. ## Control the test run There are ways to control how many tests are run. Here are some ways: - **Run test by pattern**. . You can provide a pattern to have Go run some of the tests, which matches it by a substring or even at a certain depth and more. Here's how: ```console go test -run ``` - **Skip a test**. `t.Skip()` by calling this inside the test, the test is skipped. - **Run a single test**. You can run a single test by running a pattern that specifies the package and the name of the test, here's how: ```console go test -run TestAdd ./math ``` Here's the package is called math and the name of the test is `TestAdd()`. ## Coverage There's a built-in tool for dealing with coverage. To learn more about the tool, you can type: ```console go tool cover -help ``` it will list a set of commands. The tool is centred on the concept of having an out file. The out file contains instructions on where your code is covered by tests and where it isn't. An out file can look something like this: ```output mode: set test-example/math/math.go:3.32,5.2 1 1 test-example/math/math.go:7.37,9.2 1 1 test-example/math/math.go:11.47,13.2 1 0 ``` This is a format readable by the tool. It's a prerequisite to generate said "out file" before you can view your code's coverage. Place yourself in the directory you mean to measure coverage on and run this command: ```console go test -coverprofile=c.out ``` Now you are ready to run a command that shows the result in a browser: ```console go tool cover -html=c.out ``` The above command spins up a browser and the output look something like so: ![coverage](coverage.png) The coverage report tells us that the the green portions are covered by tests whereas the red portions should have tests covering it. ## Learn more There's a lot more to learn on testing with Go, have a look at package documentation, [docs](https://pkg.go.dev/testing) ## Challenge Create a test for a piece of code you wrote. Run the test. See if you can produce a coverage report and implement any gaps pointed out by the report. ================================================ FILE: 03-projects/04-testing/go.mod ================================================ module test-example go 1.16 ================================================ FILE: 03-projects/04-testing/main.go ================================================ package main import ( "fmt" m "test-example/math" ) func main() { fmt.Println(m.Add(1, 1)) } ================================================ FILE: 03-projects/04-testing/math/c.out ================================================ mode: set test-example/math/math.go:3.32,5.2 1 1 test-example/math/math.go:7.37,9.2 1 1 test-example/math/math.go:11.47,13.2 1 0 ================================================ FILE: 03-projects/04-testing/math/math.go ================================================ package math func Add(lhs int, rhs int) int { return lhs + rhs } func Subtract(lhs int, rhs int) int { return lhs - rhs } func Divide(lhs float32, rhs float32) float32 { return lhs / rhs } ================================================ FILE: 03-projects/04-testing/math/math_test.go ================================================ package math import ( "testing" ) func TestAdd(t *testing.T) { total := Add(2, 2) if total != 4 { t.Errorf("Sum was incorrect, Actual: %d, Expected: %d", total, 4) } t.Log("running TestAdd") } func TestSub(t *testing.T) { total := Subtract(2, 2) if total != 0 { t.Errorf("Sum was incorrect, Actual: %d, Expected: %d", total, 0) } t.Log("running TestSubtract") } ================================================ FILE: 04-webdev/01-json/README.md ================================================ # Working with JSON In this chapter, you will work with the JSON data format. ## Introduction In this chapter, you will learn the following: - The JSON data format. - How to read JSON data and map it to existing structures in Go. - How to create JSON and persist it in files. ## JSON JSON is a lightweight data format for storing and transporting data. The acronym stands for **J**ava**S**cript **O**bject **n**otation. The format is commonly used in Web services. Usually, data is encoded as JSON and sent via HTTP. A client, for example, a web browser, consumes the JSON data and uses it to render a frontend with the help of HTML and CSS. It's also common for JSON to be used to communicate between services. Here's an example of what the format looks like: ```json { "products": [{ "id": 1, "name": "a product" }, { "id": 2, "name": "another product" }] } ``` The above depicts a list of products. Each key needs to be encased with quotes and values can be everything from primitives like numbers, strings, Booleans etc to more complex types like an array or an object. what is this format, and what contexts is it used in. ## Reading JSON To work with JSON, you need to use the `encoding/json` library. It allows you to both read and write JSON data. To read JSON data you need to first have a structure in your Go code read to map to the JSON data. Imagine having the following JSON data: ```json { "products" : [ { "id": 1, "name": "some product" } ] } ``` To map this in Go, you need a struct that matches its structure like so: ```go type Product struct { Id int Name string } type Response struct { Products []Product } ``` ### Notation The first step is to create the structures needed to match your JSON data. But you also need to do one more thing, add notations. The problem we are looking to solve is how the `Product` property is called `Id` and not `id` as it's called in the JSON data. > Won't that just work? Unfortunately, not > Why don't you just name the struct property `id` The library `encoding/json`, is a separate package from the main package, meaning we need to make a field name, defined in the main package, uppercase for the `encoding/json` package to find it. So that means, we need to add the following annotations to our above created structs: ```go type Product struct { Id int `json: "id"` Name string `json: "name"` } type Response struct { Products []Product `json: "products"` } ``` What these annotations do is to say, in the JSON data, look for properties with these names and map them to the following property. Like in this example from above: ```go Id int `json: "id"` ``` ### Reading the data Ok, so we've defined the structures in Go that we will map our JSON data to. So how do we read from a JSON source? Well, JSON is usually stored in one of two ways: - a string literal, like so `{ "name": "my product", "id": 1 }` - in a JSON file: ```json { "id": 1, "name": "my product" } ``` Let's show how to work with both approaches: **Reading from a string literal** We will use the `Unmarshal()` function and provide it with the string literal as the first parameter and the data to write the result to as the second parameter. ```go package main imports ( "fmt" "encoding/json" ) func main() { str := `{ "name": "my product", "id": 1 }` product := Product{} json.Unmarshal([]byte(str), &product) fmt.Println(product) // prints the object } ``` Note how we also convert the response to a byte array `[]byte(str)` and how the data is written in the second parameter to the `product` instance as a reference, `&product`. **read from a file** To read from a file, we will use the `io/ioutil` library and its `ReadFile()` function. Like with the string literal, the `Unmarshal()` function will be used to write the data to a struct instance. ```go package main import ( "encoding/json" "fmt" "io/ioutil" "iohelper/dir" "iohelper/file" "log" ) type Products struct { Products []Product `json: products` } type Product struct { Id int `json: "id"` Name string `json: "name"` } func main(){ file, _ := ioutil.ReadFile("products.json") data := Products{} _ = json.Unmarshal([]byte(file), &data) for i := 0; i < len(data.Products); i++ { fmt.Println("Product Id: ", data.Products[i].Id) fmt.Println("Name: ", data.Products[i].Name) } } ``` ## Writing JSON We've seen so far how we can read JSON data either from a string literal or from a file, but what about writing data? Two cases are important for us: - Writing data to a structure. We are working with structs so any changes we do on the structs need to be converted back to JSON. - Generate JSON from data. In the second case, we might be working with a raw string literal or even with pure primitives, how would we do that? Let's address both these cases. What these cases have in common is the `Marshal()` function. The `Marshal()` method can take a primitive or a struct and taking that into JSON: ```go package main import ( "fmt" "encoding/json" ) type Person struct { Id int `json: "id"` Name string`json: "name"` } func main() { aBoolean, _ := json.Marshal(true) aString, _ := json.Marshal("a string") person := Person{ Id: 1 Name: "a person" } aPerson, _ := json.Marshal(&person) fmt.Println(string(aBoolean)) // true fmt.Println(string(aString)) // a string fmt.Println(string(person)) // { "id": 1, "name": "a person" } } ``` ## Assignment Given the following file *response.json*, find a way to read the data and display it on the screen: ```json { "orders": [ { "id": 1, "items": [ { "id": 1, "quantity": 3, "total": 34.3 }, { "id": 2, "quantity": 2, "total": 17.8 } ] }, { "id": 2, "items": [ { "id": 3, "quantity": 3, "total": 10.0 }, { "id": 4, "quantity": 2, "total": 100.5 } ] } ] } ``` ## Solution ```go package main import ( "encoding/json" "fmt" "io/ioutil" ) type OrderItem struct { Id int `json: "id"` Quantity int `json: "quantity"` Total float32 `json: "total"` } type Order struct { Id int `json: "id"` Items []OrderItem `json: items` } type Response struct { Orders []Order `json: orders` } func main() { file, _ := ioutil.ReadFile("orders.json") data := Response{} _ = json.Unmarshal([]byte(file), &data) fmt.Println(data) for i := 0; i < len(data.Orders); i++ { fmt.Println("Order Id: ", data.Orders[i].Id) for j := 0; j < len(data.Orders[i].Items); j++ { item := data.Orders[i].Items[j] fmt.Println("Item id", item.Id) fmt.Println("Item quantity", item.Quantity) fmt.Println("Item total", item.Total) } } } ``` ## 🚀 Challenge See if you can add `products` to your JSON file. Here's the JSON for it: ```json "products" :[{ "id": 1 "name" "product" }] ``` What structs do you need and how would you iterate over them? ## Learn more ================================================ FILE: 04-webdev/01-json/main.go ================================================ package main import ( "encoding/json" "fmt" ) type Person struct { Name string `json: "name"` Age int `json: "age"` } func main() { str := `{ "name": "chris", "age": 20 }` person := Person{} json.Unmarshal([]byte(str), &person) fmt.Println(person) } ================================================ FILE: 04-webdev/01-json/orders.go ================================================ package main import ( "encoding/json" "fmt" "io/ioutil" ) type OrderItem struct { Id int `json: "id"` Quantity int `json: "quantity"` Total float32 `json: "total"` } type Order struct { Id int `json: "id"` Items []OrderItem `json: items` } type Response struct { Orders []Order `json: orders` } func main() { file, _ := ioutil.ReadFile("orders.json") data := Response{} _ = json.Unmarshal([]byte(file), &data) fmt.Println(data) for i := 0; i < len(data.Orders); i++ { fmt.Println("Order Id: ", data.Orders[i].Id) for j := 0; j < len(data.Orders[i].Items); j++ { item := data.Orders[i].Items[j] fmt.Println("Item id", item.Id) fmt.Println("Item quantity", item.Quantity) fmt.Println("Item total", item.Total) } } } ================================================ FILE: 04-webdev/01-json/orders.json ================================================ { "orders": [ { "id": 1, "items": [ { "id": 1, "quantity": 3, "total": 34.3 }, { "id": 2, "quantity": 2, "total": 17.8 } ] }, { "id": 2, "items": [ { "id": 3, "quantity": 3, "total": 10.0 }, { "id": 4, "quantity": 2, "total": 100.5 } ] } ] } ================================================ FILE: 04-webdev/01-json/person.json ================================================ { "name": "chris", "age": 20 } ================================================ FILE: 04-webdev/02-web-dev/README.md ================================================ # Build a Web API A Web API is usually what we interact with to serve data to our services or a client. Said client may either be web page or tool like curl. In this chapter, we will learn to build a Web API that will process requests via HTTP. ## Introduction In this chapter, you will learn the following: - What's a Web API. - The `net/http` library, and its capabilities at high-level. - Responding to a request. - Working with request data like router and query parameters but also the body. - Using ServeMux, and why it may the preferred choice. ## Web API Common responsibilities for web services are to respond to requests: - **asking for data** and serve data like JSON, XML images, CSS, HTML - **asking to modify a resource** either by creating, updating, or deleting it. ## The `net/http` library There's a library `net/http` that will help us build a web server. Building a web server with this library involves the following: - Create a server instance. - Define route requests and how to respond to them. - Start the server instance, making sure it's accessible on a certain address and port. ### Create a server instance In `net/http`, `http` represents your service instance. ```go import ( "fmt" "net/http" ) func main() { // do something with `http` } ``` ### Define routes A route is you defining logical separations in your app like `products`, `orders` or some other area it makes sense to divide your app in. To define a route, you define a route pattern and function that is invoked when the route is hit: ```go func hello(w http.ResponseWriter, req *http.Request) { fmt.Fprintf(w, "hello\n") } func main(){ http.HandleFunc("/hello", hello) } ``` In the code above, the string "/hello" is a route pattern that states that all web requests to "/hello" should be handled by the `hello()` function. ### Response and Request A close inspection of the `hello()` function reveals that it takes a `ResponseWriter` and `Request`: ```go func hello(w http.ResponseWriter, req *http.Request) { fmt.c(w, "hello\n") } ``` The expectation is that you inspect the `req` object, your request for any data that decides what to return. Then you are to use `w` to produce a response. In this case, you are returning a string by passing `w` to `Fprintf()`. `FPrintf()` takes a writer. The writer is anything IO, so it could be, be writing to a file for example as well, or as in this case an HTTP response stream. ### Start the server Ok, we've gone through routes, producing a response, how would we get this server activated so it starts responding to requests? You use the `ListenAndServe()` function that takes a port like so: ```go http.ListenAndServe(":8090", nil) ``` ## Responding to a request An incoming request could be asking for a specific route like /products or /orders for example, or it could be asking for a specific static file like an image, a text file or maybe CSS. The request itself gives us a hint, about not only the logical domain it wants data from, like orders or products but what data type it wants, or it might even present credentials for authentication. The hint is known as headers. ### Header There's a concept called headers. A header is giving off a piece of information that could say what piece of content it is, how big the content is, or it could be a token helping you authenticate for example. Headers can exist both on the incoming request as well as the response. ### Serving different types of content Serving different types of content means that we are working on the response. To serve various content type, we need to instruct the response on what type of content it is so that a consuming client knows how to interpret it, (in some cases, clients like a web browser can figure that out anyway through a process called content sniffing). To serve a specific type of content, there are two things you need to do: - **set the content type**, you set the content type by calling: ```go w.Header().Set("Content-Type", "image/jpeg") ``` Here the content type is an image of subtype jpeg. There are many content types you could be setting like plain text, CSS, JSON, XML and more. - **produce the response**. Producing a response means writing to the response stream. That can be done by calling the `Write()` method on the `ResponseWriter` instance we are passed when we handle a route. There are other methods capable of writing to said stream as well. ### Serving image data To serve an image, you need to load it into memory, set the content type and write it to the response stream like below code: ```go func GetImage(w http.ResponseWriter, r *http.Request) { f, _ := os.Open("/image.jpg") // Read the entire JPG file into memory. reader := bufio.NewReader(f) content, _ := ioutil.ReadAll(reader) // Set the Content Type header. w.Header().Set("Content-Type", "image/jpeg") // Write image to the response. w.Write(content) } ``` - First, we open the image: ```go f, _ := os.Open("/image.jpg") ``` - Secondly, we read the file into memory: ```go reader := bufio.NewReader(f) content, _ := ioutil.ReadAll(reader) ``` - Thirdly, set the `Content-Type` header and tell it it's a JPEG image, with the value "image/jpeg": ```go w.Header().Set("Content-Type", "image/jpeg") ``` - Finally, we write the content to the response: ```go w.Write(content) ``` ### Serving JSON data Just like with serving images, we need to follow a similar approach of configuring the correct content-type header and then constructing the response. Here's the code: ```go package main import ( "encoding/json" ) type Person struct { Id int Name string } func ReturnJson(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") p := Person { Id: 1 Name: "a person" } json.NewEncoder(w).Encode(p) } func main() { http.HandleFunc("/json", ReturnJson) } ``` - First, we set the content type, by setting the value "application/json": ```go w.Header().Set("Content-Type", "application/json") ``` - Secondly, we construct the data we are about to send out: ```go p := Person { Id: 1 Name: "a person" } ``` - Finally, we encode the data as JSON and write it to the response stream: ```go json.NewEncoder(w).Encode(p) ``` It's also possible to use the `Marshal()` function like so, instead of `json.NewEncoder()`: ```go data, err := json.Marshal(p) w.Write(data) ``` ## Working with the request There are various ways, additionally to headers, to instruct the server program what to do: - **HTTP verb**, the HTTP verb expresses intention. The POST verb means to create a resource and the GET verb says to only read the data for example. There are many HTTP verbs that we will cover later in this chapter. These two below requests mean different things: ```text GET /products # fetching a list of products POST /products # creating a new product resource ``` - **body**, The body can contain a payload, data we can use to create or update a resource usually. Here's an example: ```json { "name" : "a new product" } ``` - **router parameters**. As part of a route request, you can have parameters that carry meaning. If the client asks for the route `/products/5` then the 5 can mean the calling client is after a specific product whose unique identifier is 5. - **query parameter**. At the end of the route, there can be a query section. That section can give further instruction to the request to for example reduce the size of the returning data. Does the query part start with a question mark? and is followed by key-value pairs separated by ampersands, &. It can look like so: `/products?page=1&pageSize=20` ### Parsing a body The request has a `Body` property. Depending on what's in the body, you might need to decode it. Below code is decoding a piece of JSON and writing it to the response stream: ```go package main import ( "fmt" "encoding/json" ) type Person struct { Id int Name string } func handleRequest(w http.ResponseWriter, r *http.Request) { var p Person json.NewDecoder(r.Body).Decode(&p) // save person to storage fmt.Fprintf(w) } func main() { mux := http.NewServeMux() mux.HandleFunc("/person/", handleRequest) err := http.ListenAndServe(":4000", mux) } ``` ### Read a route parameter There's no built-in way to access a route parameter so you would have to parse it like so: ```go tokens := strings.Split(r.URL.Path, "/") // check each part ``` or use for example a regular expression to parse out the parts. Another choice is using a library like the following: - [httprouter](https://github.com/julienschmidt/httprouter) - [Gorilla Mux](http://www.gorillatoolkit.org/pkg/mux) Here's an example using `httprouter`: ```go func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { fmt.Fprintf(w, "The id us, %s!\n", ps.ByName("id")) } func main() { router := httprouter.New() router.GET("/products/:id", Hello) log.Fatal(http.ListenAndServe(":8080", router)) } ``` ### Read a query parameter The query part of the route is accessible via the `Query()` function on the `URL` property of the request instance: ```go r.URL // /products?page=3&pageSize=20 r.URL.Query() // ?page=3&pageSize=20 ``` To access a specific parameter know that the `Query()` function returns a `Values` map. ```go r.URL.Query()["page"] // 3 ``` It's possible to call the `Get()` function as well, but only if there is only one parameter: ```go r.URL.Query().Get("page") ``` ### HTTP method The method means different things and should be handled differently. To access the request method, there's a `Method` property on the request, `r`. ```go r.Method ``` There's also defined constant like `MethodGet`, `MethodPost` on `http`, so you could write code like so: ```go func handleRoute(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodGet { fmt.Println("It's a GET request") } } ``` ## ServeMux, a better way So far, you've created an HTTP server by calling `ListenAndServe()` with a port argument and nil. But there's another way to do it. You could be using something called servemux. A servemux is also known as a router. Much like using the `http` directly to add routes, you instead add those routes on the servemux. Let's show some code: ```go mux := http.NewServeMux() mux.Handle("/hello", handleHello) http.ListenAndServe(":8090", mux) ``` In the preceding code, you instantiate the servemux by calling `NewServeMux()`. Then, you set up a route and its handler by calling `Handle()`. Finally, you call `ListenAndServe()` but this time around you pass the `mux` instance instead of `nil`. So how is this better than the other way we've used so far? The first way we learned about, uses a `DefaultServeMux` and risks exposing profiling endpoints, which is bad. Another reason is connecting the routes directly to `http` changes the global state, which is looked down upon in Go generally. ## Assignment - build a first web app Your web app should have at least one route. The said route should write to the response stream. The web app should start at a specific port, for example, 5500. ## Solution ```go package main import ( "fmt" "net/http" ) func handleRequest(w http.ResponseWrite, r *http.Request) { fmt.Fprintf(w, "Hi there") } func main() { http.HandleFunc("/hello", handleRequest) http.ListenAndServe(":8090", nil) } ``` ## Challenge - list details on the request such as the route, the verb used and the query parameters. - See if you can serve up different types of data like JSON or images. ================================================ FILE: 04-webdev/02-web-dev/main.go ================================================ package main import ( "fmt" "net/http" ) func hello(w http.ResponseWriter, req *http.Request) { fmt.Fprintf(w, "hello\n") } func headers(w http.ResponseWriter, req *http.Request) { for name, headers := range req.Header { for _, h := range headers { fmt.Fprintf(w, "%v: %v\n", name, h) } } } func main() { http.HandleFunc("/hello", hello) http.HandleFunc("/headers", headers) http.ListenAndServe(":8090", nil) } // todo, JSON body // router params // query params // sqlite? ================================================ FILE: 05-misc/01-logs/README.md ================================================ # Better logging with a logging library Once you start writing code, you realize quite early that you need to print things to the screen as well as sometimes to a file or even a log service. What you want to say is usually what type of logging you want to do. ## Introduction In this chapter, you will learn the following: - Why and what to log. - Using the `log` library. ## Reasons to log There are many reasons to log, here are some reasons: - **Information**, there might be a case where you want to provide some type of information that could be of use to the one using the program. - **Success**. A success message is a little more than just information, it indicates that you succeeded with something. - **Warning**. When you have a warning, something happened that you should be aware of. It's usually not serious enough to shut down the app but it should make you vigilant, it could be that memory is running low for example. - **Error**. When you get an error, you tend to end up in a state where it's no longer a wise choice to continue. - **Performance**. It's common to measure how long something takes, for the sake of improving things this information can be useful. - **Other**. There are also other reasons why you would log something, usually, that's connected to your business. ## What to log The general rule is the more you can log the better. Especially if it's an error you want to fix you might want to log things like: - When it happened - What happened - Specific error info For every case, you want to log, have a log at how the log message will be used, will a team be logging through these logs, and what would help them. See if you can interview someone on that team. ## Using `log` In general, you want to log in places where things might go wrong such as when you make web requests, work with I/O and so on. In general, use these as guidelines for when to log: - **Faulty input**. If the program risks producing a faulty response, there was a problem converting/casting a number or it received an unexpected input for example. - **Error state**. If the program ends up in a state from which it can't recover, for example, unable to fetch a batch of data from a data source. You don't want logs on every single line of code. ### Standard log `Println()` To produce a standard log message, you can use the `Println()` function in the `log` package. It takes a string and will produce a log message that combines a date, time, and your error message. Here's some code using `Println()`: ```go package main import ( "fmt" "log" ) func main() { log.Println("log message") } ``` It will produce an output like so: ```output 2022/03/24 12:42:13 log message ``` ### Use `Fatal()` for errors `Println()` produces a normal looking log message with a date, time and message. `Fatal()` is used when you want to end the program. What `Fatal()` does is to print out the message you give it and call `os.Exit(1)`. Here's how it can be used: ```go log.Fatal("quit program due to ") ``` ### Log to a file If you develop an app, you are likely to run it and keep an eye on the console for what the app prints out. However, as your app becomes ready for production, you want to make sure that all logs that can be useful to analyse is kept somewhere, either sent to a log service or stored in a file. That way, you ensure that you can analyze these logs later to understand where things went wrong or if you want to analyze the performance of your program. To log into a file, you can use the `SetOutput()` function. It takes a file handler as input. Thereby, you can use these three lines of code to log: ```go f, err := os.OpenFile("testlogfile", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) defer f.Close() log.SetOutput(f) ``` - In the first line, you open up a file "testlogfile" and ensure you can append it to it. ```go f, err := os.OpenFile("testlogfile", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) ``` - In the second line, you ensure the file is closed the last thing that happens in the program. ```go defer f.close() ``` - Finally you call `SetOutput()` which ensures all log message are sent to file "testlogfile", and not shown in the console. ```go log.SetOutput(f) ``` ## Assignment In this assignment, you will add the `log` library to your code. 1. Create a file *records.csv* with the following content: ```text item,quantity 112, 2 94, 3 ``` 1. Create a file *app.go* and give it the following content: ```go package main import ( "fmt" "io/ioutil" "os" ) func ProcessFile(path string) { filebuffer, err := ioutil.ReadFile(path) if err != nil { fmt.Println("Error: ", err) os.Exit(1) } inputdata := string(filebuffer) fmt.Println("Do something with input: \n", inputdata) } func main() { fileName := "records.csv" fmt.Printf("processing file '%s' \n", fileName) ProcessFile(fileName) } ``` 1. Run the file with `go run`: ```go go run app.go ``` You should see the following output: ```output processing file 'records.csv' Do something with input: item,quantity 112, 2 94, 3 ``` 1. Add the `log` package to the import and replace all calls to `fmt` with `log`, like so: ```go package main import ( "io/ioutil" "log" ) func ProcessFile(path string) { filebuffer, err := ioutil.ReadFile(path) if err != nil { log.Fatal("Error: ", err) } inputdata := string(filebuffer) log.Print("Do something with input: \n", inputdata) } func main() { fileName := "records.csv" log.Printf("processing file '%s' \n", fileName) ProcessFile(fileName) } ``` Let's see how the output differs. 1. Run the program with `go run`: ```go go run main.go ``` your output should be similar to: ```output 2022/03/28 13:57:57 processing file 'records.csv' 2022/03/28 13:57:57 Do something with input: item,quantity 112, 2 94, 3 ``` 1. Next, lets change the name of `fileName` to "record.csv", to trigger an error (there's no such file). ```go fileName := "record.csv" ``` 1. Now, run the app `go run`: ```bash go run app.go ``` You should see a similar output: ```output 2022/03/28 14:04:52 processing file 'record.csv' 2022/03/28 14:04:52 Error: open record.csv: no such file or directory exit status 1 ``` This time around though you see the program exciting with exit status 1. The conclusion is that it's better to rely on the `log` library because you get dates and times, and you type less. But there's more, we can log to a file, let's see how we do that next. ### Log to a file Someone examining the output of the program is likely to inspect a log file overlooking the terminal. Let's instruct `log` to log to a file instead. 1. At the start of the `main()` function, add the following code: ```go logFile := "logfile" f, err := os.OpenFile(logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) if err != nil { log.Fatal("Could not log to file: ", logFile) } defer f.Close() ``` Now you have instructed `log` to write all entries to the file *logfile*. 1. Run the program again, `go run`: ```bash go run main.go ``` You should now see the following output: ```output exit status 1 ``` All your log entries have moved to *logfile*, let's see what it looks like: ```text 2022/03/28 14:11:24 processing file 'record.csv' 2022/03/28 14:11:24 Error: open record.csv: no such file or directory ``` Great, now we have all entries in a central place, which should make it easier for us to analyze. ## Solution ```go package main import ( "io/ioutil" "log" "os" ) func ProcessFile(path string) { filebuffer, err := ioutil.ReadFile(path) if err != nil { log.Fatal("Error: ", err) } inputdata := string(filebuffer) log.Print("Do something with input: \n", inputdata) } func main() { fileName := "record.csv" logFile := "logfile" f, err := os.OpenFile(logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) if err != nil { log.Fatal("Could not log to file: ", logFile) } defer f.Close() log.SetOutput(f) log.Printf("processing file '%s' \n", fileName) ProcessFile(fileName) } ``` ================================================ FILE: 05-misc/01-logs/batch.go ================================================ package main import ( "io/ioutil" "log" "os" ) func ProcessFile(path string) { filebuffer, err := ioutil.ReadFile(path) if err != nil { log.Fatal("Error: ", err) } inputdata := string(filebuffer) log.Print("Do something with input: \n", inputdata) } func main() { fileName := "record.csv" logFile := "logfile" f, err := os.OpenFile(logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) if err != nil { log.Fatal("Could not log to file: ", logFile) } defer f.Close() log.SetOutput(f) log.Printf("processing file '%s' \n", fileName) ProcessFile(fileName) } ================================================ FILE: 05-misc/01-logs/logfile ================================================ 2022/03/28 14:11:24 processing file 'record.csv' 2022/03/28 14:11:24 Error: open record.csv: no such file or directory ================================================ FILE: 05-misc/01-logs/main.go ================================================ package main import ( "errors" "fmt" "log" "os" "time" ) var ErrorDivideBeZero = errors.New("Divide by zero") func Divide2(nominator int, divider int) (float32, error) { if divider <= 0 { return 0, ErrorDivideBeZero } return float32(nominator) / float32(divider), nil } func Divide(nominator int, divider int) float32 { if divider <= 0 { panic("divider below zero") } return float32(nominator) / float32(divider) } func main() { f, err := os.OpenFile("testlogfile", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) defer f.Close() log.SetOutput(f) log.Println("starting batch job", time.Now()) fmt.Println(Divide(10, 2)) val, err := Divide2(10, 0) if err != nil { log.Fatalln(err) // fmt.Println(err) // os.Exit(-1) } fmt.Println(val) log.Println("stopping batch job", time.Now()) } ================================================ FILE: 05-misc/01-logs/records.csv ================================================ item,quantity 112, 2 94, 3 ================================================ FILE: 05-misc/01-logs/testlogfile ================================================ 2022/03/09 21:44:02 starting batch job 2022-03-09 21:44:02.92816 +0000 GMT m=+0.000518959 2022/03/09 21:44:22 starting batch job 2022-03-09 21:44:22.138381 +0000 GMT m=+0.000334896 2022/03/09 21:44:22 stopping batch job 2022-03-09 21:44:22.13893 +0000 GMT m=+0.000883596 2022/03/09 21:45:20 starting batch job 2022-03-09 21:45:20.613968 +0000 GMT m=+0.000357770 2022/03/09 21:45:20 Divide by zero 2022/03/24 12:42:13 starting batch job 2022-03-24 12:42:13.933843 +0000 GMT m=+0.006561061 2022/03/24 12:42:13 Divide by zero ================================================ FILE: 05-misc/02-strings/README.md ================================================ # Working with strings There are many reasons why we need to work with strings in different ways. Below are some situations that you are likely to encounter and that the `strings` library has a solution for: - **user input**, or other types of storage may contain special characters that you need to cater for. - **inspection**, does the string contain what we need for our business logic? - **parsing**, splitting the file until we get what we need, could be things like a number or date for example. - **presentation**, sometimes we need to present the text in certain way in a UI for example, to highlight said information. ## Handling special characters Lets say we read user input and we want to interpret what we get as a number. To make our program robust, we're ok with the user typing spaces or newline characters. The following input should be allowed: ```text 114 114 114\n ``` There's three methods of interest to handle such a case for us, namely `Trim()`, `TrimLeft()` and `TrimRight()`. - `Trim()`, what it does is remove whitespace characters from both left and right side. - `TrimLeft()`. It removes whitespace from the left side only, and if you specify special characters as well. - `TrimRight()`. It removes whitespace from the right side only, and if you specify special characters as well. All these functions above have as their second parameter a so called *cutset* parameter, where you specify what character you want to get rid of. You can for example specify to remove space, newline and tab characters like so: ```go " \n\t" ``` Here's some code that shows all three methods in use: ```go fmt.Printf("%s , string length %d \n", s, len(s)) res := strings.Trim(s, " ") fmt.Printf("%s , string length %d \n", res, len(res)) s2 := " 114 " fmt.Printf("%s , string length %d \n", s2, len(s2)) res = strings.TrimLeft(s2, " ") fmt.Printf("%s , string length %d \n", res, len(res)) s3 := " 114 " fmt.Printf("%s , string length %d \n", s3, len(s3)) res = strings.TrimRight(s3, " ") fmt.Printf("%s , string length %d \n", res, len(res)) ``` The above string has three whitespaces to the left and two to the right, giving it a total length of 8. The output of the above code is: ```output 114 , string length 8 114 , string length 3 114 , string length 8 114 , string length 5 114 , string length 8 114 , string length 6 ``` Lets break down the output per row. For this output, using `Trim()`, the spaces are removed on both sides and we end up with something looking left aligned: ```output "114" ``` The next output, using `TrimLeft()`, shows how the right spaces are still there: ```output "114 " ``` Our final row, using `TrimRight()`, shows a right alignment and how the spaces on the left side still remains: ```output " 114" ``` ## Inspect with `Contains()` Imagine you want to inspect a string to verify whether it contains a certain substring. For that, you can use the `Contains()` function. Its syntax looks like so: ```go strings.Contains(stringSource, pattern) ``` You can then for example use it to process a list from a point of sale system, and for each item check if it contains a certain prefix: ```go rows := []string{"order: 5", "order: 10", "order: 5", "separator"} for item := range rows { if strings.Contains("order") { // process order } // ignore } ``` ## Parsing with `Split()` Lets continue with processing rows from our point of sale system. This time, we will be looking at a specific item and extract the information we need. For this, we will use the `Split()` function: ```go rows := []string{"order: 5", "order: 10", "order: 5", "separator"} for item := range rows { if strings.Contains("order") { tokens := strings.Split(item, ":") // [ "order", " 5"] value := strings.Trim(tokens[1]) fmt.Println(value) } // ignore } ``` By using this code: ```go strings.Split(item, ":") ``` on this string "order: 5", we end up with an array `["order", "5"]` and `strings.Trim(tokens[1])` would then refer to 5. > TIP: If we need to treat the 5 above as a number, as part of calculation, we would need to convert it to a number first ## Presentation Say you have customer management system and there's a lot of data to present. To give importance to certain data, we can use functions to highlight their visual appearance. Take the following multiline customer string: ```text Jean Normand 123 Way Washington ``` If you use `ToUpper()` on city you get a result like so: ```output Jean Normand 123 Way WASHINGTON ``` With `ToLower()` you ensure all characters are formatted as lowercase. ## Assignment Write a program that given a struct containing, name, address and city ensures that the name is lowercase and the address is uppercase. ## Solution ```go package main import ( "fmt" "strings" ) type Person struct { Name string Address string City string } func main() { person := Person{Name: "jean Normand", Address: "123 Way", City: "Washington"} fmt.Println(strings.ToUpper(person.Name)) fmt.Println(person.Address) fmt.Println(strings.ToUpper(person.City)) } ``` ================================================ FILE: 05-misc/02-strings/contains.go ================================================ package main import ( "fmt" "strings" ) func main() { fmt.Println(strings.Contains("hello all", "el")) } ================================================ FILE: 05-misc/02-strings/presentation.go ================================================ package main import ( "fmt" "strings" ) type Person struct { Name string Address string City string } func main() { person := Person{Name: "Jean Normand", Address: "123 Way", City: "Washington"} fmt.Println(strings.ToLower(person.Name)) fmt.Println(person.Address) fmt.Println(strings.ToUpper(person.City)) } ================================================ FILE: 05-misc/02-strings/strings.go ================================================ package main import ( "fmt" "strings" ) func main() { s := " 114 " fmt.Printf("%s , string length %d \n", s, len(s)) res := strings.Trim(s, " ") fmt.Printf("%s , string length %d \n", res, len(res)) s2 := " 114 " fmt.Printf("%s , string length %d \n", s2, len(s2)) res = strings.TrimLeft(s2, " ") fmt.Printf("%s , string length %d \n", res, len(res)) s3 := " 114 " fmt.Printf("%s , string length %d \n", s3, len(s3)) res = strings.TrimRight(s3, " ") fmt.Printf("%s , string length %d \n", res, len(res)) } ================================================ FILE: 05-misc/03-regex/README.md ================================================ # Use regular expressions to parse text In this chapter, you'll learn how to use Regular Expressions to search and replace text. ## What is RegEx and what to use it for Regular Expressions or RegEx for short, is used for searching and replacing text. Technically a RegEx is a sequence of characters that specifies a search pattern. ## Why use RegEx RegEx primary usage area is for searching text, replacing it and also extracting text. There do exist string libraries that can do some of the functionality RegEx is capable of. Sometimes using those string libraries might even be the best thing to do. However, sometimes a RegEx pattern is better. > Fair word of warning though, RegEx is hard to get right. You are encouraged to learn more of how they work cause they are quite powerful. My hope is by you reading this chapter, that you will find RegEx less intimidating and see it as a valuable tool in your toolbox. ## Where is it used ? RegEx shows up in many different contexts: - **Text editors, any programs with a search**. In most text editors, for example Visual Studio Code, you can search for files and inside of files with a RegEx search pattern. - **Code**, many programming languages and runtimes have libraries that helps you use RegEx. ## Your first RegEx Let's construct a simple RegEx to get a feel for it. Here it is: ```text an ``` if you apply this search pattern `an` to the following text: ```text highlands is a part of Scotland ``` It will match like so: > highl**an**ds is a part of Scotl**an**d For simpler cases, where you are looking to see if a specific word matches, in one or more places in a sentence, a pattern like the above is enough. ### RegEx in Go To start using RegEx in Go, there's the regexp library. There are two approaches: - `regexp` directly, here's an example: ```go matched, err := regexp.FindString("an", "highlands is a part of Scotland") ``` Here, you get a boolean back that returns true if there's a match. - **compiled**, in this way, you compile a regular expression and then calls a method on it, like so: ```go r, _ := regexp.Compile("an") matches := r.FindAllString("highlands is a part of Scotland", -1) ``` The above returns a string array with all the matches, in this case `["an", "an"]`. In this version, you have more functions available. ## Character classes Character classes are able to distinguish between different types of characters. Different types can be newlines, digits, letters and so on. Let's have a look at some common types you are likely to encounter: |Type |Description | |---------|---------| |. | This type matches any character except for a carriage return | |\ | This type escapes what's coming next | | \w | matches any character from the latin alphabet including underscore _ | | \d | matches any digit | | \D | this is the inverse of \d and matches any character that's not a digit | | \s | matches a white space character like space tab, line feed etc. | Lets show an example: ```go matched, err := regexp.FindString("\d", "abc123") ``` There would be a match above due to 123. However, there would be no match against "abc" as there's no digits in it. ## Repetition If you want to express repetition, there's two characters of interest: - `+`, matches 1 to many characters. ```text \w+ ``` Given the string "aaaa bab" it would match: **aaaaab** **bab** as the above describes matching characters but not the white space. ```go r, _ := regexp.Compile("\\w+") matches := r.FindAllString("aaaa bab", -1) ``` Note the extra `\`, we need that because of the way we construct our Regex. - `*`, matches 0 to many characters. Lets say you want to match a postal address that starts with "PA" and may contain 0 or many numbers. It should then match strings: ```text PA PA111 ``` We can use a `*` to construct this looking like so: ```go regexp.MatchString("PA*", "PA") regexp.MatchString("PA*", "PA111") ``` - `?`, also known as a greedy or optional quantifier. It looks backwards and makes it optional and takes it, if it can. Consider this case: ```text http https ``` If you want to match them both, you can type: ```text https? ``` Another example is: ```text r, _ := regexp.Compile("an.") matches := r.FindAllString("and ant an", -1) ``` The above will only match **and** and **ant** but not *an*. If we modify the regex to `an.?` it will match **and ant an**. ## Anchors and boundaries There are different anchors you can use like for example: - `^`, beginning of the string. The following states that the string needs to begin with the following string "INV" to signify the start of an invoice row: ```text ^INV ``` - `$`, end of the string. An example could be matching a string ends with a certain domain ".com": ```text \.com$ ``` ## Groups Groups are way to capture part of a string and have that returned. It's very useful for parsing out the info you need. Consider this example parsing out the info from a CSV row: ```text Name: myarticle, Price: 114, Quantity: 3 ``` To get the data you need, you want everything after the colon, :. You can construct a RegEx like so: ```go \w+:\s?(\w+) ``` what we are doing is defining we want to capture a group using parenthesis `()` but that group should happen after: - a number of letters, `\w+` - followed by a colon, `:` - followed by 0 or 1 space `\s?` - then our group `(\w+)`, one ore more letters All this ends up capturing myarticle, 114 and 3. ## Named groups A named group is a group you want to capture where the groups have names. Why would you want that? Well, say that you want to break down a URL in pieces and wants to know what's what. Given a URL "http://myapi.com/products?page=1", you have: - `http`, the protocol. - `myapi.com`, the domain. - `/products`, is the route. - `?page=1`, is the query parameters. So how can we break it apart and give it a name? Well, to break it apart, we will use something called named groups, it will allow us to look at our matches and know what's what. So instead of getting: ```text http ``` We will get a key and value that says: ```text protocol: http ``` Syntax wise, we need to use `?` within our parenthesis (). You use the following syntax: ```text (?\w+) ``` In Go, we need a `P` right after the question mark, so the code for this would be: ```go r, err := regexp.Compile(`(?P\w+):`) ``` ### Extract the data from a URL Let's approach this problem then given the string "http://myapi.com/products?page=1": - matching the protocol: ```text ^(?\w+): ``` - domain, to match the domain as well, we're looking to capture everything after http:// and until the next /: ```text ^(?\w+):\/\/(?\w+\.\w+)\/? ``` - route, ok so we've matched up "http://mydomain.com" so far, now lets match the route, i.e what happens after the / but before any questions marks, ? - query params Here's what our Go code would look like: ```go r, err := regexp.Compile(`^(?P\w+):\/\/(?P\w+\.\w+)\/(?P\w+)\/?`) ``` Ok, so we have the pattern, what about printing the parsed parts? To pair the named groups with their values, we need to combine values from both the Regex and the response. First, we call `FindStringSubmatch()`, that will give us the values. ```go m := r.FindStringSubmatch("http://myapi.com/products") ``` Then, we need to match the names with these values. We will need to call `r.SubexpNames()` and iterate over the response. ```go result := make(map[string]string) for i, name := range r.SubexpNames() { if i != 0 && name != "" { result[name] = m[i] } } ``` Note this line where each name is assigned a value: ```go result[name] = m[i] ``` Finally, to get the values, we can print them out as they are now in a map structure: ```go fmt.Println(result["protocol"]) // http fmt.Println(result["domain"]) // myapi.com fmt.Println(result["route"]) // products ``` ## Assignment - create a Go program that parses a URL From the above use case on named groups, write a Go program that takes a URL and analyzes it. It should work like so: ```output Type URL: http://myapi.com/products The URL consist of: protocol: http domain: myapi.com route: products ``` ### Solution ```go package main import ( "fmt" "log" "regexp" ) func main() { var url string fmt.Println("Type URL: ") fmt.Scan(&url) r, err := regexp.Compile(`^(?P\w+):\/\/(?P\w+\.\w+)\/(?P\w+)\/?`) if err != nil { log.Fatal("Error compiling: ", err) } m := r.FindStringSubmatch(url) if m == nil { panic("mo match") } result := make(map[string]string) for i, name := range r.SubexpNames() { if i != 0 && name != "" { result[name] = m[i] } } fmt.Println("The URL consist of:") fmt.Println(result["protocol"]) fmt.Println(result["domain"]) fmt.Println(result["route"]) } ``` ## Replacing A common use case for Regex is when it's used to replace something with something else. There's more than one method in Go you could be using but one you could use is `ReplaceAllString()` that sits on the compiled RegEx object: ```go r := regexp.MustCompile(`aa`) s := r.ReplaceAllString("aabbcc", "cc") // s = ccbbcc ``` The above replaces all occurrences of `aa` with `cc` on the string `aabbcc`. You can also use capture groups and replace a captured group with a string. Here's an example: ```go r := regexp.MustCompile(`(\d)`) s := r.ReplaceAllString("productid:114", "0${1}") // s = productid:0114 ``` in the above case, we replace 114 with itself but we also prepend it with a 0. ### Use case, replace XML Nodes Imagine you are working with XML for example and want to rename all nodes with a certain name. Here's your XML ```xml Shakespeare Romeo and Juliet 400 paperback 17 Shakespeare Hamlet 270 paperback 15 ``` Imagine `title` should be replaced by `name`, how do we do that? Well, it would be straight forward to replace title by name. Let's say we have this file content though: ```xml Shakespeare The title is Romeo and Juliet 400 paperback 17 ``` Then we would not only rename the element `title` to `name` but also the content would be replaced o "The title is Romeo and Juliet", that's NOT what we want. We need to restrict the replace operation to only target element, like so: ```text \<\/?(title)\> ``` The above would match for example `` and ``. If we try this however on this XML, we almost get what we want: ```xml Shakespeare ``` becomes ```xml nameShakespearename ``` What happened, why did we loose `<>` ? We need a way to express keeping what was there before AND replace the name. A way to do that is to express capture groups on `<>` and the element name, like so: ```text (\<\/?)(title)(\>) ``` Now we have three groups, we need to fit the result together, and this is something we can express like so: ```text ${1}name${3} ``` - `${1}` corresponds to capture group matching `<` or ``. ## Assignment - replace content Take the file *books.xml* containing: ```xml Shakespeare Romeo and Juliet 400 paperback 17 Shakespeare Hamlet 270 paperback 15 ``` and replace: - author with name - cost with price TIP: you might need to apply the replace twice. ## Solution II ```go package main import ( "fmt" "regexp" ) func main() { file := ` Shakespeare Romeo and Juliet 400 paperback 17 Shakespeare Hamlet 270 paperback 15 ` r := regexp.MustCompile(`(\<\/?)(title)(\>)`) s := r.ReplaceAllString(file, "${1}name${3}") fmt.Println(s) r = regexp.MustCompile(`(\<\/?)(cost)(\>)`) s = r.ReplaceAllString(s, "${1}price${3}") fmt.Println(s) } ``` ================================================ FILE: 05-misc/03-regex/regex.go ================================================ package main import ( "fmt" "log" "regexp" ) func main() { var url string fmt.Println("Type URL: ") fmt.Scan(&url) // // "^([a-z]*\\.)+[a-z]*@ r, err := regexp.Compile(`^(?P\w+):\/\/(?P\w+\.\w+)\/(?P\w+)\/?`) if err != nil { log.Fatal("Error compiling: ", err) } m := r.FindStringSubmatch(url) if m == nil { panic("mo match") } result := make(map[string]string) for i, name := range r.SubexpNames() { if i != 0 && name != "" { result[name] = m[i] } } fmt.Println("The URL consist of:") fmt.Println(result["protocol"]) fmt.Println(result["domain"]) fmt.Println(result["route"]) } ================================================ FILE: 05-misc/03-regex/regex2.go ================================================ package main import ( "fmt" "regexp" ) func main() { file := ` Shakespeare Romeo and Juliet 400 paperback 17 Shakespeare Hamlet 270 paperback 15 ` r := regexp.MustCompile(`(\<\/?)(title)(\>)`) s := r.ReplaceAllString(file, "${1}name${3}") fmt.Println(s) r = regexp.MustCompile(`(\<\/?)(cost)(\>)`) s = r.ReplaceAllString(s, "${1}price${3}") fmt.Println(s) } ================================================ FILE: 05-misc/04-goroutines/README.md ================================================ # Goroutines and channels A goroutine is a lightweight thread managed by the Go runtime. Channels is how you communicate between routines. ## Introduction In this chapter you will: - Understand the difference between concurrency and parallelism - Use Goroutines to run your functions - Create and use channels to communicate between your Goroutines - Apply Goroutines to an app that searches files for faster execution. ## Concurrency, what's the benefit Concurrency is the task of running and managing the multiple computations at the same time. While *parallelism* is the task of running multiple computations simultaneously. So what are some benefits: - **Faster processing**. The benefit is getting tasks done faster. Imagine that you are searching a computer for files, or processing data, if it's possible to work on these workloads in parallel, you end up getting the response back faster. - **Responsive apps** Another benefit is getting more responsive apps. If you have an app with a UI, imagine it would be great if you can perform some background work without interrupting the responsiveness of the UI. ## Goroutines A goroutine is a lightweight thread managed by the Go runtime. What you do is to add the keyword `go` in front of a function. Here's an example: ```go go myFunction() ``` Imagine the following code running, what would happen? ```go package main import "fmt" func myFunction() { for i := 0; i < 3; i++ { fmt.Println("my function: ", i) } } func anotherFunction() { for i := 4; i <7; i++ { fmt.Println("another function: ", i) } } func main() { go myFunction() anotherFunction() } ``` It would only print the result from `anotherFunction()` as it takes a short while for the go routine to start up. You can have the go routine execute as well by adding a little delay, like so: ```go func main() { go myFunction() anotherFunction() time.Sleep(1 * time.Second) } ``` The result is now the following: ```output another function: 4 another function: 5 another function: 6 my function: 0 my function: 1 my function: 2 ``` The function with the go routine finishes last. Lets modify the code slightly and have the two functions use a delay, so we simulate workloads taking different time to finish: ```go func myFunction() { time.Sleep(1500 * time.Millisecond) for i := 0; i < 3; i++ { fmt.Println("my function: ", i) } } func anotherFunction() { time.Sleep(500 * time.Millisecond) for i := 4; i < 7; i++ { fmt.Println("another function: ", i) } } func main() { go myFunction() go anotherFunction() time.Sleep(2 * time.Second) } ``` at this point, `anotherFunction()` finishes first as it has the shortest delay, which is to be expected. Here's what the output looks like now: ```output another function: 4 another function: 5 another function: 6 my function: 0 my function: 1 my function: 2 ``` ### Use case - a file search Imagine you have case where you need to find a file on disk. If you write a function like so, it will search a directory and report back the result if the file is found: ```go func SearchFiles(dir string, lookFor string) string { log.Println("[SEARCHING] ", dir) files, err := ioutil.ReadDir(dir) if err != nil { log.Fatal(err) } for _, file := range files { log.Println(dir+file.Name(), file.IsDir()) if file.Name() == lookFor { return "[FOUND] " + filepath.Join(dir, file.Name()) } } return "[NOT FOUND] " + dir } ``` Imagine you now run this code like so, to search many directories: ```go result := make([]string, 0) append(result, SearchFile("./tmp", "myfile.txt")) append(result, SearchFile("./tmp2", "myfile.txt")) append(result, SearchFile("./tmp3", "myfile.txt")) append(result, SearchFile("./tmp4", "myfile.txt")) for i := 0 i< len(result); i++ { fmt.Println(result[i]) } ``` If found, you will get an output similar to the below, depending on whether *myfile.txt* is found in any of the searched directories: ```go [FOUND] ./tmp/myfile.txt [NOT FOUND] ./tmp2/myfile.txt [NOT FOUND] ./tmp3/myfile.txt [NOT FOUND] ./tmp4/myfile.txt ``` Now to speed up this process, it would be great if you are able to search many directories at once, so you could type something like so: ```go go SearchFile("./tmp", "myfile.txt") go SearchFile("./tmp2", "myfile.txt") go SearchFile("./tmp3", "myfile.txt") go SearchFile("./tmp4", "myfile.txt") ``` This works, it now searches all directories, in parallel. However, now we don't have a way to get the response back as we can't write like so: ```go result := make([]string, 0) go append(result,SearchFile("./tmp", "myfile.txt")) // won't compile, says "go discards results" ``` So how can we get the result from a go routine, the answer is by using channels, so lets discuss those next. ## Channels A channel is how we can communicate cross go routines but also between go routines and the part of our code not using a go routine. The idea is to send a value to a channel, and have part of our code listen to values from a channel. ### Creating a channel To create a channel, you need the keyword `chan` and the data type of the messages you are about to send into it. Here's an example: ```go ch := make(chan int) ``` In the above example, a channel `ch` will be created that accepts messages of type `int`. ### Sending a value to a channel To send to a channel, you need to use this operator `<-`, it look like a left pointing arrow and is meant to be read as the direction something is sent. Here's an example of sending a message to a channel: ```go ch <- 2 ``` In the above code, the number 2 is sent into the channel `ch`. ### Listening to a channel To listen to a channel, you again use the arrow `<-`, but this time you need a receiving variable on the left side and the channel on the right side, like so: ```go value := <- ch ``` ### Matching sending and receiving Let's say you have the following code: ```go package main import "fmt" func produceResults(ch chan int) { ch <- 1 ch <- 2 } func main() { ch := make(chan int) go produceResults(ch) var result int result = <-ch fmt.Println(result) result = <-ch fmt.Println(result) } ``` You are invoking `produceResults()` and it sends messages to the channel twice: ```go ch <- 1 ch <- 2 ``` in `main()`, you receive the results: ```go var result int result = <-ch fmt.Println(result) result = <-ch fmt.Println(result) ``` So what happens if you produce more values than you receive like so? ```go ch <- 1 ch <- 2 ch <- 3 ``` answer: you will miss out on the extra value. What if it's the opposite, you try to receive one more value than you actually get? ```go var result int result = <-ch fmt.Println(result) result = <-ch fmt.Println(result) result = <-ch fmt.Println(result) ``` At this point, your code will deadlock, like so: **fatal error: all goroutines are asleep - deadlock!**. Your code will never finish as that value will never arrive. The lesson here is that you need to keep track of how many results you might get and only try to receive that many. There's another way to receive values, and that's by using a `select` like so: ```go for i := 0; i < 2; i++ { select { case x, ok := <-ch: if ok { fmt.Println(x) } } } ``` The idea is to *match* the receiving of a value like so: ```go case x, ok := <-ch: ``` What you are getting is two things, the value itself `x` and `bool` we name `ok`. If we managed to get a value ok, then `ok` holds the value `true`. What happens if it's not ok then? It would be `false` if the channel is closed and can no longer produce any more values, so lets discuss that next. ### Closing a channel A channel is open until you close it. You can actively close it by calling `close()` with the channel as an input parameter: ```go close(ch) ``` However, when we close a channel, we need to test for it. If we attempt to receive a value from a closed channel, it will cause a crash. To test whether the channel is open or not, we can use the `select` we just wrote: ```go select { case x, ok := <-ch: if ok { fmt.Println(x) } else { break // channel is closed } } ``` The value of `ok` is now false. To apply the concept of closing a channel, we add `close()` to `produceResults()` and we have our for loop run one more time than there's values, like so: ```go package main import ( "fmt" ) func produceResults(ch chan int) { ch <- 1 ch <- 2 // ch <- 3 close(ch) } func main() { ch := make(chan int) go produceResults(ch) // time.Sleep(1 * time.Second) for i := 0; i < 3; i++ { select { case x, ok := <-ch: if ok { fmt.Println(x) } else { fmt.Println("channel closed") } } } } ``` The output of running said code is: ```output 1 2 channel closed ``` We can see how the `else` clause is matched on the third iteration. Now, we might have more long running tasks, at which point we need to sit and wait until the channel tells us it closed. Here's code to handle that: ```go label: for { select { case x, ok := <-ch: if ok { fmt.Println(x) } else { fmt.Println("channel closed") break label } } } ``` What's happening here is that we set up a for loop that runs forever, until closed. To ensure we break out of the for loop and not just the `select`, we add `label:` TODO, you can use range over the channel as well. ## Assignment - `SearchFiles()` with channels Let's take all our learning and add channels to the program we wrote containing a file searcher. ## Challenge ## Solution ```go package main import ( "io/ioutil" "log" "path/filepath" ) func SearchFiles(dir string, lookFor string, ch chan string) { log.Println("[SEARCHING] ", dir) files, err := ioutil.ReadDir(dir) if err != nil { log.Fatal(err) } for _, file := range files { log.Println(dir+file.Name(), file.IsDir()) if file.Name() == lookFor { ch <- "[FOUND] " + filepath.Join(dir, file.Name()) return } } ch <- "[NOT FOUND] " + dir } func main() { ch := make(chan string) go SearchFiles("./test/", "test2.txt", ch) go SearchFiles("./other/", "test2.txt", ch) var res = "" for i := 0; i < 2; i++ { res = <-ch log.Println(res) } } ``` ================================================ FILE: 05-misc/04-goroutines/channel.go ================================================ package main import ( "fmt" "time" ) func run(ch chan int, no int) { ch <- no } func main() { ch := make(chan int) go run(ch, 1) go run(ch, 2) time.Sleep(1 * time.Second) block: for { select { case x, ok := <-ch: if ok { fmt.Println(x) } else { fmt.Println("channel closed") break block } default: fmt.Println("No value to read, exiting") break block } } fmt.Println("Done with values") } ================================================ FILE: 05-misc/04-goroutines/channel1.go ================================================ package main import ( "fmt" ) func produceResults(ch chan int) { // time.Sleep(2 * time.Second) ch <- 1 ch <- 2 // ch <- 3 close(ch) } func main() { ch := make(chan int) go produceResults(ch) // time.Sleep(1 * time.Second) // for i := 0; i < 3; i++ { // select { // case x, ok := <-ch: // if ok { // fmt.Println(x) // } else { // fmt.Println("channel closed") // } // } // } // } label: for { select { case x, ok := <-ch: if ok { fmt.Println(x) } else { fmt.Println("channel closed") break label } // default: // fmt.Println("nothing") } } } ================================================ FILE: 05-misc/04-goroutines/file-search.go ================================================ package main import ( "io/ioutil" "log" "path/filepath" ) func SearchFiles(dir string, lookFor string) string { log.Println("[SEARCHING] ", dir) files, err := ioutil.ReadDir(dir) if err != nil { log.Fatal(err) } for _, file := range files { log.Println(dir+file.Name(), file.IsDir()) if file.Name() == lookFor { return "[FOUND] " + filepath.Join(dir, file.Name()) } } return "[NOT FOUND] " + dir } func main() { result := make([]string, 0) go append(result, SearchFiles("./test", "test2.txt")) } ================================================ FILE: 05-misc/04-goroutines/first.go ================================================ package main import ( "fmt" "time" ) func myFunction() { time.Sleep(1500 * time.Millisecond) for i := 0; i < 3; i++ { fmt.Println("my function: ", i) } } func anotherFunction() { time.Sleep(500 * time.Millisecond) for i := 4; i < 7; i++ { fmt.Println("another function: ", i) } } func main() { go myFunction() go anotherFunction() time.Sleep(2 * time.Second) } ================================================ FILE: 05-misc/04-goroutines/main.go ================================================ package main import ( "io/ioutil" "log" "path/filepath" ) func SearchFiles(dir string, lookFor string, ch chan string) { log.Println("[SEARCHING] ", dir) files, err := ioutil.ReadDir(dir) if err != nil { log.Fatal(err) } for _, file := range files { log.Println(dir+file.Name(), file.IsDir()) if file.Name() == lookFor { ch <- "[FOUND] " + filepath.Join(dir, file.Name()) return } } ch <- "[NOT FOUND] " + dir } func main() { ch := make(chan string) go SearchFiles("./test/", "test2.txt", ch) go SearchFiles("./other/", "test2.txt", ch) var res = "" for i := 0; i < 2; i++ { res = <-ch log.Println(res) } } ================================================ FILE: 05-misc/04-goroutines/other/test.txt ================================================ ================================================ FILE: 05-misc/04-goroutines/other/test3.txt ================================================ ================================================ FILE: 05-misc/04-goroutines/test/test.txt ================================================ dfdf ================================================ FILE: 05-misc/04-goroutines/test/test2.txt ================================================ ================================================ FILE: 05-misc/05-sqlite/README.md ================================================ # Working with a database You will be working with a sqlite database and read and write values to it. ## Introduction In this chapter you will: - Create a database and its structure. - Write to the database. - Read from the database. ## Select a sqlite driver To connect with a sqlite database we've got a few libraries to choose from. These libraries will provide you with a sqlite driver that you need to successfully connect with your database. Here are some common ones: - SQLite (uses cgo): [*] - SQLite (uses cgo): - Supports SQLite dynamic data typing - SQLite (uses cgo): - SQLite: (uses cgo): - SQLite: (pure go): Refer to this link to see libraries for other databases: - . ## Use `sqlite3` from the console To work with your database, it's beneficial to use sqlite from the command line. Consult the official [downloads page](https://www.sqlite.org/download.html) for sqlite and ensure you pick the executable for your operating system. Installing sqlite will give you an executable. With the executable, you can: - Create a database. - Run SQL commands. - Run other commands supported by the executable. ### Create a database To create a database, you give it the name of the database like so: ```console sqlite3 activities.db ``` The preceding command will give you a database in a file called "activities.db" > It will also start up a shell where you can execute SQL commands as well as commands supported by sqlite3. ### Run SQL commands Run a SQL command in the shell by typing SQL and end it by a semicolon, `;`. ```sql CREATE TABLE `person` ( `uid` INTEGER PRIMARY KEY AUTOINCREMENT, `name` VARCHAR(64) NULL, `lastname` VARCHAR(64) NULL, `created` DATE NULL ); ``` ### Exit the shell After you're done, you can exit the shell by typing `.exit`: ```console .exit ``` ## Talking to your database via Go To talk to your database via Go, there's some steps you need to take in order: 1. **Create a project**. You need to create a project so you can import a Go package containing your sqlite driver. Create a project by running `go mod init`. Below is an example: ```console go mod init "example-project" ``` 1. **Add imports**. Once you have the needed packages you need to refer to them in the import section: ```go import ( "database/sql" _ "github.com/mattn/go-sqlite3" ) ``` Above, are the two packages we will use, "database/sql" that provides an interface for us to run queries and statements. "github.com/mattn/go-sqlite3" contains the driver that will enable us connecting to the database. 1. **Establish connection**. To connect with the database, you call the `Open()` function on the `sql` instance like so: ```go db, err := sql.Open("sqlite3", "./mydb.db") ``` In the preceding command, we specify first the type of database and in the second parameter the name of the database and where it's located. We get a database instance back of type `*sql.DB`. 1. **Run queries**. At this point, we are free to run queries. You use `Query()` function and give it a SQL statement like in this example: ```go rows, err := db.Query("SELECT * FROM person") ``` To iterate over the results, you can use a for-loop like so: ```go for rows.Next() { var uid int var name string var lastname string var created time.Time err = rows.Scan(&uid, &name, &lastname, &created) } ``` 1. **Run prepared statements**. Prepared statements are SQL statements where we can provide parameter values at a later point. You call the `Prepare()` function with `?` as placeholders where there will be data inserted: ```go stmt, err := db.Prepare("UPDATE person set lastname=? where uid=?") ``` To run the statement against the database, you can call `Exec()`: ```go res, err := stmt.Exec("smith", 1) ``` The `res` instance coming back has a function `RowsAffected()` that returns the number of affected rows: ```go affected, _ := res.RowsAffected() ``` Getting affected rows is a good indicator that you actually changed something. 1. Close the database connection. You should close the database when you're done with it like so: ```go db.Close() ``` ## Assignment In this assignment, we will create a Go program that's able to write and write to the database. We will go all the way from creating the database with the console to writing the Go code needed. ### Create the database and populate it We will create our database using the sqlite executable in the console. 1. Run `sqlite` to create the database and initialize the sqlite shell: ```console sqlite3 mydb.db sqlite3SQLite version 3.32.3 2020-06-18 14:16:19 Enter ".help" for usage hints. ``` At this point, you have a database created. Next, we need some tables in there. 1. Run the following SQL command in the sqlite shell: ```sql CREATE TABLE `person` ( `uid` INTEGER PRIMARY KEY AUTOINCREMENT, `name` VARCHAR(64) NULL, `lastname` VARCHAR(64) NULL, `created` DATE NULL ); ``` You know have the table "person" created. Next, we need some data in the table that we will interact with later in our Go code. 1. Run this SQL command to insert data into "person" table: ```sql insert into person(name,lastname, created) values ("joe", "schmoo", '2021-01-01'); ``` Great, we now have data in our table. Time to focus on the Go code next. 1. Run `.exit` to exit the database. ### Create a project Now we will create a Go project and some code able to access our database. 1. Create *db.go* and give it this content: ```go package main import ( "database/sql" "fmt" "log" _ "github.com/mattn/go-sqlite3" ) func main() { db, err := sql.Open("sqlite3", "./mydb.db") if err != nil { log.Fatal(err) } fmt.Println("database open") fmt.Println("bye") fmt.Println("closing db") db.Close() } ``` Next, lets initialize our Go project. 1. Run the following commands to create our project: ```console go mod init sql-demo ``` 1. Run `go mo tidy`, to install the needed packages you specified in the import section of your program (this will download and add "github.com/mattn/go-sqlite3" to your project): ```console go mod tidy ``` ### Read data Next, we will add a function capable of reading data. 1. Add a function `Read()` like so: ```go func Read(db *sql.DB) { rows, err := db.Query("SELECT * FROM person") } ``` At this point, we have read the response into `rows`. Next, we need iterate on the response. 1. Add the following code, in `Read()` to iterate over the response: ```go for rows.Next() { var uid int var name string var lastname string var created time.Time err = rows.Scan(&uid, &name, &lastname, &created) if err != nil { log.Fatal(err) } fmt.Println(uid) fmt.Println(name) fmt.Println(lastname) fmt.Println(created) } ``` Not the usage of `Scan()` and variables being sent in as references so the response is written to them. ### Create data Now we will create code that will allow us to create data in our database. 1. Add a function `Read()`: ```go func Create(db *sql.DB) { stmt, err := db.Prepare("INSERT INTO person(name, lastname, created) values(?,?,?)") } ``` At this point, you have created a statement that when executed will attempt to insert row. Note the use of `?`, these are placeholders that you will need to provide values to at the moment of execution. 1. Add the following code to `Create()`: ```go if err != nil { log.Fatal(err) } res, err := stmt.Exec("Mrs", "Smith", "2022-01-01") if err != nil { log.Fatal(err) } affected, _ := res.RowsAffected() log.Printf("Affected rows %d", affected) ``` Note the call to `Exec()`, here you are providing data and `?` is being replaced by the values you send in. Also note the last two rows: ```go affected, _ := res.RowsAffected() log.Printf("Affected rows %d", affected) ``` Here for result `res` we are invoking `RowsAffected()` that returns the number of affected rows then we go on to print said value. ### Update and delete data Updating and deleting data takes on very similar approach to how to create data. You will use a statement that you prepare and then send in the real values. Below is the code for performing both these actions: **Update** ```go func Update(db *sql.DB) { stmt, err := db.Prepare("UPDATE person set lastname=? where uid=?") if err != nil { log.Fatal(err) } res, err := stmt.Exec("smith", 1) if err != nil { log.Fatal(err) } affected, _ := res.RowsAffected() log.Printf("Affected rows %d", affected) } ``` **Delete** ```go func Delete(db *sql.DB) { stmt, err := db.Prepare("delete from person where uid=?") if err != nil { log.Fatal(err) } res, err := stmt.Exec(1) if err != nil { log.Fatal(err) } affected, _ := res.RowsAffected() log.Printf("Affected rows %d", affected) } ``` ## Solution ```go package main import ( "database/sql" "fmt" "log" "time" _ "github.com/mattn/go-sqlite3" ) func Read(db *sql.DB) { rows, err := db.Query("SELECT * FROM person") if err != nil { log.Fatal(err) } for rows.Next() { var uid int var name string var lastname string var created time.Time err = rows.Scan(&uid, &name, &lastname, &created) if err != nil { log.Fatal(err) } fmt.Println(uid) fmt.Println(name) fmt.Println(lastname) fmt.Println(created) } } func Update(db *sql.DB) { stmt, err := db.Prepare("UPDATE person set lastname=? where uid=?") if err != nil { log.Fatal(err) } res, err := stmt.Exec("smith", 1) if err != nil { log.Fatal(err) } affected, _ := res.RowsAffected() log.Printf("Affected rows %d", affected) } func Create(db *sql.DB) { stmt, err := db.Prepare("INSERT INTO person(name, lastname, created) values(?,?,?)") if err != nil { log.Fatal(err) } res, err := stmt.Exec("Mrs", "Smith", "2022-01-01") if err != nil { log.Fatal(err) } affected, _ := res.RowsAffected() log.Printf("Affected rows %d", affected) } func Delete(db *sql.DB) { stmt, err := db.Prepare("delete from person where uid=?") if err != nil { log.Fatal(err) } res, err := stmt.Exec(1) if err != nil { log.Fatal(err) } affected, _ := res.RowsAffected() log.Printf("Affected rows %d", affected) } func main() { db, err := sql.Open("sqlite3", "./mydb.db") if err != nil { log.Fatal(err) } fmt.Println("database open") Create(db) Read(db) // Update(db) fmt.Println("bye") fmt.Println("closing db") db.Close() } ``` ================================================ FILE: 05-misc/05-sqlite/go.mod ================================================ module db-project go 1.16 require github.com/mattn/go-sqlite3 v1.14.12 ================================================ FILE: 05-misc/05-sqlite/go.sum ================================================ github.com/mattn/go-sqlite3 v1.14.12 h1:TJ1bhYJPV44phC+IMu1u2K/i5RriLTPe+yc68XDJ1Z0= github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= ================================================ FILE: 05-misc/05-sqlite/main.go ================================================ package main import ( "database/sql" "fmt" "log" "time" _ "github.com/mattn/go-sqlite3" ) func Read(db *sql.DB) { rows, err := db.Query("SELECT * FROM person") if err != nil { log.Fatal(err) } for rows.Next() { var uid int var name string var lastname string var created time.Time err = rows.Scan(&uid, &name, &lastname, &created) if err != nil { log.Fatal(err) } fmt.Println(uid) fmt.Println(name) fmt.Println(lastname) fmt.Println(created) } } func Update(db *sql.DB) { stmt, err := db.Prepare("UPDATE person set lastname=? where uid=?") if err != nil { log.Fatal(err) } res, err := stmt.Exec("smith", 1) if err != nil { log.Fatal(err) } affected, _ := res.RowsAffected() log.Printf("Affected rows %d", affected) } func Create(db *sql.DB) { stmt, err := db.Prepare("INSERT INTO person(name, lastname, created) values(?,?,?)") if err != nil { log.Fatal(err) } res, err := stmt.Exec("Mrs", "Smith", "2022-01-01") if err != nil { log.Fatal(err) } affected, _ := res.RowsAffected() log.Printf("Affected rows %d", affected) } func Delete(db *sql.DB) { stmt, err := db.Prepare("delete from person where uid=?") if err != nil { log.Fatal(err) } res, err := stmt.Exec(1) if err != nil { log.Fatal(err) } affected, _ := res.RowsAffected() log.Printf("Affected rows %d", affected) } func main() { db, err := sql.Open("sqlite3", "./mydb.db") if err != nil { log.Fatal(err) } fmt.Println("database open") Create(db) Read(db) // Update(db) fmt.Println("bye") fmt.Println("closing db") db.Close() } ================================================ FILE: 06-io/01-read-write-files/README.md ================================================ # Read and write to files There are different types of files, text files, files containing images, videos etc. For that reason you might want to read the content differently, either byte by byte or maybe even the entire file in one go, as text. ## Introduction In this chapter you will learn to: - Read content from text files. - Write to text files. - Append text at the end of a text file. - Analyze a file for its metadata like size, modified date and more. ## Read a text file One approach could be using the `ioutil` library and its `ReadFile()` method like so: ```go import ( "io/ioutil" "log" ) func main() { filebuffer, err := ioutil.ReadFile(path) if err != nil { log.Fatal(err) } var inputdata string = string(filebuffer) } ``` Note how the result of reading the file ends up in `filebuffer`. To interpret it as a string, you need to convert it via `string(filebuffer)`. Now, you're ready to process the file content, read it line by line or whatever you want to do. ## Write text to a file In this scenario, we are looking to do two things: - Create a file. - Write text to our newly created file. For this scenario, we can use the `os` library and a combination of the `Create()` method, to create a file and the `WriteString()` method to write a string to the file. ```go import ( "os" "log" ) f, err := os.Create(path) if err != nil { log.Fatal(err) } n, err := f.WriteString(content + "\n") if err != nil { log.Fatal(err) } fmt.Printf("wrote %d bytes\n", n) f.Sync() ``` First the file is created calling `Create()`. From that, we get a file handle `f`. With `f`, we can call `WriteString()` with a string. Lastly, we call `Sync()` to ensure the string is persisted in the file. ## Append to a file Appending text, implies you already have an existing file. When you append, you information to the end of the file. Appending is something you are likely to do when you add new entries to a log file or adding a new purchase to a Point of Sale, POS system in a grocery store for example. To append to a file you use the `OpenFile()` method in the `os` lib. What you need to do is to pass it some flags that states that you want to append content. You should also have a behavior that says, create if it doesn't already exist. You end up with code looking like so: ```go f, err := os.OpenFile("text.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { log.Println(err) } defer f.Close() ``` The 0644, is a 3x3 bit flag. It sets permissing for User (6, Read/Write), Group (4, Read), and Other (4, Read). To append a string, you can call `WriteString()` like so: ```go f.WriteString("my text \n") ``` ## Assignment Imagine you have a file *invoices.csv* looking like so: ```text customer, amount, date Wood LTD, 100, 2020-01-01 Metal, 345, 2020-01-29 Steel, 700, 2020-07-29 ``` Open up and read the file content, line by line. ## Solution ```go package main import ( "fmt" "io/ioutil" "log" "strings" ) func main() { var path = "invoices.csv" filebuffer, err := ioutil.ReadFile(path) if err != nil { log.Fatal(err) } var inputdata string = string(filebuffer) rows := strings.Split(inputdata, "\n") for _, row := range rows { fmt.Println("row:", row) } } ``` ## Challenge See if you can split up each row further in columns and summarize how much the orders are worth in total. ================================================ FILE: 06-io/01-read-write-files/invoices.csv ================================================ customer, amount, date Wood LTD, 100, 2020-01-01 Metal, 345, 2020-01-29 Steel, 700, 2020-07-29 ================================================ FILE: 06-io/01-read-write-files/main.go ================================================ package main import ( "fmt" "io/ioutil" "log" "strings" ) func main() { var path = "invoices.csv" filebuffer, err := ioutil.ReadFile(path) if err != nil { log.Fatal(err) } var inputdata string = string(filebuffer) rows := strings.Split(inputdata, "\n") for _, row := range rows { fmt.Println("row:", row) } } ================================================ FILE: 06-io/02-file-directories/README.md ================================================ # Perform operations on files and directories Things you likely want to do to files and directories are moving them about, removing them, rename them etc. In short, high-level operations that is less about the content of the file but doing something with it. ## Introduction In this chapter you will: - Analyze a file for its metadata like size, modified date and more. - Copy files from one location to the next. - Rename files. - Remove files. - Create and read directories. ## File information You might want to look at a specific file and find out various details about it. Things that can be interesting are: - **Name**, maybe youu started from a path looking at a list of file. Getting the filename can be valuable. - **Size**. Getting the size of disc can be good to know. - **Permission**. To know what permissions you have tells you want you are able to do with the file, like running it, writing to it and so on. - **Last modified**. You might have a query looking for newly updated files only. Inspecting the modified date is what you want. - **Is a directory**. Files and directories are ultimately just files. There's a flag distinguishing a file from a directory. To get file information, use the `Stat()` function like so: ```go fileStat, err := os.Stat(path) fmt.Println("File Name:", fileStat.Name()) // Base name of the file fmt.Println("Size:", fileStat.Size()) // Length in bytes for regular files fmt.Println("Permissions:", fileStat.Mode()) // File mode bits fmt.Println("Last Modified:", fileStat.ModTime()) // Last modification time fmt.Println("Is Directory: ", fileStat.IsDir()) // Abbreviation for Mode().IsDir() ``` Also when you call `ReadDir()` you get back an array of `FileInfo` objects: ```go files, err := ioutil.ReadDir(path) ``` ## Copy file Copying a file is really three operations: - **open** the file at the destination. - **create** a new file at a given destination. - **copy**, then transfer the content to that location. Here's how you can implement a *copy* operation: ```go // copies 'test.txt' and its content to 'copy.txt' src := "test.txt" dest := "copy.txt" srcFile, err := os.Open(src) if err != nil { log.Fatal(err) } defer srcFile.Close() newFile, err := os.Create(dest) if err != nil { log.Fatal(err) } defer newFile.Close() _, err = io.Copy(newFile, srcFile) if err != nil { log.Fatal(err) } ``` ## Rename Rename is a bit easier to achieve. The `os` package has a `Rename()` function. Here's how to use it: ```go err := os.Rename(src, dest) ``` ## Remove file To remove file, there's a `Remove()` function you can use. Here's how to use it: ```go err := os.Remove(path) ``` ## Create dir To create a directory, you can use the `MkdirAll()` function in the `os` library. However, you should check whether the directory exist first. The way to do that is to use the `IsNotExist()` function like so: ```go _, err := os.Stat(dirName) if os.IsNotExist(err) { errDir := os.MkdirAll(dirName, 0755) if errDir != nil { log.Fatal(err) } } else if err != nil { log.Fatal("error creating dir") } else { log.Fatal("directory exist") } ``` As you can see on the above code: 1. you first use the `Stat()` function. The `Stat()` returns a `FileInfo` object or an error of type `PathError` if path doesn't exist. 1. `os.IsNotExist(err)`. This returns `true` if `err` is a `PathError`, i.e the path don't exist, which is good, we want to create it. 1. Finally, we call `os.MkdirAll(dirName, 0755)`. The 755 instruction is about permissions on the created directory, which gives the permissions, Read/Write/Execute, Read/Execute, Read/Execute. 755 is a common permission set on web servers. You essentially want to avoid anyone but you to modify the file. ## Read dir Reading a directory is quite straight forward. You can use the `ReadDir()` function on the `io/ioutil` library. Here's how you would read a directory: ```go files, err := ioutil.ReadDir(path) ``` `files` is an array of type `FileInfo`. TODO, copy, rename, remove, check for existence ## Assignment Create the following files and directories like so: ```text tmp/ a.txt b.xt subdir/ ``` Your program should read the diretory *tmp* and for each entry list, if it's a dir or a file, the size and when last modified. The programs output should look something like: ```output Reading directory tmp: Name, Type, Size, Modified a.txt, file, 1kb, 2022-01-01 b.txt, file, 1kb, 2022-01-01 subdir, directory, 1kb, 2022-01-01 ``` ## Solution ```go package main import ( "fmt" "io/ioutil" "log" ) func GetType(isDir bool) string { if isDir { return "directory" } return "file" } func main() { var path string = "tmp" files, err := ioutil.ReadDir(path) if err != nil { log.Fatal(err) } fmt.Println("Reading directory ", path) fmt.Println("Name, Type, Size, Modified") for _, file := range files { if err != nil { log.Fatal(err) } fmt.Printf("File Name: %s, ", file.Name()) fmt.Printf("Type: %s, ", GetType(file.IsDir())) fmt.Printf("Size: %d, ", file.Size()) fmt.Printf("Last Modified: %s, ", file.ModTime()) fmt.Print("\n") } } ``` ================================================ FILE: 06-io/02-file-directories/main.go ================================================ package main import ( "fmt" "io/ioutil" "log" ) func GetType(isDir bool) string { if isDir { return "directory" } return "file" } func main() { var path string = "tmp" files, err := ioutil.ReadDir(path) if err != nil { log.Fatal(err) } fmt.Println("Reading directory ", path) fmt.Println("Name, Type, Size, Modified") for _, file := range files { if err != nil { log.Fatal(err) } fmt.Printf("File Name: %s, ", file.Name()) fmt.Printf("Type: %s, ", GetType(file.IsDir())) fmt.Printf("Size: %d, ", file.Size()) // Length in bytes for regular files fmt.Printf("Last Modified: %s, ", file.ModTime()) // Last modification time fmt.Print("\n") } } ================================================ FILE: 06-io/02-file-directories/tmp/a.txt ================================================ ================================================ FILE: 06-io/02-file-directories/tmp/b.txt ================================================ ================================================ FILE: 06-io/03-compress-files/README.md ================================================ ## Compress files and directories Coming soon ## Assignment ## Solution ================================================ FILE: 06-io/fix/README.md ================================================ ## Links ================================================ FILE: 06-io/fix/dir/dir.go ================================================ package dir import ( "errors" "fmt" "io/fs" "io/ioutil" "os" ) var ErrDirExist = errors.New("Dir exist error") /* DESCRIPTION Reads the content on a directory and returns `FileInfo` array and a possible error, if dir can't be read. PARAMS path:string, a string representing the path holding a directory EXAMPLE files, err := dir.ReadDir(".") if err != nil { log.Fatal(err) } for _, file := range files { fmt.Println(file.Name()) } */ func ReadDir(path string) ([]fs.FileInfo, error) { files, err := ioutil.ReadDir(path) if err != nil { return nil, err } return files, nil } func CreateDir(dirName string) error { _, err := os.Stat(dirName) if os.IsNotExist(err) { errDir := os.MkdirAll(dirName, 0755) if errDir != nil { return err } return nil } else if err != nil { fmt.Println("error creating dir") return err // other type of error } else { return ErrDirExist } } ================================================ FILE: 06-io/fix/file/file.go ================================================ package file import ( "fmt" "io" "io/fs" "io/ioutil" "os" ) func OpenText(path string) (string, error) { filebuffer, err := ioutil.ReadFile(path) if err != nil { return "", err } var inputdata string = string(filebuffer) return inputdata, nil } func GetFileInfo(path string) (fs.FileInfo, error) { fileStat, err := os.Stat(path) return fileStat, err } func Append(path string, content string) error { f, err := os.OpenFile(path, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0660) if err != nil { return err } defer f.Close() fmt.Fprintf(f, "%s\n", content) return nil } func WriteText(path string, content string) error { f, err := os.Create(path) if err != nil { return err } n, err := f.WriteString(content + "\n") if err != nil { return err } fmt.Printf("wrote %d bytes\n", n) f.Sync() return nil } func CopyFile(src string, dest string) error { srcFile, err := os.Open(src) if err != nil { return err } defer srcFile.Close() newFile, err := os.Create(dest) if err != nil { return err } defer newFile.Close() _, err = io.Copy(newFile, srcFile) if err != nil { return err } return nil } func RenameFile(src string, dest string) error { err := os.Rename(src, dest) return err } func RemoveFile(path string) error { err := os.Remove(path) return err } ================================================ FILE: 06-io/fix/go.mod ================================================ module iohelper go 1.16 require golang.org/x/tools v0.1.9 // indirect ================================================ FILE: 06-io/fix/go.sum ================================================ github.com/yuin/goldmark v1.4.1 h1:/vn0k+RBvwlxEmP5E7SZMqNxPhfMVFEJiykr15/0XKM= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f h1:OfiFi4JbukWwe3lzw+xunroH1mnC1e2Gy5cxNJApiSY= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.9 h1:j9KsMiaP1c3B0OTQGth0/k+miLGTgLsAFUCrF2vLcF8= golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= ================================================ FILE: 06-io/fix/main.go ================================================ package main import ( "encoding/json" "fmt" "io/ioutil" "iohelper/dir" "iohelper/file" "log" ) type Products struct { Products []Product `json: products` } type Product struct { Id int `json: "id"` Name string `json: "name"` } // writes JSON to file // TODO // reads JSON from file func OpenJson() { file, _ := ioutil.ReadFile("products.json") data := Products{} _ = json.Unmarshal([]byte(file), &data) for i := 0; i < len(data.Products); i++ { fmt.Println("Product Id: ", data.Products[i].Id) fmt.Println("Name: ", data.Products[i].Name) } } func main() { // open file content, err := file.OpenText("test.txt") if err != nil { log.Fatal(err) } else { log.Println("file content", content) } err = file.WriteText("test2.txt", "here's some content") if err != nil { log.Fatal(err) } err = file.Append("test.txt", "append this") if err != nil { log.Fatal(err) } files, err := dir.ReadDir(".") if err != nil { log.Fatal(err) } for _, file := range files { log.Println(file.Name()) } err = dir.CreateDir("tmp") if err != nil { log.Fatal(err) } fmt.Println("dir created") file.CopyFile("test.txt", "copy.txt") log.Println("file copied") err = file.RenameFile("test.txt", "renamed.txt") if err != nil { log.Fatal(err) } fileStat, err := file.GetFileInfo("renamed.txt") if err != nil { log.Fatal(err) } fmt.Println("File Name:", fileStat.Name()) // Base name of the file fmt.Println("Size:", fileStat.Size()) // Length in bytes for regular files fmt.Println("Permissions:", fileStat.Mode()) // File mode bits fmt.Println("Last Modified:", fileStat.ModTime()) // Last modification time fmt.Println("Is Directory: ", fileStat.IsDir()) // Abbreviation for Mode().IsDir() err = file.RenameFile("copy.txt", "test.txt") if err != nil { log.Fatal(err) } log.Println("file renamed") err = file.RemoveFile("renamed.txt") if err != nil { log.Fatal(err) } log.Println("file removed") OpenJson() /* CHECK - Append text to file Create empty file CHECK - Read dir CHECK Create dir CHECK Copy file Move file CHECK Get file metadata CHECK Delete fil */ } ================================================ FILE: 06-io/fix/products.json ================================================ { "products": [{ "id": 1, "name": "test" }, { "id": 2, "name": "test2" }] } ================================================ FILE: 06-io/fix/test.txt ================================================ here's a file and the second row append this append this append this append this append this append this append this append this append this append this append this append this append this append this append this ================================================ FILE: 06-io/fix/test2.txt ================================================ here's some content ================================================ FILE: CONTRIBUTING.md ================================================ This project welcomes contributions and suggestions. Any feature suggestion, bugs, tests. Please raise an issue at . ================================================ FILE: LICENSE ================================================ Copyright (c) 2022 Chris Noring Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Go from the beginning Welcome to Go from the beginning, a free book containing 25+ lessons that will take you from "zero to hero" in the amazing language Go. ## Read online > ## Read offline/PDF/EPUB > FREE on LeanPub ## Donate Please consider donating to the people of Ukraine. - **Back and Alive** (they are buying what the army requests and do public reports) - **NBU** (National Bank of Ukraine) accounts for the army support - **UNICEF**, ## Table of content | Chapter | Title | Section | What you will learn | Lesson |--|--|--|--|--| | 01 | Hello world | Basics | Why use Go and how to write your first program | [Lesson](/01-basics/01-hello/README.md) | | 02 | Using variables | Basics | How to declare variables with different data types and how to initialize and assign values | [Lesson](/01-basics/02-variables/README.md) | | 03 | Boolean logic with If and Else | Basics | How to work with boolean variables and create different execution paths with If, Else and Else If | [Lesson](/01-basics/03-if-and-else/README.md) | | 04 | Converting between strings and numbers | Basics | How to use the `strconv` library to convert between primitives and strings. | [Lesson](/01-basics/04-conversions/README.md) | | 05 | Loop statements | Basics | How to repeat statements and iterating over list structures and ways to control the loops | [Lesson](/01-basics/05-loops/README.md) | | 06 | User input | Basics | How to read user input from the console | [Lesson](/01-basics/06-user-input/README.md) | | 07 | Functions | Basics | Reuse your code by creating functions. Learn how to deal with parameters and how to deal with returns and return types | [Lesson](/01-basics/07-functions/README.md) | | 08 | Error handling | Basics | How manage errors in your code. This will teach both how to produce errors as well as deal with them | [Lesson](/01-basics/08-error-handling/README.md) | | 09 | Arrays | Composite data types | Here we will learn about arrays, how to construct them, access items and iterate over them | [Lesson](/02-data-types/01-arrays/README.md) | | 10 | Structs | Composite data types | Structs enables us to collect many fields in one grouping, learn how to create and modify structs | [Lesson](/02-data-types/02-structs%20/README.md) | | 11 | Maps | Composite data types | Maps make it easy to lookup items if you know the key. Maps have the notion of keys and values. | [Lesson](/02-data-types/03-maps/README.md) | | 12 | Interfaces | Composite data types | Learn how you can model your data as interfaces and how to implement them | [Lesson](/02-data-types/04-interfaces/README.md) | | 13 | Create your first project | Projects | Learn how to create your first project | [Lesson](/03-projects/01-first-project/README.md) | | 14 | Consume external packages | Projects | Learn how to use external packages | [Lesson](/03-projects/02-consume-external/README.md) | | 15 | Create shared module | Projects | Create a module you can share with others | [Lesson](/03-projects/03-create-shared-module/README.md) | | 16 | Testing | Testing | Learn to test your code | [Lesson](/03-projects/04-testing/README.md) | | 17 | JSON | Web Dev | Learn to work with the JSON format | [Lesson](/04-webdev/01-json/README.md) | | 18 | Build a Web App | Web Dev | Learn how to build a web app capable of serving many different formats | [Lesson](/04-webdev/02-web-dev/README.md) | | 19 | Logs | Miscellaneous | Use logging for better management of all kinds of messages in your app | [Lesson](/05-misc/01-logs/README.md) | | 20 | Strings | Miscellaneous | Work with the string library | [Lesson](/05-misc/02-strings/README.md) | | 21 | Regex | Miscellaneous | Work with regular epressions | [Lesson](/05-misc/03-regex/README.md) | | 22 | Goroutines | Miscellaneous | Work with goroutines and channels | [Lesson](/05-misc/04-goroutines/README.md) | | 23 | Database with Sqlite | Miscellaneous | Work with databases | [Lesson](/05-misc/05-sqlite/README.md) | | 24 | Read and write to files | IO | Learn to read from and write to files | [Lesson](/06-io/01-read-write-files/README.md) | | 25 | Files and directories | IO | Learn to perform operations on files and directories | [Lesson](/06-io/02-file-directories/README.md) | ## How to use this content Every chapter consist of a lesson and an exercise. You are encouraged to run the code in the exercise, modify it and understand how it works. ## Contributions Contributions are very welcome. Please raise an issue of you see something or a PR. I welcome contributions on: - Suggestions on topics to cover - Correctness issues - Spelling - Suggestions on better formatting - I hope I hear from you. :) ================================================ FILE: _config.yml ================================================ theme: jekyll-theme-cayman ================================================ FILE: _layouts/default.html ================================================           {{ page.title }}                 {{ content }}   ================================================ FILE: _sidebar.md ================================================ # Lessons | Chapter | Lesson |--|--| | 01 | [Hello world](/01-basics/01-hello/README.md) | | 02 | [Using variables](/01-basics/02-variables/README.md) | | 03 | [Boolean logic with If and Else ](/01-basics/03-if-and-else/README.md) | | 04 | [Converting between strings and numbers](/01-basics/04-conversions/README.md) | | 05 | [Loop statements](/01-basics/05-loops/README.md) | | 06 | [User input](/01-basics/06-user-input/README.md) | | 07 | [Functions](/01-basics/07-functions/README.md) | | 08 | [Error handling](/01-basics/08-error-handling/README.md) | | 09 | [Arrays](/02-data-types/01-arrays/README.md) | | 10 | [Structs](/02-data-types/02-structs%20/README.md) | | 11 | [Maps](/02-data-types/03-maps/README.md) | | 12 | [Interfaces](/02-data-types/04-interfaces/README.md) | | 13 | [Create your first project](/03-projects/01-first-project/README.md) | | 14 | [Consume external packages](/03-projects/02-consume-external/README.md) | | 15 | [Create shared module](/03-projects/03-create-shared-module/README.md) | | 16 | [Testing](/03-projects/04-testing/README.md) | | 17 | [JSON](/04-webdev/01-json/README.md) | | 18 | [Build a Web App](/04-webdev/02-web-dev/README.md) | | 19 | [Logs](/05-misc/01-logs/README.md) | | 20 | [Strings](/05-misc/02-strings/README.md) | | 21 | [Regex](/05-misc/03-regex/README.md) | | 22 | [Goroutines](/05-misc/04-goroutines/README.md) | | 23 | [Database with Sqlite](/05-misc/05-sqlite/README.md) | | 24 | [Read and write to files](/06-io/01-read-write-files/README.md) | | 25 | [Files and directories](/06-io/02-file-directories/README.md) | ================================================ FILE: index.html ================================================ Golang book