├── 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
--------------------------------------------------------------------------------