├── README.md ├── assets └── learn-x-in-y-minutes.md ├── cmd ├── vk-friends │ └── friends.go └── vk-storage │ └── storage.go └── draw_gopher ├── body.png ├── compose.go ├── ears.png ├── eyes.png ├── hands.png ├── invert.go ├── nose.png ├── png2jpg.go ├── resize.go ├── teeth.png └── undernose.png /README.md: -------------------------------------------------------------------------------- 1 | # Hello, Go! 2 | 3 | ## Способы изучать Go 4 | 5 | * Если нравится формат FAQ, читайте дальше и пропустите эту секцию. 6 | * Если любите решать задачки на [leetcode](https://leetcode.com/problemset/all/?difficulty=Easy), [Codewars](https://www.codewars.com/), [CodinGame](https://www.codingame.com/start) или [HackerRank](https://www.hackerrank.com/), то можете попробовать решать их на Go (они поддерживают решения на этом языке). 7 | * Если любите смотреть уже готовые примеры решений и сравнивать их с решениями 8 | на знакомых вам языках программирования, загляните в [rosettacode](http://www.rosettacode.org/wiki/Category:Go). 9 | Аналогичным ресурсом является [Go by example](https://gobyexample.com/). 10 | * Для rosettacode есть сайт с side-by-side сравнением решений на двух языках. 11 | Вот, например, страница для [Go<->Python](https://rosetta.alhur.es/compare/Go/Python/). 12 | * Для уверенных в себе есть [learnxinyminutes](https://learnxinyminutes.com/docs/ru-ru/go-ru/). Качество подачи не слишком высокое, но это один из самых быстрых способов изучить самые базовые возможности языка. 13 | * Если есть опыт с другими языками программирования, можно попробовать [go tour](https://tour.golang.org/welcome/1). 14 | 15 | ## Как установить Go? 16 | 17 | Скачать нужный дистрибутив по ссылке: https://golang.org/dl/. 18 | Есть версии под Windows, Linux и macOS. 19 | 20 | Качать последнюю стабильную версию (1.13.3). 21 | 22 | ## Какой текстовой редактор использовать для Go? 23 | 24 | - Visual studio code + [плагин для Go](https://marketplace.visualstudio.com/items?itemName=golang.go) 25 | - Если знакомы продукты JetBrains Intellij, то Goland IDE 26 | - Если всё выше звучит непонятно, то используйте https://play.golang.org/ 27 | 28 | ## Как проверить, что Go установлен? 29 | 30 | Набрать в терминале `go version`. 31 | Если не работает, то нужно добавить путь к папке с 32 | исполняемым файлом `go` в переменную окружения `PATH`. 33 | 34 | ## Как запускать программы на Go? 35 | 36 | Создайте файл `hello.go` следующего содержания: 37 | 38 | ```go 39 | package main 40 | 41 | import "fmt" 42 | 43 | func main() { 44 | fmt.Println("Hello, World!") 45 | } 46 | ``` 47 | 48 | Для запуска нужно сначала скомпилировать программу, а затем её запустить: 49 | 50 | ```bash 51 | $ go build -o hello.exe hello.go 52 | $ ./hello.exe 53 | Hello, World! 54 | ``` 55 | 56 | Но для таких простых случаев есть команда `run`, выполняющая эти два шага за вас: 57 | 58 | ```bash 59 | $ go run hello.go 60 | Hello, World! 61 | ``` 62 | 63 | ## Что такое GOPATH? Как узнать его значение? 64 | 65 | `GOPATH` указывает на директорию, куда будут устанавливаться пакеты 66 | и в которой будут искаться импортируемые (подключаемые) пакеты. 67 | 68 | Если переменной окружения `GOPATH` нет, Go всё равно будет 69 | иметь некоторое значение по умолчанию. Узнать текущее 70 | значение `GOPATH` проще всего командой: 71 | 72 | ```bash 73 | go env GOPATH 74 | ``` 75 | 76 | На системах типа Linux директория по умолчанию `~/go`. 77 | 78 | > Внимание: после Go 1.13 важность GOPATH понизилась. Теперь нужно работать с [модулями](https://github.com/golang/go/wiki/Modules). 79 | 80 | ## Что такое пакет? 81 | 82 | Пакет - это набор файлов, который образует логическую группу. 83 | Можно называть пакет словом "библиотека" (хотя библиотека может состоять из 84 | нескольких пакетов). 85 | 86 | ## Что такое модуль? 87 | 88 | Модуль может содержать в себе один или более пакетов, он же ассоциирует с ними версию. 89 | 90 | Например, пакет `foo` может быть частью модуля `github.com/someuser/foo` с версией `v0.5.0`. 91 | 92 | Для версий используется подход семантического версионирования. 93 | 94 | ## Где найти документацию по стандартной библиотеке Go? 95 | 96 | Документация по пакетам: https://golang.org/pkg/. 97 | 98 | Можно установить godoc и смотреть документацию оффлайн: 99 | 100 | ```bash 101 | go get -v golang.org/x/tools/cmd/godoc 102 | ``` 103 | 104 | Теперь можно запустить godoc: 105 | 106 | ```bash 107 | godoc -http=:8080 108 | ``` 109 | 110 | Если открыть в браузере адрес , то вы 111 | увидите ту же документацию, что была доступна онлайн. 112 | 113 | Сайт [godoc.org](https://godoc.org/) можно использовать для поиска Go пакетов и/или их документации. 114 | 115 | > Внимание: вместо `godoc.org` теперь стоит использовать [pkg.go.dev](https://pkg.go.dev/). 116 | 117 | ## Какие ещё есть полезные ресурсы? 118 | 119 | Большинство ссылок легко найти в гугле по запросу "golang learning resources".
120 | Самое главное правило - всегда искать по слову `golang`, а не `go`.
121 | 122 | Ниже наиболее стоящие результаты с описаниями: 123 | 124 | * [С чего начать новичку в Go](http://dev.tulu.la/post/go-newbies/) 125 | * [Golang book (перевод на русский)](http://golang-book.ru) 126 | * [Resources for new Go programmers](https://dave.cheney.net/resources-for-new-go-programmers) - статья [Dave Cheney](https://dave.cheney.net/about), одного из ведущих разработчиков Go. 127 | * [golang/go/wiki/Learn](https://github.com/golang/go/wiki/Learn) - много учебного материала. 128 | * [Go videos](https://github.com/hH39797J/golang-videos-ru) - собрание видеозаписей докладов про Go. 129 | * [Go webdev examples](https://gowebexamples.com/) - аналог Go by example, но с уклоном в веб разработку. 130 | 131 | ## Книги по Go 132 | 133 | Многие книги имеют переводы на русский язык. 134 | 135 | * [Get programming with Go](https://www.manning.com/books/get-programming-with-go) - хорошая книга если Go один из первых ваших языков программирования. 136 | * [The Go Programming Language](http://www.gopl.io/) - очень известная книга, довольно хороша, но подойдёт только тем, кто уже более-менее комфортно программирует на одном или более языках программирования. 137 | * [Go in practice](https://www.manning.com/books/go-in-practice) - книга, которая может дополнить книги, перечисленные выше. 138 | 139 | ## Что такое "сообщество Go"? 140 | 141 | [GolangShow](http://golangshow.com) - русскоязычный подкаст о Go. Крутые ведущие, интересные гости. 142 | 143 | Сообщество стоит понимать как "группа людей со схожими интересами и/или целями". 144 | 145 | [golang-ru Slack](http://slack.golang-ru.com) - русскоязычное Go сообщество. 146 | Там можно задавать вопросы, обсуждать Go, библиотеки под него и прочее. 147 | 148 | Для вопросов лучше всего подходит канал `#school` (при формулировке вопроса можно 149 | опираться на [How To Ask Questions The Smart Way](http://www.catb.org/esr/faqs/smart-questions.html)). 150 | 151 | Всем участникам следует соблюдать [кодекс норм поведения](https://golang.org/conduct). 152 | 153 | Для Казани есть группа [GolangKazan](https://vk.com/golangkazan). 154 | -------------------------------------------------------------------------------- /assets/learn-x-in-y-minutes.md: -------------------------------------------------------------------------------- 1 | --- 2 | language: Go 3 | filename: learngo-ru.go 4 | contributors: 5 | - ["Sonia Keys", "https://github.com/soniakeys"] 6 | - ["Christopher Bess", "https://github.com/cbess"] 7 | - ["Jesse Johnson", "https://github.com/holocronweaver"] 8 | - ["Quint Guvernator", "https://github.com/qguv"] 9 | translators: 10 | - ["Artem Medeusheyev", "https://github.com/armed"] 11 | - ["Valery Cherepanov", "https://github.com/qumeric"] 12 | lang: ru-ru 13 | --- 14 | 15 | Go - это язык общего назначения, целью которого является удобство, простота, 16 | конкурентность. Это не тренд в компьютерных науках, а новейший и быстрый 17 | способ решать насущные проблемы. 18 | 19 | Концепции Go схожи с другими императивными статически типизированными языками. 20 | Быстро компилируется и быстро исполняется, имеет лёгкие в понимании конструкции 21 | для создания масштабируемых и многопоточных программ. 22 | 23 | Может похвастаться отличной стандартной библиотекой и большим комьюнити, полным 24 | энтузиастов. 25 | 26 | ```go 27 | // Однострочный комментарий 28 | /* Многострочный 29 | комментарий */ 30 | 31 | // Ключевое слово package присутствует в начале каждого файла. 32 | // Main это специальное имя, обозначающее исполняемый файл, нежели библиотеку. 33 | package main 34 | 35 | // Import предназначен для указания зависимостей этого файла. 36 | import ( 37 | "fmt" // Пакет в стандартной библиотеке Go 38 | "io/ioutil" // Реализация функций ввод/ввывода. 39 | "net/http" // Да, это веб-сервер! 40 | "strconv" // Конвертирование типов в строки и обратно 41 | m "math" // Импортировать math под локальным именем m. 42 | ) 43 | 44 | // Объявление функции. Main это специальная функция, служащая точкой входа для 45 | // исполняемой программы. Нравится вам или нет, но Go использует фигурные 46 | // скобки. 47 | func main() { 48 | // Println выводит строку в stdout. 49 | // Данная функция находится в пакете fmt. 50 | fmt.Println("Hello world!") 51 | 52 | // Вызов другой функции из текущего пакета. 53 | beyondHello() 54 | } 55 | 56 | // Функции содержат входные параметры в круглых скобках. 57 | // Пустые скобки все равно обязательны, даже если параметров нет. 58 | func beyondHello() { 59 | var x int // Переменные должны быть объявлены до их использования. 60 | x = 3 // Присвоение значения переменной. 61 | // Краткое определение := позволяет объявить переменную с автоматической 62 | // подстановкой типа из значения. 63 | y := 4 64 | sum, prod := learnMultiple(x, y) // Функция возвращает два значения. 65 | fmt.Println("sum:", sum, "prod:", prod) // Простой вывод. 66 | learnTypes() // < y minutes, learn more! 67 | } 68 | 69 | // Функция, имеющая входные параметры и возвращающая несколько значений. 70 | func learnMultiple(x, y int) (sum, prod int) { 71 | return x + y, x * y // Возврат двух значений. 72 | } 73 | 74 | // Некоторые встроенные типы и литералы. 75 | func learnTypes() { 76 | // Краткое определение переменной говорит само за себя. 77 | s := "Learn Go!" // Тип string. 78 | 79 | s2 := `"Чистый" строковой литерал 80 | может содержать переносы строк` // Тоже тип данных string 81 | 82 | // Символ не из ASCII. Исходный код Go в кодировке UTF-8. 83 | g := 'Σ' // тип rune, это алиас для типа int32, содержит символ юникода. 84 | 85 | f := 3.14195 // float64, 64-х битное число с плавающей точкой (IEEE-754). 86 | c := 3 + 4i // complex128, внутри себя содержит два float64. 87 | 88 | // Синтаксис var с инициализациями. 89 | var u uint = 7 // Беззнаковое, но размер зависит от реализации, как и у int. 90 | var pi float32 = 22. / 7 91 | 92 | // Синтаксис приведения типа с кратким определением 93 | n := byte('\n') // byte – это алиас для uint8. 94 | 95 | // Массивы имеют фиксированный размер на момент компиляции. 96 | var a4 [4]int // массив из 4-х int, инициализирован нулями. 97 | a3 := [...]int{3, 1, 5} // массив из 3-х int, ручная инициализация. 98 | 99 | // Слайсы (slices) имеют динамическую длину. И массивы, и слайсы имеют свои 100 | // преимущества, но слайсы используются гораздо чаще. 101 | s3 := []int{4, 5, 9} // Сравните с a3, тут нет троеточия. 102 | s4 := make([]int, 4) // Выделение памяти для слайса из 4-х int (нули). 103 | var d2 [][]float64 // Только объявление, память не выделяется. 104 | bs := []byte("a slice") // Синтаксис приведения типов. 105 | 106 | p, q := learnMemory() // Объявление p и q как указателей на int. 107 | fmt.Println(*p, *q) // * извлекает указатель. Печатает два int-а. 108 | 109 | // Map, также как и словарь или хеш из некоторых других языков, является 110 | // ассоциативным массивом с динамически изменяемым размером. 111 | m := map[string]int{"three": 3, "four": 4} 112 | m["one"] = 1 113 | 114 | delete(m, "three") // Встроенная функция, удаляет элемент из map-а. 115 | 116 | // Неиспользуемые переменные в Go являются ошибкой. 117 | // Нижнее подчёркивание позволяет игнорировать такие переменные. 118 | _, _, _, _, _, _, _, _, _ = s2, g, f, u, pi, n, a3, s4, bs 119 | // Вывод считается использованием переменной. 120 | fmt.Println(s, c, a4, s3, d2, m) 121 | 122 | learnFlowControl() // Идем дальше. 123 | } 124 | 125 | // У Go есть полноценный сборщик мусора. В нем есть указатели, но нет арифметики 126 | // указателей. Вы можете допустить ошибку с указателем на nil, но не с 127 | // инкрементацией указателя. 128 | func learnMemory() (p, q *int) { 129 | // Именованные возвращаемые значения p и q являются указателями на int. 130 | p = new(int) // Встроенная функция new выделяет память. 131 | // Выделенный int проинициализирован нулём, p больше не содержит nil. 132 | s := make([]int, 20) // Выделение единого блока памяти под 20 int-ов. 133 | s[3] = 7 // Присвоить значение одному из них. 134 | r := -2 // Определить ещё одну локальную переменную. 135 | return &s[3], &r // Амперсанд(&) обозначает получение адреса переменной. 136 | } 137 | 138 | func expensiveComputation() float64 { 139 | return m.Exp(10) 140 | } 141 | 142 | func learnFlowControl() { 143 | // If-ы всегда требуют наличие фигурных скобок, но не круглых. 144 | if true { 145 | fmt.Println("told ya") 146 | } 147 | // Форматирование кода стандартизировано утилитой "go fmt". 148 | if false { 149 | // Будущего нет. 150 | } else { 151 | // Жизнь прекрасна. 152 | } 153 | // Используйте switch вместо нескольких if-else. 154 | x := 42.0 155 | switch x { 156 | case 0: 157 | case 1: 158 | case 42: 159 | // Case-ы в Go не "проваливаются" (неявный break). 160 | case 43: 161 | // Не выполнится. 162 | } 163 | // For, как и if не требует круглых скобок 164 | // Переменные, объявленные в for и if являются локальными. 165 | for x := 0; x < 3; x++ { // ++ – это операция. 166 | fmt.Println("итерация", x) 167 | } 168 | // Здесь x == 42. 169 | 170 | // For – это единственный цикл в Go, но у него есть альтернативные формы. 171 | for { // Бесконечный цикл. 172 | break // Не такой уж и бесконечный. 173 | continue // Не выполнится. 174 | } 175 | // Как и в for, := в if-е означает объявление и присвоение значения y, 176 | // проверка y > x происходит после. 177 | if y := expensiveComputation(); y > x { 178 | x = y 179 | } 180 | // Функции являются замыканиями. 181 | xBig := func() bool { 182 | return x > 10000 // Ссылается на x, объявленный выше switch. 183 | } 184 | fmt.Println("xBig:", xBig()) // true (т.к. мы присвоили x = e^10). 185 | x = 1.3e3 // Тут х == 1300 186 | fmt.Println("xBig:", xBig()) // Теперь false. 187 | 188 | // Метки, куда же без них, их все любят. 189 | goto love 190 | love: 191 | 192 | learnDefer() // Быстрый обзор важного ключевого слова. 193 | learnInterfaces() // О! Интерфейсы, идём далее. 194 | } 195 | 196 | func learnDefer() (ok bool) { 197 | // Отложенные(deferred) выражения выполняются сразу перед тем, как функция 198 | // возвратит значение. 199 | defer fmt.Println("deferred statements execute in reverse (LIFO) order.") 200 | defer fmt.Println("\nThis line is being printed first because") 201 | // defer широко используется для закрытия файлов, чтобы закрывающая файл 202 | // функция находилась близко к открывающей. 203 | return true 204 | } 205 | 206 | // Объявление Stringer как интерфейса с одним методом, String. 207 | type Stringer interface { 208 | String() string 209 | } 210 | 211 | // Объявление pair как структуры с двумя полями x и y типа int. 212 | type pair struct { 213 | x, y int 214 | } 215 | 216 | // Объявление метода для типа pair. Теперь pair реализует интерфейс Stringer. 217 | func (p pair) String() string { // p в данном случае называют receiver-ом. 218 | // Sprintf – ещё одна функция из пакета fmt. 219 | // Обращение к полям p через точку. 220 | return fmt.Sprintf("(%d, %d)", p.x, p.y) 221 | } 222 | 223 | func learnInterfaces() { 224 | // Синтаксис с фигурными скобками это "литерал структуры". Он возвращает 225 | // проинициализированную структуру, а оператор := присваивает её p. 226 | p := pair{3, 4} 227 | fmt.Println(p.String()) // Вызов метода String у переменной p типа pair. 228 | var i Stringer // Объявление i как типа с интерфейсом Stringer. 229 | i = p // Валидно, т.к. pair реализует Stringer. 230 | // Вызов метода String у i типа Stringer. Вывод такой же, что и выше. 231 | fmt.Println(i.String()) 232 | 233 | // Функции в пакете fmt сами всегда вызывают метод String у объектов для 234 | // получения строкового представления о них. 235 | fmt.Println(p) // Вывод такой же, что и выше. Println вызывает метод String. 236 | fmt.Println(i) // Вывод такой же, что и выше. 237 | 238 | learnVariadicParams("Учиться", "учиться", "и ещё раз учиться!") 239 | } 240 | 241 | // Функции могут иметь варьируемое количество параметров. 242 | func learnVariadicParams(myStrings ...interface{}) { 243 | // Вывести все параметры с помощью итерации. 244 | for _, param := range myStrings { 245 | fmt.Println("param:", param) 246 | } 247 | 248 | // Передать все варьируемые параметры. 249 | fmt.Println("params:", fmt.Sprintln(myStrings...)) 250 | 251 | learnErrorHandling() 252 | } 253 | 254 | func learnErrorHandling() { 255 | // Идиома ", ok" служит для обозначения корректного срабатывания чего-либо. 256 | m := map[int]string{3: "three", 4: "four"} 257 | if x, ok := m[1]; !ok { // ok будет false, потому что 1 нет в map-е. 258 | fmt.Println("тут никого нет") 259 | } else { 260 | fmt.Print(x) // x содержал бы значение, если бы 1 был в map-е. 261 | } 262 | // Идиома ", err" служит для обозначения была ли ошибка или нет. 263 | if _, err := strconv.Atoi("non-int"); err != nil { // _ игнорирует значение 264 | // выведет "strconv.ParseInt: parsing "non-int": invalid syntax" 265 | fmt.Println(err) 266 | } 267 | // Мы ещё обратимся к интерфейсам чуть позже, а пока... 268 | learnConcurrency() 269 | } 270 | 271 | // c – это тип данных channel (канал), объект для конкурентного взаимодействия. 272 | func inc(i int, c chan int) { 273 | c <- i + 1 // когда channel слева, <- являтся оператором "отправки". 274 | } 275 | 276 | // Будем использовать функцию inc для конкурентной инкрементации чисел. 277 | func learnConcurrency() { 278 | // Тот же make, что и в случае со slice. Он предназначен для выделения 279 | // памяти и инициализации типов slice, map и channel. 280 | c := make(chan int) 281 | // Старт трех конкурентных goroutine. Числа будут инкрементированы 282 | // конкурентно и, может быть параллельно, если машина правильно 283 | // сконфигурирована и позволяет это делать. Все они будут отправлены в один 284 | // и тот же канал. 285 | go inc(0, c) // go начинает новую горутину. 286 | go inc(10, c) 287 | go inc(-805, c) 288 | // Считывание всех трех результатов из канала и вывод на экран. 289 | // Нет никакой гарантии в каком порядке они будут выведены. 290 | fmt.Println(<-c, <-c, <-c) // канал справа, <- обозначает "получение". 291 | 292 | cs := make(chan string) // другой канал, содержит строки. 293 | cc := make(chan chan string) // канал каналов со строками. 294 | go func() { c <- 84 }() // пуск новой горутины для отправки значения 295 | go func() { cs <- "wordy" }() // ещё раз, теперь для cs 296 | // Select тоже что и switch, но работает с каналами. Он случайно выбирает 297 | // готовый для взаимодействия канал. 298 | select { 299 | case i := <-c: // полученное значение можно присвоить переменной 300 | fmt.Printf("это %T", i) 301 | case <-cs: // либо значение можно игнорировать 302 | fmt.Println("это строка") 303 | case <-cc: // пустой канал, не готов для коммуникации. 304 | fmt.Println("это не выполнится.") 305 | } 306 | // В этой точке значение будет получено из c или cs. Одна горутина будет 307 | // завершена, другая останется заблокированной. 308 | 309 | learnWebProgramming() // Да, Go это может. 310 | } 311 | 312 | // Всего одна функция из пакета http запускает web-сервер. 313 | func learnWebProgramming() { 314 | // У ListenAndServe первый параметр это TCP адрес, который нужно слушать. 315 | // Второй параметр это интерфейс типа http.Handler. 316 | err := http.ListenAndServe(":8080", pair{}) 317 | fmt.Println(err) // не игнорируйте сообщения об ошибках 318 | } 319 | 320 | // Реализация интерфейса http.Handler для pair, только один метод ServeHTTP. 321 | func (p pair) ServeHTTP(w http.ResponseWriter, r *http.Request) { 322 | // Обработка запроса и отправка данных методом из http.ResponseWriter 323 | w.Write([]byte("You learned Go in Y minutes!")) 324 | } 325 | 326 | func requestServer() { 327 | resp, err := http.Get("http://localhost:8080") 328 | fmt.Println(err) 329 | defer resp.Body.Close() 330 | body, err := ioutil.ReadAll(resp.Body) 331 | fmt.Printf("\nWebserver said: `%s`", string(body)) 332 | } 333 | ``` 334 | 335 | ## Что дальше 336 | 337 | Основа всех основ в Go это [официальный веб сайт](http://golang.org/). 338 | Там можно пройти туториал, поиграться с интерактивной средой Go и почитать 339 | объёмную документацию. 340 | 341 | Для живого ознакомления рекомендуется почитать исходные коды [стандартной 342 | библиотеки Go](http://golang.org/src/pkg/). Отлично задокументированная, она 343 | является лучшим источником для чтения и понимания Go, его стиля и идиом. Либо 344 | можно, кликнув на имени функции в [документации](http://golang.org/pkg/), 345 | перейти к ее исходным кодам. 346 | -------------------------------------------------------------------------------- /cmd/vk-friends/friends.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "net/http" 10 | "net/url" 11 | "sort" 12 | "strings" 13 | ) 14 | 15 | // Утилита, демонстрирующая использование VK API без каких-либо 16 | // сторонних библиотек на примере friends методом. 17 | // 18 | // Пример использования: 19 | // $ vk-friends -token $TOKEN online 20 | // $ vk-friends -token $TOKEN list 21 | // 22 | // Для более простого примера смотри cmd/vk-storage. 23 | 24 | func main() { 25 | var args arguments 26 | ctxt := newContext(&args) 27 | 28 | // Все шаги программы, в последовательности выполнения. 29 | steps := []struct { 30 | name string 31 | fn func() error 32 | }{ 33 | {"parse args", args.parse}, 34 | {"validate args", ctxt.validateArgs}, 35 | {"exec command", ctxt.execCommand}, 36 | {"print stats", ctxt.printStats}, 37 | } 38 | 39 | for _, step := range steps { 40 | ctxt.debugf("start %q step", step.name) 41 | // Единственное место для обработки ошибок в main функции. 42 | if err := step.fn(); err != nil { 43 | log.Printf("%s: %v", step.name, err) 44 | return 45 | } 46 | } 47 | } 48 | 49 | // arguments - аргументы командной строки. 50 | type arguments struct { 51 | // Все поля описаны внутри метода parse. 52 | 53 | token string 54 | apiVersion string 55 | command string 56 | verbose bool 57 | } 58 | 59 | // parse связывает аргументы командной строки с объектом args. 60 | // Не производит детальной валидации. 61 | func (args *arguments) parse() error { 62 | flag.StringVar(&args.token, "token", "", 63 | `A token for VK API access_token parameter`) 64 | flag.StringVar(&args.apiVersion, "api", "5.95", 65 | `Which VK API version to use`) 66 | flag.BoolVar(&args.verbose, "verbose", false, 67 | `Whether to print debug information`) 68 | 69 | flag.Parse() 70 | 71 | if n := len(flag.Args()); n != 1 { 72 | return fmt.Errorf("expected exactly 1 positional argument, got %d", n) 73 | } 74 | 75 | args.command = flag.Args()[0] 76 | 77 | return nil 78 | } 79 | 80 | // context хранит состояние выполнения программы. 81 | // Для создания экземпляра следует использовать newContext. 82 | type context struct { 83 | args *arguments 84 | 85 | // commands хранит все зарегистрированные обработчики. 86 | // Заполняется в newContext. 87 | commands map[string]func() error 88 | 89 | // requests является счётчиком выполненного количества запросов к API. 90 | requests int 91 | } 92 | 93 | func newContext(args *arguments) *context { 94 | ctxt := &context{args: args} 95 | 96 | ctxt.commands = map[string]func() error{ 97 | "online": ctxt.onlineCommand, 98 | "list": ctxt.listCommand, 99 | } 100 | 101 | return ctxt 102 | } 103 | 104 | func (ctxt *context) apiURL(path string, params ...string) *url.URL { 105 | // Мы могли бы просто сформировать строку, но url.URL 106 | // можно использовать как простой билдер для URL'ов. 107 | u := url.URL{ 108 | Scheme: "https", 109 | Host: "api.vk.com", 110 | Path: path, 111 | } 112 | query := u.Query() 113 | query.Set("access_token", ctxt.args.token) 114 | query.Set("version", ctxt.args.apiVersion) 115 | for _, p := range params { 116 | kv := strings.Split(p, "=") 117 | query.Set(kv[0], kv[1]) 118 | } 119 | u.RawQuery = query.Encode() 120 | return &u 121 | } 122 | 123 | type vkResponse map[string]interface{} 124 | 125 | // debugf подобен log.Printf, но печатает только в verbose режиме. 126 | func (ctxt *context) debugf(format string, args ...interface{}) { 127 | if ctxt.args.verbose { 128 | log.Printf("debug: "+format, args...) 129 | } 130 | } 131 | 132 | func (ctxt *context) apiGet(path string, params ...string) (vkResponse, error) { 133 | ctxt.requests++ 134 | 135 | targetURL := ctxt.apiURL(path, params...).String() 136 | ctxt.debugf("GET %q", targetURL) 137 | rawResp, err := http.Get(targetURL) 138 | if err != nil { 139 | return nil, fmt.Errorf("get: %v", err) 140 | } 141 | defer rawResp.Body.Close() 142 | 143 | data, err := ioutil.ReadAll(rawResp.Body) 144 | if err != nil { 145 | return nil, fmt.Errorf("read resp: %v", err) 146 | } 147 | ctxt.debugf("API response: %s", data) 148 | 149 | var resp vkResponse 150 | if err := json.Unmarshal(data, &resp); err != nil { 151 | return nil, fmt.Errorf("json decode: %v", err) 152 | } 153 | 154 | if apiError := resp["error"]; apiError != nil { 155 | return resp, fmt.Errorf("api error: %v", apiError) 156 | } 157 | 158 | return resp, nil 159 | } 160 | 161 | func (ctxt *context) listCommand() error { 162 | resp, err := ctxt.apiGet("method/friends.get") 163 | if err != nil { 164 | return err 165 | } 166 | 167 | friends := resp["response"].([]interface{}) 168 | fmt.Printf("friends (%d):\n", len(friends)) 169 | return ctxt.printFriends(friends) 170 | } 171 | 172 | func (ctxt *context) onlineCommand() error { 173 | resp, err := ctxt.apiGet("method/friends.getOnline") 174 | if err != nil { 175 | return err 176 | } 177 | 178 | friends := resp["response"].([]interface{}) 179 | fmt.Printf("friends online (%d):\n", len(friends)) 180 | return ctxt.printFriends(friends) 181 | } 182 | 183 | func (ctxt *context) validateArgs() error { 184 | checkNonEmpty := []struct { 185 | name string 186 | value string 187 | }{ 188 | {"-token", ctxt.args.token}, 189 | {"-api", ctxt.args.apiVersion}, 190 | } 191 | 192 | for _, check := range checkNonEmpty { 193 | if check.value != "" { 194 | continue 195 | } 196 | return fmt.Errorf("%s argument can't be empty", check.name) 197 | } 198 | 199 | // Проверяем, что подкоманда определена. 200 | if _, ok := ctxt.commands[ctxt.args.command]; !ok { 201 | // Если пользователь использует неправильную команду, 202 | // соберём список доступных комманд и подскажем ему их. 203 | var hints []string 204 | for command := range ctxt.commands { 205 | hints = append(hints, command) 206 | } 207 | // Поскольку map в Go не отсортирован, после получения 208 | // ключей их следует отсортировать, иначе каждый раз 209 | // подсказки будут печататься в разном порядке. 210 | sort.Strings(hints) 211 | return fmt.Errorf("unrecognized command %q (supported commands: %s)", 212 | ctxt.args.command, strings.Join(hints, ", ")) 213 | } 214 | 215 | return nil 216 | } 217 | 218 | func (ctxt *context) execCommand() error { 219 | // Делегируем выполнение зарегистрированной команде. 220 | return ctxt.commands[ctxt.args.command]() 221 | } 222 | 223 | func (ctxt *context) printStats() error { 224 | log.Printf("made %d API requests", ctxt.requests) 225 | return nil 226 | } 227 | 228 | // printFriends печатает список друзей. 229 | func (ctxt *context) printFriends(friends []interface{}) error { 230 | var ids []string 231 | for _, id := range friends { 232 | ids = append(ids, fmt.Sprint(int(id.(float64)))) 233 | } 234 | 235 | idsParam := strings.Join(ids, ",") 236 | resp, err := ctxt.apiGet("method/users.get", "user_ids="+idsParam) 237 | if err != nil { 238 | return err 239 | } 240 | users := resp["response"].([]interface{}) 241 | 242 | for i, user := range users { 243 | user := user.(map[string]interface{}) 244 | fmt.Printf("\t%4d %s %s\n", i+1, user["first_name"], user["last_name"]) 245 | } 246 | 247 | return nil 248 | } 249 | -------------------------------------------------------------------------------- /cmd/vk-storage/storage.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "net/http" 10 | "net/url" 11 | "strings" 12 | ) 13 | 14 | // Простейший пример утилиты командной строки, которая использует 15 | // методы storage: get и set. 16 | // 17 | // Пример использования: 18 | // $ vk-storage -token $TOKEN set mykey 123 19 | // $ vk-storage -token $TOKEN get mykey 20 | // 21 | // Для более интересного примера смотри cmd/vk-friends. 22 | 23 | func main() { 24 | var args arguments 25 | 26 | if err := args.parse(); err != nil { 27 | log.Panicf("parse args error: %v", err) 28 | } 29 | 30 | switch args.command { 31 | case "get": 32 | storageGet(&args) 33 | case "set": 34 | storageSet(&args) 35 | default: 36 | log.Panicf("unknown storage method: %q", args.command) 37 | } 38 | } 39 | 40 | // arguments - аргументы командной строки. 41 | type arguments struct { 42 | // Все поля описаны внутри метода parse. 43 | 44 | token string 45 | apiVersion string 46 | command string 47 | commandArgs []string 48 | } 49 | 50 | // parse связывает аргументы командной строки с объектом args. 51 | // Не производит детальной валидации. 52 | func (args *arguments) parse() error { 53 | flag.StringVar(&args.token, "token", "", 54 | `A token for VK API access_token parameter`) 55 | flag.StringVar(&args.apiVersion, "api", "5.95", 56 | `Which VK API version to use`) 57 | 58 | flag.Parse() 59 | 60 | if n := len(flag.Args()); n < 1 { 61 | return fmt.Errorf("expected at least 1 positional argument, got 0") 62 | } 63 | 64 | args.command = flag.Args()[0] 65 | args.commandArgs = flag.Args()[1:] 66 | 67 | return nil 68 | } 69 | 70 | func storageGet(args *arguments) { 71 | resp, err := apiGet(args, "method/storage.get", "key="+args.commandArgs[0]) 72 | if err != nil { 73 | log.Panic(err) 74 | } 75 | fmt.Println(resp["response"]) 76 | } 77 | 78 | func storageSet(args *arguments) { 79 | resp, err := apiGet(args, 80 | "method/storage.set", 81 | "key="+args.commandArgs[0], 82 | "value="+args.commandArgs[1]) 83 | if err != nil { 84 | log.Panic(err) 85 | } 86 | fmt.Println(resp["response"]) 87 | } 88 | 89 | func apiURL(args *arguments, path string, params ...string) *url.URL { 90 | // Мы могли бы просто сформировать строку, но url.URL 91 | // можно использовать как простой билдер для URL'ов. 92 | u := url.URL{ 93 | Scheme: "https", 94 | Host: "api.vk.com", 95 | Path: path, 96 | } 97 | query := u.Query() 98 | query.Set("access_token", args.token) 99 | query.Set("version", args.apiVersion) 100 | for _, p := range params { 101 | kv := strings.Split(p, "=") 102 | query.Set(kv[0], kv[1]) 103 | } 104 | u.RawQuery = query.Encode() 105 | return &u 106 | } 107 | 108 | type vkResponse map[string]interface{} 109 | 110 | func apiGet(args *arguments, path string, params ...string) (vkResponse, error) { 111 | targetURL := apiURL(args, path, params...).String() 112 | 113 | rawResp, err := http.Get(targetURL) 114 | if err != nil { 115 | return nil, fmt.Errorf("get: %v", err) 116 | } 117 | defer rawResp.Body.Close() 118 | 119 | data, err := ioutil.ReadAll(rawResp.Body) 120 | if err != nil { 121 | return nil, fmt.Errorf("read resp: %v", err) 122 | } 123 | 124 | var resp vkResponse 125 | if err := json.Unmarshal(data, &resp); err != nil { 126 | return nil, fmt.Errorf("json decode: %v", err) 127 | } 128 | 129 | if apiError := resp["error"]; apiError != nil { 130 | return resp, fmt.Errorf("api error: %v", apiError) 131 | } 132 | 133 | return resp, nil 134 | } 135 | -------------------------------------------------------------------------------- /draw_gopher/body.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quasilyte/hello-go/a283076149b84bddcacd6795fff8ff16353c862a/draw_gopher/body.png -------------------------------------------------------------------------------- /draw_gopher/compose.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "image" 6 | "image/draw" 7 | "image/png" 8 | "log" 9 | "os" 10 | ) 11 | 12 | // Example: 13 | // go run compose.go ears.png body.png eyes.png teeth.png undernose.png nose.png hands.png 14 | 15 | func main() { 16 | // Bind and parse command-line flags. 17 | // Defaults are enough for gopher-drawing. 18 | width := flag.Int("w", 490, "output image width in pixels") 19 | height := flag.Int("h", 600, "output image height in pixels") 20 | outFilename := flag.String("out", "gopher.png", "output file name") 21 | flag.Parse() 22 | filenames := flag.Args() // Gopher parts 23 | if len(filenames) == 0 { 24 | log.Fatalf("expected 1 or more command-line arguments") 25 | } 26 | 27 | // Turn filenames into image objects. 28 | // Every image is a layer. 29 | // The order of layers is important. 30 | var layers []image.Image 31 | for i, filename := range filenames { 32 | f, err := os.Open(filename) 33 | if err != nil { 34 | log.Panicf("open part[%d]: %v", i, err) 35 | } 36 | defer f.Close() 37 | img, err := png.Decode(f) 38 | if err != nil { 39 | log.Panicf("decode part[%d]: %v", i, err) 40 | } 41 | layers = append(layers, img) 42 | } 43 | 44 | // Draw layers, one by one, on a new image (outImage). 45 | // Note that the first layer is drawn separately. 46 | bounds := image.Rect(0, 0, *width, *height) 47 | outImage := image.NewRGBA(bounds) 48 | draw.Draw(outImage, bounds, layers[0], image.ZP, draw.Src) 49 | for _, layer := range layers[1:] { 50 | draw.Draw(outImage, bounds, layer, image.ZP, draw.Over) 51 | } 52 | 53 | // Write our new image to a file. 54 | outFile, err := os.Create(*outFilename) 55 | if err != nil { 56 | log.Panicf("create file: %v", err) 57 | } 58 | defer outFile.Close() 59 | if err := png.Encode(outFile, outImage); err != nil { 60 | log.Panicf("encode: %v", err) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /draw_gopher/ears.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quasilyte/hello-go/a283076149b84bddcacd6795fff8ff16353c862a/draw_gopher/ears.png -------------------------------------------------------------------------------- /draw_gopher/eyes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quasilyte/hello-go/a283076149b84bddcacd6795fff8ff16353c862a/draw_gopher/eyes.png -------------------------------------------------------------------------------- /draw_gopher/hands.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quasilyte/hello-go/a283076149b84bddcacd6795fff8ff16353c862a/draw_gopher/hands.png -------------------------------------------------------------------------------- /draw_gopher/invert.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "image" 6 | "image/png" 7 | "log" 8 | "os" 9 | ) 10 | 11 | // invert: invert PNG image colors. 12 | // 13 | // Example: 14 | // go run invert.go gopher.png 15 | 16 | func main() { 17 | outFilename := flag.String("out", "inverted.png", "output file name") 18 | flag.Parse() 19 | if len(flag.Args()) != 1 { 20 | log.Fatalf("expected exactly 1 input file name") 21 | } 22 | 23 | filename := flag.Args()[0] // Image to invert 24 | 25 | f, err := os.Open(filename) 26 | if err != nil { 27 | log.Panicf("open input image: %v", err) 28 | } 29 | defer f.Close() 30 | 31 | img, err := png.Decode(f) 32 | if err != nil { 33 | log.Panicf("decode input image: %v", err) 34 | } 35 | 36 | // Type-assert ("cast") generic image to NRGBA to access individual pixels. 37 | dst, ok := img.(*image.NRGBA) 38 | if !ok { 39 | log.Panicf("only NRGBA images are supported") 40 | } 41 | 42 | bounds := dst.Bounds() 43 | for y := 0; y < bounds.Size().Y; y++ { 44 | i := y * dst.Stride 45 | for x := 0; x < bounds.Size().X; x++ { 46 | // Use i+4 to capture alpha as well. 47 | // Since we leave it "as is", we take a slice of 3 48 | // components: RGB. Alpha would be in d[3]. 49 | d := dst.Pix[i : i+3 : i+3] 50 | 51 | // Invert colors. 52 | d[0] = 255 - d[0] // R 53 | d[1] = 255 - d[1] // G 54 | d[2] = 255 - d[2] // B 55 | 56 | i += 4 57 | } 58 | } 59 | 60 | outFile, err := os.Create(*outFilename) 61 | if err != nil { 62 | log.Panicf("create file: %v", err) 63 | } 64 | defer outFile.Close() 65 | if err := png.Encode(outFile, img); err != nil { 66 | log.Panicf("encode: %v", err) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /draw_gopher/nose.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quasilyte/hello-go/a283076149b84bddcacd6795fff8ff16353c862a/draw_gopher/nose.png -------------------------------------------------------------------------------- /draw_gopher/png2jpg.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "image/jpeg" 6 | "image/png" 7 | "log" 8 | "os" 9 | "strings" 10 | ) 11 | 12 | // png2jpg: convert given PNG image to JPEG image. 13 | // 14 | // Example: 15 | // go run png2jpg.go -out gopher.jpg gopher.png 16 | 17 | func main() { 18 | outFilename := flag.String("out", "", "output file name") 19 | quality := flag.Int("q", 80, "output JPEG quality") 20 | flag.Parse() 21 | if len(flag.Args()) != 1 { 22 | log.Fatalf("expected exactly 1 input file name") 23 | } 24 | 25 | filename := flag.Args()[0] // Image to convert 26 | 27 | switch { 28 | case *quality < 0: 29 | *quality = 0 30 | case *quality > 100: 31 | *quality = 100 32 | } 33 | 34 | f, err := os.Open(filename) 35 | if err != nil { 36 | log.Panicf("open input image: %v", err) 37 | } 38 | defer f.Close() 39 | 40 | img, err := png.Decode(f) 41 | if err != nil { 42 | log.Panicf("decode input image: %v", err) 43 | } 44 | 45 | if *outFilename == "" { 46 | *outFilename = strings.ReplaceAll(filename, "png", "jpg") 47 | } 48 | 49 | outFile, err := os.Create(*outFilename) 50 | if err != nil { 51 | log.Panicf("create file: %v", err) 52 | } 53 | defer outFile.Close() 54 | 55 | opts := &jpeg.Options{Quality: *quality} 56 | if err := jpeg.Encode(outFile, img, opts); err != nil { 57 | log.Panicf("encode: %v", err) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /draw_gopher/resize.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "image/png" 6 | "log" 7 | "os" 8 | 9 | "github.com/nfnt/resize" 10 | ) 11 | 12 | // resize: resize given image to the specified size. 13 | // If either of dimensions (-w or -h) are 0, aspect ratios are preserved. 14 | // 15 | // Example: 16 | // go run resize.go -w 100 gopher.png 17 | 18 | func main() { 19 | // This program uses almost the same steps as compose.go. 20 | 21 | // Flags binding and decoding. 22 | // Using Uint instead of Int because "Resize" expects unsigned values. 23 | width := flag.Uint("w", 640, "output image width in pixels") 24 | height := flag.Uint("h", 0, "output image height in pixels") 25 | outFilename := flag.String("out", "resized.png", "output file name") 26 | flag.Parse() 27 | if len(flag.Args()) != 1 { 28 | log.Fatalf("expected exactly 1 file name") 29 | } 30 | filename := flag.Args()[0] // Image to resize 31 | 32 | f, err := os.Open(filename) 33 | if err != nil { 34 | log.Panicf("open input image: %v", err) 35 | } 36 | defer f.Close() 37 | 38 | img, err := png.Decode(f) 39 | if err != nil { 40 | log.Panicf("decode input image: %v", err) 41 | } 42 | 43 | // Resizing itself. 44 | resizedImg := resize.Resize(*width, *height, img, resize.Bicubic) 45 | 46 | outFile, err := os.Create(*outFilename) 47 | if err != nil { 48 | log.Panicf("create file: %v", err) 49 | } 50 | defer outFile.Close() 51 | 52 | if err := png.Encode(outFile, resizedImg); err != nil { 53 | log.Panicf("encode: %v", err) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /draw_gopher/teeth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quasilyte/hello-go/a283076149b84bddcacd6795fff8ff16353c862a/draw_gopher/teeth.png -------------------------------------------------------------------------------- /draw_gopher/undernose.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quasilyte/hello-go/a283076149b84bddcacd6795fff8ff16353c862a/draw_gopher/undernose.png --------------------------------------------------------------------------------