Started
This commit is contained in:
commit
fcc336bf3f
10 changed files with 1413 additions and 0 deletions
221
README.md
Normal file
221
README.md
Normal file
|
|
@ -0,0 +1,221 @@
|
||||||
|
# rubric
|
||||||
|
|
||||||
|
Интерактивный терминальный справочник для разработчика. Цветной вывод, структурированные разделы с примерами кода, быстрый доступ по ключам.
|
||||||
|
|
||||||
|
```
|
||||||
|
./rubric
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
╔════════════════════════════════════════════════════════════╗
|
||||||
|
║ rubric — интерактивный справочник ║
|
||||||
|
╚════════════════════════════════════════════════════════════╝
|
||||||
|
|
||||||
|
Доступные справочники:
|
||||||
|
|
||||||
|
1. go Справочник по языку Go
|
||||||
|
Установка, синтаксис, типы, функции, горутины...
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Установка
|
||||||
|
|
||||||
|
**Требования:** Go 1.21+
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/user/rubric
|
||||||
|
cd rubric
|
||||||
|
go build -o rubric .
|
||||||
|
```
|
||||||
|
|
||||||
|
Или сразу установить глобально:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go install github.com/user/rubric@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Использование
|
||||||
|
|
||||||
|
### Интерактивный режим
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./rubric
|
||||||
|
```
|
||||||
|
|
||||||
|
Открывается главное меню со списком справочников. Навигация двухуровневая:
|
||||||
|
|
||||||
|
```
|
||||||
|
rubric → выбор справочника → выбор раздела
|
||||||
|
```
|
||||||
|
|
||||||
|
Вводить можно **номер** или **ключ**:
|
||||||
|
|
||||||
|
```
|
||||||
|
Выберите справочник: 1
|
||||||
|
Выберите справочник: go
|
||||||
|
```
|
||||||
|
|
||||||
|
Внутри справочника:
|
||||||
|
|
||||||
|
```
|
||||||
|
Раздел: 2
|
||||||
|
Раздел: structs
|
||||||
|
Раздел: s ← сокращённый алиас
|
||||||
|
```
|
||||||
|
|
||||||
|
Для возврата на уровень выше — `b`. Для выхода — `q`.
|
||||||
|
|
||||||
|
### Быстрый доступ (без меню)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Открыть справочник Go
|
||||||
|
./rubric -book go
|
||||||
|
|
||||||
|
# Открыть конкретный раздел сразу
|
||||||
|
./rubric -book go -topic basics
|
||||||
|
./rubric -book go -topic structs
|
||||||
|
|
||||||
|
# По алиасу
|
||||||
|
./rubric -book g -topic s
|
||||||
|
```
|
||||||
|
|
||||||
|
### Вспомогательные флаги
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./rubric -list # список всех справочников
|
||||||
|
./rubric -help # справка по флагам и ключам
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Структура проекта
|
||||||
|
|
||||||
|
```
|
||||||
|
rubric/
|
||||||
|
├── go.mod
|
||||||
|
├── main.go # точка входа, разбор флагов
|
||||||
|
├── utils/
|
||||||
|
│ └── display.go # цвета, форматирование вывода
|
||||||
|
├── catalog/
|
||||||
|
│ ├── catalog.go # главное меню, реестр справочников
|
||||||
|
│ └── gobook.go # справочник Go: меню и навигация
|
||||||
|
└── modules/
|
||||||
|
└── gomodules/
|
||||||
|
├── registry.go # реестр разделов Go
|
||||||
|
├── basics.go # раздел: установка и команды
|
||||||
|
└── structures.go # раздел: структура программы
|
||||||
|
```
|
||||||
|
|
||||||
|
Каждый **справочник** — отдельный файл в `catalog/`. Каждый **раздел** внутри справочника — отдельный файл в соответствующем подпакете `modules/`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Справочники и разделы
|
||||||
|
|
||||||
|
### go — Язык Go
|
||||||
|
|
||||||
|
| Ключ | Алиасы | Раздел |
|
||||||
|
|------|--------|--------|
|
||||||
|
| `basics` | `b`, `base`, `install` | Установка и основные команды |
|
||||||
|
| `structs` | `s`, `struct`, `vars`, `types` | Структура программы |
|
||||||
|
|
||||||
|
#### basics — Установка и основные команды
|
||||||
|
|
||||||
|
- Установка Go на Linux / macOS / Windows
|
||||||
|
- Структура проекта, `go mod init`
|
||||||
|
- `go build` — сборка и кросс-компиляция
|
||||||
|
- `go run` — запуск без сборки
|
||||||
|
- `go test` — тесты, покрытие, бенчмарки, race-детектор
|
||||||
|
- `go fmt`, `go vet`, `golangci-lint` — форматирование и анализ
|
||||||
|
- `go doc` — документация
|
||||||
|
- `go get`, `go mod tidy` — управление зависимостями
|
||||||
|
- `go env`, `go clean`, `go install` — окружение и утилиты
|
||||||
|
|
||||||
|
#### structs — Структура программы
|
||||||
|
|
||||||
|
- Имена, ключевые слова, предопределённые идентификаторы
|
||||||
|
- Четыре вида объявлений: `const`, `type`, `var`, `func`
|
||||||
|
- Переменные: `var`, `:=`, нулевые значения, `var`-блоки
|
||||||
|
- Указатели: `&`, `*`, nil, сравнение адресов
|
||||||
|
- Функция `new` и когда её использовать
|
||||||
|
- Время жизни переменных, escape analysis
|
||||||
|
- Присваивания: простые, составные, tuple assignment, `_`
|
||||||
|
- Объявления типов, алиасы, конверсии
|
||||||
|
- Пакеты, файлы, все формы импорта
|
||||||
|
- Инициализация пакетов, функция `init()`
|
||||||
|
- Область видимости, затенение (shadowing)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Как добавить новый раздел в справочник Go
|
||||||
|
|
||||||
|
1. Создать файл `modules/gomodules/mymodule.go`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package gomodules
|
||||||
|
|
||||||
|
import "rubric/utils"
|
||||||
|
|
||||||
|
var MyModule = Module{
|
||||||
|
Key: "mykey",
|
||||||
|
Aliases: []string{"mk"},
|
||||||
|
Title: "Название раздела",
|
||||||
|
Description: "Краткое описание",
|
||||||
|
Run: showMyModule,
|
||||||
|
}
|
||||||
|
|
||||||
|
func showMyModule() {
|
||||||
|
utils.Header("Заголовок")
|
||||||
|
utils.Section("Подраздел")
|
||||||
|
utils.Code(`// пример кода`)
|
||||||
|
utils.Note("Важное замечание")
|
||||||
|
utils.Footer()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Добавить в `modules/gomodules/registry.go` в срез `All`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
var All = []Module{
|
||||||
|
BasicsModule,
|
||||||
|
StructuresModule,
|
||||||
|
MyModule, // ← добавить сюда
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Больше ничего не требуется — модуль автоматически появится в меню, поиске и навигации.
|
||||||
|
|
||||||
|
## Как добавить новый справочник (например, linux)
|
||||||
|
|
||||||
|
1. Создать `catalog/linuxbook.go` по образцу `catalog/gobook.go`
|
||||||
|
2. Создать `modules/linuxmodules/` с нужными разделами
|
||||||
|
3. Добавить `LinuxBook` в срез `catalog.All` в `catalog/catalog.go`
|
||||||
|
4. При необходимости расширить `switch *book` в `main.go` для флага `-book linux -topic ...`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Вспомогательные функции форматирования
|
||||||
|
|
||||||
|
Все модули используют пакет `utils` для единообразного вывода:
|
||||||
|
|
||||||
|
| Функция | Описание |
|
||||||
|
|---------|----------|
|
||||||
|
| `utils.Header(title)` | Заголовок раздела в рамке |
|
||||||
|
| `utils.Section(title)` | Подзаголовок с линией |
|
||||||
|
| `utils.Code(code)` | Блок кода в рамке |
|
||||||
|
| `utils.Command(cmd, desc)` | Команда с описанием |
|
||||||
|
| `utils.Note(text)` | Замечание с иконкой 💡 |
|
||||||
|
| `utils.Info(label, value)` | Пара ключ — значение |
|
||||||
|
| `utils.Println(text)` | Обычная строка с отступом |
|
||||||
|
| `utils.Footer()` | Нижняя линия раздела |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Цветовой вывод
|
||||||
|
|
||||||
|
Использует ANSI escape-коды. Работает в большинстве современных терминалов: iTerm2, Terminal.app, GNOME Terminal, Windows Terminal, tmux и других.
|
||||||
|
|
||||||
|
В средах без поддержки цвета (например, при перенаправлении вывода в файл) escape-коды будут видны как есть. При необходимости отключения цвета — установите переменную `NO_COLOR=1` перед запуском или реализуйте проверку `os.Getenv("NO_COLOR")` в `utils/display.go`.
|
||||||
176
catalog/catalog.go
Normal file
176
catalog/catalog.go
Normal file
|
|
@ -0,0 +1,176 @@
|
||||||
|
package catalog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"rubric/utils"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Book описывает один справочник верхнего уровня
|
||||||
|
type Book struct {
|
||||||
|
Key string
|
||||||
|
Aliases []string
|
||||||
|
Title string
|
||||||
|
Description string
|
||||||
|
Run func(scanner *bufio.Scanner) // передаём scanner чтобы не создавать новый
|
||||||
|
}
|
||||||
|
|
||||||
|
// All — список всех справочников
|
||||||
|
var All = []Book{
|
||||||
|
GoBook,
|
||||||
|
// LinuxBook — будет добавлен позже
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find ищет книгу по ключу или алиасу
|
||||||
|
func Find(key string) (Book, bool) {
|
||||||
|
key = strings.ToLower(strings.TrimSpace(key))
|
||||||
|
for _, b := range All {
|
||||||
|
if b.Key == key {
|
||||||
|
return b, true
|
||||||
|
}
|
||||||
|
for _, a := range b.Aliases {
|
||||||
|
if a == key {
|
||||||
|
return b, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Book{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShowMainMenu отображает главное меню
|
||||||
|
func ShowMainMenu() {
|
||||||
|
utils.Header("rubric — интерактивный справочник")
|
||||||
|
|
||||||
|
fmt.Printf(" %s%sДоступные справочники:%s\n\n",
|
||||||
|
utils.ColorBold, utils.ColorWhite, utils.ColorReset)
|
||||||
|
|
||||||
|
for i, b := range All {
|
||||||
|
aliases := ""
|
||||||
|
if len(b.Aliases) > 0 {
|
||||||
|
aliases = fmt.Sprintf("%s [%s]%s",
|
||||||
|
utils.ColorDim, strings.Join(b.Aliases, ", "), utils.ColorReset)
|
||||||
|
}
|
||||||
|
fmt.Printf(" %s%s %2d.%s %s%-8s%s %s%-34s%s%s\n",
|
||||||
|
utils.ColorBold, utils.ColorCyan, i+1, utils.ColorReset,
|
||||||
|
utils.ColorYellow+utils.ColorBold, b.Key, utils.ColorReset,
|
||||||
|
utils.ColorWhite, b.Title, utils.ColorReset,
|
||||||
|
aliases,
|
||||||
|
)
|
||||||
|
fmt.Printf(" %s%s%s\n\n",
|
||||||
|
utils.ColorDim, b.Description, utils.ColorReset)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf(" %s%sВыберите справочник (номер / ключ / q — выход):%s ",
|
||||||
|
utils.ColorBold, utils.ColorGreen, utils.ColorReset)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunInteractive — главный интерактивный цикл
|
||||||
|
func RunInteractive(scanner *bufio.Scanner) {
|
||||||
|
for {
|
||||||
|
ShowMainMenu()
|
||||||
|
|
||||||
|
if !scanner.Scan() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
input := strings.TrimSpace(scanner.Text())
|
||||||
|
|
||||||
|
if input == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if input == "q" || input == "quit" || input == "exit" {
|
||||||
|
fmt.Printf("\n %s%sДо свидания!%s\n\n",
|
||||||
|
utils.ColorBold, utils.ColorCyan, utils.ColorReset)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// По номеру
|
||||||
|
if n, err := strconv.Atoi(input); err == nil {
|
||||||
|
if n >= 1 && n <= len(All) {
|
||||||
|
All[n-1].Run(scanner)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Printf("\n %s%sНомер %d вне диапазона (1–%d)%s\n",
|
||||||
|
utils.ColorBold, utils.ColorRed, n, len(All), utils.ColorReset)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// По ключу
|
||||||
|
if b, ok := Find(input); ok {
|
||||||
|
b.Run(scanner)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("\n %s%sНе понял '%s'. Введите номер или ключ справочника.%s\n",
|
||||||
|
utils.ColorBold, utils.ColorRed, input, utils.ColorReset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShowHelp — общая справка
|
||||||
|
func ShowHelp() {
|
||||||
|
utils.Header("rubric — справка по использованию")
|
||||||
|
|
||||||
|
utils.Section("Запуск")
|
||||||
|
utils.Command("./rubric", "главное меню")
|
||||||
|
utils.Command("./rubric -book go", "открыть справочник напрямую")
|
||||||
|
utils.Command("./rubric -book go -topic basics", "открыть конкретный раздел")
|
||||||
|
utils.Command("./rubric -list", "список всех справочников и разделов")
|
||||||
|
utils.Command("./rubric -help", "эта справка")
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
utils.Section("Доступные справочники")
|
||||||
|
for _, b := range All {
|
||||||
|
keys := append([]string{b.Key}, b.Aliases...)
|
||||||
|
fmt.Printf(" %s%s%-8s%s %-30s %sключи: %s%s\n",
|
||||||
|
utils.ColorBold, utils.ColorCyan, b.Key, utils.ColorReset,
|
||||||
|
b.Title,
|
||||||
|
utils.ColorDim, strings.Join(keys, ", "), utils.ColorReset,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.Footer()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAll — краткий список всех справочников и их разделов
|
||||||
|
func ListAll() {
|
||||||
|
fmt.Printf("\n%s%srubric — все справочники:%s\n\n",
|
||||||
|
utils.ColorBold, utils.ColorCyan, utils.ColorReset)
|
||||||
|
|
||||||
|
for i, b := range All {
|
||||||
|
fmt.Printf(" %s%d.%s %s%s%s — %s\n",
|
||||||
|
utils.ColorDim, i+1, utils.ColorReset,
|
||||||
|
utils.ColorYellow+utils.ColorBold, b.Key, utils.ColorReset,
|
||||||
|
b.Title,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrintBackPrompt — подсказка "вернуться назад"
|
||||||
|
func PrintBackPrompt(scanner *bufio.Scanner) {
|
||||||
|
fmt.Printf("\n %s%s[Enter — вернуться в меню справочника, b — главное меню]%s ",
|
||||||
|
utils.ColorDim, utils.ColorCyan, utils.ColorReset)
|
||||||
|
if scanner.Scan() {
|
||||||
|
text := strings.TrimSpace(scanner.Text())
|
||||||
|
_ = text // "b" обрабатывается в вызывающем коде
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadLine читает строку из сканера
|
||||||
|
func ReadLine(scanner *bufio.Scanner) (string, bool) {
|
||||||
|
if !scanner.Scan() {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(scanner.Text()), true
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsBack возвращает true если пользователь хочет вернуться на уровень выше
|
||||||
|
func IsBack(s string) bool {
|
||||||
|
return s == "b" || s == "back" || s == "назад" || s == ".."
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsQuit возвращает true если пользователь хочет выйти
|
||||||
|
func IsQuit(s string) bool {
|
||||||
|
return s == "q" || s == "quit" || s == "exit"
|
||||||
|
}
|
||||||
122
catalog/gobook.go
Normal file
122
catalog/gobook.go
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
package catalog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"rubric/modules/gomodules"
|
||||||
|
"rubric/utils"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GoBook — запись справочника Go в главном меню
|
||||||
|
var GoBook = Book{
|
||||||
|
Key: "go",
|
||||||
|
Aliases: []string{"g", "golang"},
|
||||||
|
Title: "Справочник по языку Go",
|
||||||
|
Description: "Установка, синтаксис, типы, функции, горутины, интерфейсы и многое другое",
|
||||||
|
Run: runGoBook,
|
||||||
|
}
|
||||||
|
|
||||||
|
func runGoBook(scanner *bufio.Scanner) {
|
||||||
|
for {
|
||||||
|
showGoMenu()
|
||||||
|
|
||||||
|
input, ok := ReadLine(scanner)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if input == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if IsQuit(input) {
|
||||||
|
fmt.Printf("\n %s%sДо свидания!%s\n\n",
|
||||||
|
utils.ColorBold, utils.ColorCyan, utils.ColorReset)
|
||||||
|
// сигнализируем главному циклу о выходе через os.Exit
|
||||||
|
// чтобы не усложнять — просто выходим
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if IsBack(input) {
|
||||||
|
return // назад в главное меню
|
||||||
|
}
|
||||||
|
|
||||||
|
// По номеру
|
||||||
|
if n, err := strconv.Atoi(input); err == nil {
|
||||||
|
if n >= 1 && n <= len(gomodules.All) {
|
||||||
|
gomodules.All[n-1].Run()
|
||||||
|
waitBack(scanner)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Printf("\n %s%sНомер %d вне диапазона (1–%d)%s\n",
|
||||||
|
utils.ColorBold, utils.ColorRed, n, len(gomodules.All), utils.ColorReset)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// По ключу
|
||||||
|
if m, ok := gomodules.Find(input); ok {
|
||||||
|
m.Run()
|
||||||
|
waitBack(scanner)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("\n %s%sНе понял '%s'. Номер, ключ раздела, b — назад.%s\n",
|
||||||
|
utils.ColorBold, utils.ColorRed, input, utils.ColorReset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func showGoMenu() {
|
||||||
|
utils.Header("Справочник Go")
|
||||||
|
|
||||||
|
fmt.Printf(" %s%sРазделы:%s\n\n",
|
||||||
|
utils.ColorBold, utils.ColorWhite, utils.ColorReset)
|
||||||
|
|
||||||
|
for i, m := range gomodules.All {
|
||||||
|
aliases := ""
|
||||||
|
if len(m.Aliases) > 0 {
|
||||||
|
aliases = fmt.Sprintf("%s [%s]%s",
|
||||||
|
utils.ColorDim, strings.Join(m.Aliases, ", "), utils.ColorReset)
|
||||||
|
}
|
||||||
|
fmt.Printf(" %s%s %2d.%s %s%-10s%s %s%-32s%s%s\n",
|
||||||
|
utils.ColorBold, utils.ColorCyan, i+1, utils.ColorReset,
|
||||||
|
utils.ColorYellow+utils.ColorBold, m.Key, utils.ColorReset,
|
||||||
|
utils.ColorWhite, m.Title, utils.ColorReset,
|
||||||
|
aliases,
|
||||||
|
)
|
||||||
|
fmt.Printf(" %s%s%s\n\n",
|
||||||
|
utils.ColorDim, m.Description, utils.ColorReset)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf(" %s%sРаздел (номер / ключ / b — главное меню / q — выход):%s ",
|
||||||
|
utils.ColorBold, utils.ColorGreen, utils.ColorReset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitBack(scanner *bufio.Scanner) {
|
||||||
|
fmt.Printf("\n %s%s[Enter — назад в меню Go, b — главное меню]%s ",
|
||||||
|
utils.ColorDim, utils.ColorCyan, utils.ColorReset)
|
||||||
|
if scanner.Scan() {
|
||||||
|
if IsBack(strings.TrimSpace(scanner.Text())) {
|
||||||
|
// вернёмся на уровень выше — используем специальный возврат
|
||||||
|
// реализуем через панику+recover для чистоты навигации
|
||||||
|
panic(backSignal{})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// backSignal — сигнал для возврата на уровень выше из waitBack
|
||||||
|
type backSignal struct{}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Оборачиваем runGoBook чтобы перехватывать backSignal
|
||||||
|
orig := GoBook.Run
|
||||||
|
GoBook.Run = func(scanner *bufio.Scanner) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
if _, ok := r.(backSignal); !ok {
|
||||||
|
panic(r) // пробрасываем не наши паники
|
||||||
|
}
|
||||||
|
// backSignal — просто возвращаемся в главное меню, ничего не делаем
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
orig(scanner)
|
||||||
|
}
|
||||||
|
}
|
||||||
3
go.mod
Normal file
3
go.mod
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
module rubric
|
||||||
|
|
||||||
|
go 1.21
|
||||||
68
main.go
Normal file
68
main.go
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"rubric/catalog"
|
||||||
|
"rubric/modules/gomodules"
|
||||||
|
"rubric/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
book := flag.String("book", "", "открыть справочник напрямую (например: -book go)")
|
||||||
|
topic := flag.String("topic", "", "открыть раздел (используется вместе с -book)")
|
||||||
|
list := flag.Bool("list", false, "список всех справочников и разделов")
|
||||||
|
help := flag.Bool("help", false, "справка по использованию")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(os.Stdin)
|
||||||
|
|
||||||
|
if *help {
|
||||||
|
catalog.ShowHelp()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if *list {
|
||||||
|
catalog.ListAll()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// -book go -topic basics
|
||||||
|
if *book != "" && *topic != "" {
|
||||||
|
switch *book {
|
||||||
|
case "go", "g", "golang":
|
||||||
|
m, ok := gomodules.Find(*topic)
|
||||||
|
if !ok {
|
||||||
|
fmt.Printf("\n %s%sРаздел '%s' не найден в справочнике Go.%s\n",
|
||||||
|
utils.ColorBold, utils.ColorRed, *topic, utils.ColorReset)
|
||||||
|
gomodules.ListModules()
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
m.Run()
|
||||||
|
default:
|
||||||
|
fmt.Printf("\n %s%sСправочник '%s' не найден.%s\n",
|
||||||
|
utils.ColorBold, utils.ColorRed, *book, utils.ColorReset)
|
||||||
|
catalog.ListAll()
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// -book go
|
||||||
|
if *book != "" {
|
||||||
|
b, ok := catalog.Find(*book)
|
||||||
|
if !ok {
|
||||||
|
fmt.Printf("\n %s%sСправочник '%s' не найден.%s\n",
|
||||||
|
utils.ColorBold, utils.ColorRed, *book, utils.ColorReset)
|
||||||
|
catalog.ListAll()
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
b.Run(scanner)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Интерактивный режим
|
||||||
|
catalog.RunInteractive(scanner)
|
||||||
|
}
|
||||||
185
modules/gomodules/go_basics.go
Normal file
185
modules/gomodules/go_basics.go
Normal file
|
|
@ -0,0 +1,185 @@
|
||||||
|
package gomodules
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"rubric/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
var BasicsModule = Module{
|
||||||
|
Key: "basics",
|
||||||
|
Aliases: []string{"b", "base", "install"},
|
||||||
|
Title: "Установка и основные команды",
|
||||||
|
Description: "Установка Go, go build, go test, go doc и базовые инструменты",
|
||||||
|
Run: showBasics,
|
||||||
|
}
|
||||||
|
|
||||||
|
func showBasics() {
|
||||||
|
utils.Header("Go — Установка и основные команды")
|
||||||
|
|
||||||
|
// ─── Установка ───────────────────────────────────────────────
|
||||||
|
utils.Section("Установка Go")
|
||||||
|
|
||||||
|
utils.Println("Официальный сайт: https://go.dev/dl/")
|
||||||
|
fmt.Println()
|
||||||
|
utils.Info("Linux / macOS", "скачать архив и распаковать в /usr/local/go")
|
||||||
|
utils.Code(`# Linux (пример для версии 1.22)
|
||||||
|
wget https://go.dev/dl/go1.22.0.linux-amd64.tar.gz
|
||||||
|
sudo tar -C /usr/local -xzf go1.22.0.linux-amd64.tar.gz
|
||||||
|
|
||||||
|
# Добавить в ~/.bashrc или ~/.zshrc:
|
||||||
|
export PATH=$PATH:/usr/local/go/bin`)
|
||||||
|
|
||||||
|
utils.Info("macOS (Homebrew)", "brew install go")
|
||||||
|
utils.Info("Windows", "скачать .msi установщик с go.dev/dl")
|
||||||
|
utils.Code(`# Проверить установку:
|
||||||
|
go version
|
||||||
|
# go version go1.22.0 linux/amd64`)
|
||||||
|
|
||||||
|
utils.Note("Переменная GOPATH по умолчанию — ~/go. Код проектов хранится в ~/go/src/")
|
||||||
|
|
||||||
|
// ─── Структура проекта ───────────────────────────────────────
|
||||||
|
utils.Section("Структура проекта и модули")
|
||||||
|
|
||||||
|
utils.Code(`# Создать новый модуль
|
||||||
|
go mod init github.com/user/myproject
|
||||||
|
|
||||||
|
# Структура типичного проекта:
|
||||||
|
myproject/
|
||||||
|
├── go.mod # описание модуля и зависимостей
|
||||||
|
├── go.sum # контрольные суммы зависимостей
|
||||||
|
├── main.go # точка входа
|
||||||
|
├── internal/ # внутренние пакеты (недоступны снаружи)
|
||||||
|
└── pkg/ # переиспользуемые пакеты`)
|
||||||
|
|
||||||
|
// ─── go build ────────────────────────────────────────────────
|
||||||
|
utils.Section("Команды: сборка")
|
||||||
|
|
||||||
|
utils.Command("go build", "скомпилировать текущий пакет")
|
||||||
|
utils.Command("go build ./...", "скомпилировать все пакеты проекта")
|
||||||
|
utils.Command("go build -o myapp", "указать имя выходного файла")
|
||||||
|
utils.Command("go build -v", "показать компилируемые пакеты")
|
||||||
|
utils.Command("go build -race", "включить детектор гонок данных")
|
||||||
|
fmt.Println()
|
||||||
|
utils.Code(`# Кросс-компиляция (собрать под Windows на Linux):
|
||||||
|
GOOS=windows GOARCH=amd64 go build -o app.exe
|
||||||
|
|
||||||
|
# Основные пары GOOS/GOARCH:
|
||||||
|
# linux/amd64 linux/arm64
|
||||||
|
# windows/amd64
|
||||||
|
# darwin/amd64 darwin/arm64 (macOS Intel / Apple Silicon)`)
|
||||||
|
|
||||||
|
// ─── go run ──────────────────────────────────────────────────
|
||||||
|
utils.Section("Команды: запуск")
|
||||||
|
|
||||||
|
utils.Command("go run main.go", "скомпилировать и сразу запустить файл")
|
||||||
|
utils.Command("go run .", "запустить пакет в текущей директории")
|
||||||
|
utils.Command("go run ./cmd/app", "запустить конкретный пакет")
|
||||||
|
|
||||||
|
// ─── go test ─────────────────────────────────────────────────
|
||||||
|
utils.Section("Команды: тестирование")
|
||||||
|
|
||||||
|
utils.Command("go test ./...", "запустить все тесты проекта")
|
||||||
|
utils.Command("go test -v", "подробный вывод (имена тестов)")
|
||||||
|
utils.Command("go test -run TestName", "запустить конкретный тест")
|
||||||
|
utils.Command("go test -run TestName/SubTest", "запустить под-тест")
|
||||||
|
utils.Command("go test -count=3", "запустить каждый тест 3 раза")
|
||||||
|
utils.Command("go test -timeout 30s", "таймаут на все тесты")
|
||||||
|
utils.Command("go test -race", "тесты с детектором гонок")
|
||||||
|
utils.Command("go test -cover", "показать покрытие кода")
|
||||||
|
utils.Command("go test -coverprofile=c.out", "сохранить отчёт о покрытии")
|
||||||
|
utils.Command("go tool cover -html=c.out", "открыть покрытие в браузере")
|
||||||
|
utils.Command("go test -bench=.", "запустить бенчмарки")
|
||||||
|
utils.Command("go test -bench=. -benchmem", "бенчмарки + аллокации памяти")
|
||||||
|
fmt.Println()
|
||||||
|
utils.Code(`// Пример тестового файла (math_test.go):
|
||||||
|
package mypackage
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestAdd(t *testing.T) {
|
||||||
|
result := Add(2, 3)
|
||||||
|
if result != 5 {
|
||||||
|
t.Errorf("Add(2,3) = %d, хотели 5", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Под-тесты (subtests):
|
||||||
|
func TestOperations(t *testing.T) {
|
||||||
|
t.Run("сложение", func(t *testing.T) { ... })
|
||||||
|
t.Run("вычитание", func(t *testing.T) { ... })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Бенчмарк:
|
||||||
|
func BenchmarkAdd(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
Add(2, 3)
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
|
||||||
|
// ─── go fmt / vet / lint ─────────────────────────────────────
|
||||||
|
utils.Section("Команды: форматирование и анализ")
|
||||||
|
|
||||||
|
utils.Command("go fmt ./...", "автоформатирование всех файлов")
|
||||||
|
utils.Command("go vet ./...", "статический анализ на типичные ошибки")
|
||||||
|
utils.Command("go vet -shadow ./...", "поиск затенения переменных")
|
||||||
|
utils.Command("gofmt -d main.go", "показать diff без записи")
|
||||||
|
utils.Command("gofmt -w main.go", "записать форматирование в файл")
|
||||||
|
fmt.Println()
|
||||||
|
utils.Note("golangci-lint — популярный агрегатор линтеров: golangci-lint.run")
|
||||||
|
utils.Code(`# Установка golangci-lint:
|
||||||
|
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
|
||||||
|
golangci-lint run ./...`)
|
||||||
|
|
||||||
|
// ─── go doc ──────────────────────────────────────────────────
|
||||||
|
utils.Section("Документация: go doc")
|
||||||
|
|
||||||
|
utils.Command("go doc fmt", "документация пакета fmt")
|
||||||
|
utils.Command("go doc fmt.Println", "документация конкретной функции")
|
||||||
|
utils.Command("go doc -all fmt", "вся документация пакета")
|
||||||
|
utils.Command("go doc -src fmt.Println", "исходный код функции")
|
||||||
|
utils.Command("go doc builtin", "встроенные функции и типы")
|
||||||
|
fmt.Println()
|
||||||
|
utils.Code(`// Комментарий к функции становится документацией:
|
||||||
|
// Add складывает два целых числа и возвращает результат.
|
||||||
|
// Оба аргумента должны быть неотрицательными.
|
||||||
|
func Add(a, b int) int {
|
||||||
|
return a + b
|
||||||
|
}
|
||||||
|
|
||||||
|
// Запустить веб-сервер документации:
|
||||||
|
// godoc -http=:6060`)
|
||||||
|
|
||||||
|
// ─── go get / mod ────────────────────────────────────────────
|
||||||
|
utils.Section("Управление зависимостями")
|
||||||
|
|
||||||
|
utils.Command("go get github.com/pkg/errors", "добавить зависимость")
|
||||||
|
utils.Command("go get github.com/pkg/errors@v0.9.1", "конкретная версия")
|
||||||
|
utils.Command("go get github.com/pkg/errors@latest", "последняя версия")
|
||||||
|
utils.Command("go mod tidy", "удалить лишние / добавить нужные зависимости")
|
||||||
|
utils.Command("go mod vendor", "скопировать зависимости в ./vendor")
|
||||||
|
utils.Command("go mod graph", "граф зависимостей")
|
||||||
|
utils.Command("go mod why pkg", "почему пакет является зависимостью")
|
||||||
|
utils.Command("go list -m all", "список всех зависимостей")
|
||||||
|
|
||||||
|
// ─── go install / env ─────────────────────────────────────────
|
||||||
|
utils.Section("Прочие полезные команды")
|
||||||
|
|
||||||
|
utils.Command("go install tool@latest", "установить CLI-инструмент глобально")
|
||||||
|
utils.Command("go env", "показать все переменные окружения Go")
|
||||||
|
utils.Command("go env GOPATH", "показать конкретную переменную")
|
||||||
|
utils.Command("go env -w GONOSUMCHECK=*", "установить переменную глобально")
|
||||||
|
utils.Command("go clean -cache", "очистить кэш сборки")
|
||||||
|
utils.Command("go clean -modcache", "очистить кэш модулей")
|
||||||
|
utils.Command("go generate ./...", "запустить //go:generate директивы")
|
||||||
|
utils.Command("go work init ./mod1 ./mod2", "workspace для нескольких модулей")
|
||||||
|
fmt.Println()
|
||||||
|
utils.Code(`# Ключевые переменные окружения:
|
||||||
|
GOROOT — где установлен Go (/usr/local/go)
|
||||||
|
GOPATH — рабочая директория (~/go)
|
||||||
|
GOBIN — куда устанавливаются бинарники (~/go/bin)
|
||||||
|
GOPROXY — прокси для модулей (https://proxy.golang.org)
|
||||||
|
GONOSUMCHECK — пакеты без проверки checksumdb
|
||||||
|
CGO_ENABLED — включить/отключить cgo (0 или 1)`)
|
||||||
|
|
||||||
|
utils.Footer()
|
||||||
|
}
|
||||||
101
modules/gomodules/go_registry.go
Normal file
101
modules/gomodules/go_registry.go
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
package gomodules
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"rubric/utils"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Module описывает один раздел справочника
|
||||||
|
type Module struct {
|
||||||
|
Key string // основной ключ для -topic
|
||||||
|
Aliases []string // сокращения
|
||||||
|
Title string // название в меню
|
||||||
|
Description string // краткое описание
|
||||||
|
Run func() // функция вывода
|
||||||
|
}
|
||||||
|
|
||||||
|
// All — список всех модулей в порядке меню
|
||||||
|
var All = []Module{
|
||||||
|
BasicsModule,
|
||||||
|
StructuresModule,
|
||||||
|
// сюда будут добавляться новые модули
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find ищет модуль по ключу или алиасу
|
||||||
|
func Find(key string) (Module, bool) {
|
||||||
|
key = strings.ToLower(strings.TrimSpace(key))
|
||||||
|
for _, m := range All {
|
||||||
|
if m.Key == key {
|
||||||
|
return m, true
|
||||||
|
}
|
||||||
|
for _, a := range m.Aliases {
|
||||||
|
if a == key {
|
||||||
|
return m, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Module{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShowMenu отображает интерактивное меню
|
||||||
|
func ShowMenu() {
|
||||||
|
utils.Header("Справочник Go")
|
||||||
|
|
||||||
|
fmt.Printf(" %s%sДоступные разделы:%s\n\n", utils.ColorBold, utils.ColorWhite, utils.ColorReset)
|
||||||
|
|
||||||
|
for i, m := range All {
|
||||||
|
aliases := ""
|
||||||
|
if len(m.Aliases) > 0 {
|
||||||
|
aliases = fmt.Sprintf("%s [%s]%s", utils.ColorDim, strings.Join(m.Aliases, ", "), utils.ColorReset)
|
||||||
|
}
|
||||||
|
fmt.Printf(" %s%s%2d.%s %-10s %s%-36s%s%s\n",
|
||||||
|
utils.ColorBold, utils.ColorCyan, i+1, utils.ColorReset,
|
||||||
|
utils.ColorYellow+utils.ColorBold+m.Key+utils.ColorReset,
|
||||||
|
utils.ColorWhite, m.Title, utils.ColorReset,
|
||||||
|
aliases,
|
||||||
|
)
|
||||||
|
fmt.Printf(" %s%s%s\n", utils.ColorDim, m.Description, utils.ColorReset)
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf(" %s%sВведите номер или ключ раздела (q — выход):%s ", utils.ColorBold, utils.ColorGreen, utils.ColorReset)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShowHelp выводит справку по ключам
|
||||||
|
func ShowHelp() {
|
||||||
|
utils.Header("rubric — справка по использованию")
|
||||||
|
|
||||||
|
utils.Section("Запуск")
|
||||||
|
utils.Command("./rubric", "интерактивное меню")
|
||||||
|
utils.Command("./rubric -topic basics", "открыть раздел напрямую")
|
||||||
|
utils.Command("./rubric -topic b", "по короткому алиасу")
|
||||||
|
utils.Command("./rubric -list", "список всех разделов")
|
||||||
|
utils.Command("./rubric -help", "эта справка")
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
utils.Section("Доступные разделы и ключи")
|
||||||
|
for _, m := range All {
|
||||||
|
keys := append([]string{m.Key}, m.Aliases...)
|
||||||
|
fmt.Printf(" %s%-10s%s %-32s %sключи: %s%s\n",
|
||||||
|
utils.ColorBold+utils.ColorCyan, m.Key, utils.ColorReset,
|
||||||
|
m.Title,
|
||||||
|
utils.ColorDim, strings.Join(keys, ", "), utils.ColorReset,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.Footer()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListModules выводит краткий список модулей
|
||||||
|
func ListModules() {
|
||||||
|
fmt.Printf("\n%s%sРазделы Go:%s\n\n", utils.ColorBold, utils.ColorCyan, utils.ColorReset)
|
||||||
|
for i, m := range All {
|
||||||
|
fmt.Printf(" %s%d.%s %-10s — %s\n",
|
||||||
|
utils.ColorDim, i+1, utils.ColorReset,
|
||||||
|
utils.ColorYellow+utils.ColorBold+m.Key+utils.ColorReset,
|
||||||
|
m.Title,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
461
modules/gomodules/go_structures.go
Normal file
461
modules/gomodules/go_structures.go
Normal file
|
|
@ -0,0 +1,461 @@
|
||||||
|
package gomodules
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"rubric/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
var StructuresModule = Module{
|
||||||
|
Key: "structs",
|
||||||
|
Aliases: []string{"s", "struct", "vars", "types"},
|
||||||
|
Title: "Структура программы",
|
||||||
|
Description: "Имена, объявления, переменные, указатели, типы, пакеты, область видимости",
|
||||||
|
Run: showStructures,
|
||||||
|
}
|
||||||
|
|
||||||
|
func showStructures() {
|
||||||
|
utils.Header("Go — Структура программы")
|
||||||
|
|
||||||
|
// ─── Имена и ключевые слова ───────────────────────────────────
|
||||||
|
utils.Section("Имена и ключевые слова")
|
||||||
|
|
||||||
|
utils.Println("Имя начинается с буквы (Unicode) или символа подчёркивания, затем — буквы, цифры, _.")
|
||||||
|
utils.Println("Регистр имеет значение: " +
|
||||||
|
utils.ColorBold + utils.ColorCyan + "Name" + utils.ColorReset +
|
||||||
|
" и " +
|
||||||
|
utils.ColorBold + utils.ColorCyan + "name" + utils.ColorReset +
|
||||||
|
" — разные идентификаторы.")
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
utils.Println(utils.ColorBold + utils.ColorYellow + "25 ключевых слов языка Go:" + utils.ColorReset)
|
||||||
|
fmt.Println()
|
||||||
|
keywords := [][]string{
|
||||||
|
{"break", "case", "chan", "const", "continue"},
|
||||||
|
{"default", "defer", "else", "fallthrough", "for"},
|
||||||
|
{"func", "go", "goto", "if", "import"},
|
||||||
|
{"interface", "map", "package", "range", "return"},
|
||||||
|
{"select", "struct", "switch", "type", "var"},
|
||||||
|
}
|
||||||
|
for _, row := range keywords {
|
||||||
|
fmt.Print(" ")
|
||||||
|
for _, kw := range row {
|
||||||
|
fmt.Printf("%s%-14s%s", utils.ColorCyan+utils.ColorBold, kw, utils.ColorReset)
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
utils.Println(utils.ColorBold + utils.ColorYellow + "Предопределённые имена (не ключевые слова — можно переопределить, но не стоит):" + utils.ColorReset)
|
||||||
|
fmt.Println()
|
||||||
|
utils.Println(utils.ColorDim + " Типы:" + utils.ColorReset)
|
||||||
|
utils.Println(" bool byte rune string error")
|
||||||
|
utils.Println(" int int8 int16 int32 int64")
|
||||||
|
utils.Println(" uint uint8 uint16 uint32 uint64 uintptr")
|
||||||
|
utils.Println(" float32 float64 complex64 complex128")
|
||||||
|
fmt.Println()
|
||||||
|
utils.Println(utils.ColorDim + " Константы:" + utils.ColorReset)
|
||||||
|
utils.Println(" true false iota")
|
||||||
|
fmt.Println()
|
||||||
|
utils.Println(utils.ColorDim + " Нулевое значение:" + utils.ColorReset)
|
||||||
|
utils.Println(" nil")
|
||||||
|
fmt.Println()
|
||||||
|
utils.Println(utils.ColorDim + " Встроенные функции:" + utils.ColorReset)
|
||||||
|
utils.Println(" append cap close complex copy delete imag")
|
||||||
|
utils.Println(" len make new panic print println real recover")
|
||||||
|
fmt.Println()
|
||||||
|
utils.Note("Если имя начинается с заглавной буквы — оно экспортируется из пакета (публичное).")
|
||||||
|
utils.Note("Стиль: camelCase для локальных, CamelCase для экспортируемых. Аббревиатуры — заглавными: URL, ID, HTTP.")
|
||||||
|
|
||||||
|
// ─── Объявления ───────────────────────────────────────────────
|
||||||
|
utils.Section("Объявления")
|
||||||
|
|
||||||
|
utils.Println("В Go четыре вида объявлений верхнего уровня:")
|
||||||
|
fmt.Println()
|
||||||
|
utils.Code(`package main
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// const — константа верхнего уровня
|
||||||
|
const Greeting = "Привет"
|
||||||
|
|
||||||
|
// type — новый тип
|
||||||
|
type Celsius float64
|
||||||
|
|
||||||
|
// var — переменная верхнего уровня
|
||||||
|
var boilingPoint Celsius = 100
|
||||||
|
|
||||||
|
// func — функция
|
||||||
|
func main() {
|
||||||
|
fmt.Println(Greeting, boilingPoint)
|
||||||
|
}`)
|
||||||
|
utils.Println("Порядок объявлений на верхнем уровне не важен — компилятор разрешает зависимости сам.")
|
||||||
|
|
||||||
|
// ─── Переменные ───────────────────────────────────────────────
|
||||||
|
utils.Section("Переменные и способы объявления")
|
||||||
|
|
||||||
|
utils.Println(utils.ColorBold + "1. Полное объявление через var:" + utils.ColorReset)
|
||||||
|
utils.Code(`var имя тип = выражение
|
||||||
|
|
||||||
|
var x int // x == 0 (нулевое значение)
|
||||||
|
var s string // s == ""
|
||||||
|
var b bool // b == false
|
||||||
|
var p *int // p == nil
|
||||||
|
|
||||||
|
// Несколько переменных одного типа:
|
||||||
|
var i, j, k int
|
||||||
|
|
||||||
|
// Несколько переменных разных типов:
|
||||||
|
var (
|
||||||
|
name string = "Gopher"
|
||||||
|
age int = 3
|
||||||
|
active bool = true
|
||||||
|
)`)
|
||||||
|
|
||||||
|
utils.Println(utils.ColorBold + "2. Краткое объявление := (только внутри функций):" + utils.ColorReset)
|
||||||
|
utils.Code(`имя := выражение // тип выводится автоматически
|
||||||
|
|
||||||
|
x := 42 // int
|
||||||
|
s := "hello" // string
|
||||||
|
f := 3.14 // float64
|
||||||
|
b := true // bool
|
||||||
|
|
||||||
|
// Несколько переменных:
|
||||||
|
i, j := 0, 1
|
||||||
|
name, err := os.Open("file.txt") // часто используется для ошибок
|
||||||
|
|
||||||
|
// ВАЖНО: хотя бы одна переменная слева должна быть новой:
|
||||||
|
x, y := 1, 2 // ok: обе новые
|
||||||
|
x, y = 3, 4 // ok: присваивание (уже объявлены)
|
||||||
|
x, z := 5, 6 // ok: z — новая, x — переприсваивается`)
|
||||||
|
|
||||||
|
utils.Println(utils.ColorBold + "3. Нулевые значения (zero values):" + utils.ColorReset)
|
||||||
|
utils.Code(`// Каждый тип имеет нулевое значение при объявлении без инициализации:
|
||||||
|
var i int // 0
|
||||||
|
var f float64 // 0.0
|
||||||
|
var b bool // false
|
||||||
|
var s string // ""
|
||||||
|
var p *int // nil
|
||||||
|
var sl []int // nil
|
||||||
|
var m map[string]int // nil
|
||||||
|
var fn func() // nil
|
||||||
|
var err error // nil`)
|
||||||
|
utils.Note("В Go нет неинициализированных переменных — нулевое значение гарантировано.")
|
||||||
|
|
||||||
|
utils.Println(utils.ColorBold + "4. Объявление нескольких переменных с var-блоком:" + utils.ColorReset)
|
||||||
|
utils.Code(`// Группировка для читаемости:
|
||||||
|
var (
|
||||||
|
debug = false
|
||||||
|
version = "1.0.0"
|
||||||
|
maxConn = 100
|
||||||
|
)`)
|
||||||
|
|
||||||
|
// ─── Указатели ────────────────────────────────────────────────
|
||||||
|
utils.Section("Указатели")
|
||||||
|
|
||||||
|
utils.Println("Указатель хранит " + utils.ColorBold + "адрес" + utils.ColorReset + " переменной в памяти.")
|
||||||
|
utils.Code(`var x int = 42
|
||||||
|
|
||||||
|
p := &x // p — указатель на x, тип *int
|
||||||
|
fmt.Println(p) // 0xc0000b4008 (адрес)
|
||||||
|
fmt.Println(*p) // 42 (разыменование — значение по адресу)
|
||||||
|
|
||||||
|
*p = 100 // изменить значение x через указатель
|
||||||
|
fmt.Println(x) // 100
|
||||||
|
|
||||||
|
// Указатель на указатель:
|
||||||
|
pp := &p // тип **int
|
||||||
|
fmt.Println(**pp) // 100`)
|
||||||
|
|
||||||
|
utils.Code(`// Нулевое значение указателя — nil:
|
||||||
|
var p *int
|
||||||
|
fmt.Println(p == nil) // true
|
||||||
|
// *p = 1 — паника! нельзя разыменовывать nil-указатель
|
||||||
|
|
||||||
|
// Сравнение: указатели равны, если указывают на один адрес:
|
||||||
|
a, b := 1, 1
|
||||||
|
pa, pb := &a, &b
|
||||||
|
fmt.Println(pa == pb) // false (разные переменные)
|
||||||
|
pa2 := &a
|
||||||
|
fmt.Println(pa == pa2) // true (один и тот же адрес)`)
|
||||||
|
|
||||||
|
utils.Note("Go — безопасный язык: нет арифметики указателей (без пакета unsafe).")
|
||||||
|
utils.Note("Указатель позволяет функции изменять переменную вызывающего кода.")
|
||||||
|
|
||||||
|
// ─── Функция new ──────────────────────────────────────────────
|
||||||
|
utils.Section("Функция new")
|
||||||
|
|
||||||
|
utils.Println("new(T) выделяет память для значения типа T, инициализирует нулевым значением,")
|
||||||
|
utils.Println("возвращает указатель *T.")
|
||||||
|
utils.Code(`p := new(int) // *int, *p == 0
|
||||||
|
fmt.Println(*p) // 0
|
||||||
|
*p = 7
|
||||||
|
fmt.Println(*p) // 7
|
||||||
|
|
||||||
|
// Эквивалентно:
|
||||||
|
var tmp int
|
||||||
|
p2 := &tmp
|
||||||
|
|
||||||
|
// new для структур:
|
||||||
|
type Point struct{ X, Y int }
|
||||||
|
pt := new(Point) // *Point, pt.X==0, pt.Y==0
|
||||||
|
pt.X = 10`)
|
||||||
|
|
||||||
|
utils.Note("new используется редко — чаще пишут &T{...} для структур или var x T; return &x.")
|
||||||
|
utils.Code(`// Эти три записи эквивалентны:
|
||||||
|
p1 := new(int)
|
||||||
|
|
||||||
|
var v int
|
||||||
|
p2 := &v
|
||||||
|
|
||||||
|
p3 := &struct{ int }{} // анонимная структура — другой случай`)
|
||||||
|
|
||||||
|
// ─── Время жизни переменных ───────────────────────────────────
|
||||||
|
utils.Section("Время жизни переменных")
|
||||||
|
|
||||||
|
utils.Println("Время жизни — период, в течение которого переменная существует в памяти.")
|
||||||
|
fmt.Println()
|
||||||
|
utils.Println(utils.ColorBold + "Переменные пакетного уровня:" + utils.ColorReset +
|
||||||
|
" живут всё время работы программы.")
|
||||||
|
utils.Println(utils.ColorBold + "Локальные переменные:" + utils.ColorReset +
|
||||||
|
" создаются при каждом выполнении объявления, удаляются когда становятся недостижимы.")
|
||||||
|
fmt.Println()
|
||||||
|
utils.Code(`// Escape analysis — компилятор решает, где хранить переменную:
|
||||||
|
// стек (быстро) или куча (heap, медленнее, но живёт дольше).
|
||||||
|
|
||||||
|
func newInt() *int {
|
||||||
|
v := 42 // v "убегает" на кучу (heap escape),
|
||||||
|
return &v // потому что возвращается её адрес.
|
||||||
|
}
|
||||||
|
|
||||||
|
func sum(a, b int) int {
|
||||||
|
result := a + b // result на стеке — не "убегает",
|
||||||
|
return result // возвращается значение, не адрес.
|
||||||
|
}`)
|
||||||
|
utils.Note("Не нужно думать о стеке/куче вручную — GC Go управляет памятью автоматически.")
|
||||||
|
utils.Note("go build -gcflags='-m' — показывает, какие переменные уходят на кучу.")
|
||||||
|
|
||||||
|
// ─── Присваивания ─────────────────────────────────────────────
|
||||||
|
utils.Section("Присваивания")
|
||||||
|
|
||||||
|
utils.Println(utils.ColorBold + "Простое присваивание:" + utils.ColorReset)
|
||||||
|
utils.Code(`x = 1
|
||||||
|
*p = true
|
||||||
|
person.name = "Алиса"
|
||||||
|
count[x] = count[x] * scale`)
|
||||||
|
|
||||||
|
utils.Println(utils.ColorBold + "Составные операторы присваивания:" + utils.ColorReset)
|
||||||
|
utils.Code(`x += 1 x -= 1 x *= 2 x /= 2 x %= 3
|
||||||
|
x &= y x |= y x ^= y x <<= 2 x >>= 1`)
|
||||||
|
|
||||||
|
utils.Println(utils.ColorBold + "Инкремент и декремент (только постфиксные, только как операторы):" + utils.ColorReset)
|
||||||
|
utils.Code(`x++ // x = x + 1
|
||||||
|
x-- // x = x - 1
|
||||||
|
|
||||||
|
// ВАЖНО: в Go нет префиксных ++/-- и нет выражений с ++/--:
|
||||||
|
// ++x — ошибка компиляции
|
||||||
|
// y = x++ — ошибка компиляции`)
|
||||||
|
|
||||||
|
utils.Println(utils.ColorBold + "Множественное присваивание (tuple assignment):" + utils.ColorReset)
|
||||||
|
utils.Code(`// Все правые части вычисляются ДО записи в левые:
|
||||||
|
x, y = y, x // обмен значениями
|
||||||
|
|
||||||
|
a, b, c := 1, 2, 3
|
||||||
|
a, b, c = b, c, a // сдвиг
|
||||||
|
|
||||||
|
// Возврат нескольких значений из функции:
|
||||||
|
quotient, remainder := divide(17, 5)
|
||||||
|
|
||||||
|
// Игнорирование значения через _:
|
||||||
|
_, err = fmt.Println("hi")`)
|
||||||
|
|
||||||
|
utils.Println(utils.ColorBold + "Присваиваемость (assignability):" + utils.ColorReset)
|
||||||
|
utils.Code(`// Присваивание допустимо, если:
|
||||||
|
// 1. Типы идентичны
|
||||||
|
// 2. Типы имеют одинаковую базу и хотя бы один — безымянный
|
||||||
|
// 3. T реализует интерфейс I
|
||||||
|
// 4. Присваивание nil совместимому типу (chan, func, interface, map, pointer, slice)
|
||||||
|
|
||||||
|
var i interface{} = "строка" // string → interface{}
|
||||||
|
var n int = 42
|
||||||
|
var f float64 = float64(n) // явное преобразование типов`)
|
||||||
|
|
||||||
|
// ─── Объявления типов ────────────────────────────────────────
|
||||||
|
utils.Section("Объявления типов")
|
||||||
|
|
||||||
|
utils.Println("type создаёт " + utils.ColorBold + "новый именованный тип" + utils.ColorReset + " с тем же базовым представлением.")
|
||||||
|
utils.Code(`type Celsius float64
|
||||||
|
type Fahrenheit float64
|
||||||
|
|
||||||
|
var boiling Celsius = 100
|
||||||
|
|
||||||
|
// Celsius и Fahrenheit — РАЗНЫЕ типы, хотя оба float64:
|
||||||
|
// boiling = Fahrenheit(212) — ошибка компиляции!
|
||||||
|
|
||||||
|
// Явное преобразование разрешено (одно базовое представление):
|
||||||
|
f := Fahrenheit(boiling*9/5 + 32)
|
||||||
|
fmt.Println(f) // 212`)
|
||||||
|
|
||||||
|
utils.Code(`// Методы можно добавлять только к именованным типам:
|
||||||
|
func (c Celsius) String() string {
|
||||||
|
return fmt.Sprintf("%.2f°C", float64(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(boiling) // 100.00°C (автоматически вызывается String())`)
|
||||||
|
|
||||||
|
utils.Code(`// type-алиас (псевдоним) — НЕ создаёт новый тип, это то же самое:
|
||||||
|
type Rune = int32 // rune — именно так определён в builtin
|
||||||
|
|
||||||
|
// Отличие: алиас и оригинал взаимозаменяемы без конверсии.
|
||||||
|
// Обычный type — нет.`)
|
||||||
|
|
||||||
|
utils.Note("type позволяет добавлять методы к базовым типам (int, string, slice и т.д.).")
|
||||||
|
utils.Note("Явное преобразование T(v) работает если оба типа имеют одно представление.")
|
||||||
|
|
||||||
|
// ─── Пакеты, файлы и импорт ──────────────────────────────────
|
||||||
|
utils.Section("Пакеты, файлы и импорт")
|
||||||
|
|
||||||
|
utils.Println("Пакет — единица инкапсуляции и переиспользования в Go.")
|
||||||
|
utils.Println("Все .go-файлы в одной директории — один пакет. Имя объявляется в первой строке.")
|
||||||
|
fmt.Println()
|
||||||
|
utils.Code(`// Каждый файл начинается с объявления пакета:
|
||||||
|
package main // исполняемая программа
|
||||||
|
package utils // библиотечный пакет
|
||||||
|
package http // пакет стандартной библиотеки`)
|
||||||
|
|
||||||
|
utils.Println(utils.ColorBold + "Импорт пакетов:" + utils.ColorReset)
|
||||||
|
utils.Code(`// Одиночный импорт:
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// Групповой импорт (предпочтительный стиль):
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/user/project/utils" // внешний пакет
|
||||||
|
)`)
|
||||||
|
|
||||||
|
utils.Println(utils.ColorBold + "Формы импорта:" + utils.ColorReset)
|
||||||
|
utils.Code(`import "fmt" // стандартный: fmt.Println(...)
|
||||||
|
import f "fmt" // псевдоним: f.Println(...)
|
||||||
|
import . "fmt" // слияние: Println(...) (не рекомендуется)
|
||||||
|
import _ "image/png" // только для side-эффектов (init)`)
|
||||||
|
|
||||||
|
utils.Note("Неиспользуемый импорт — ошибка компиляции. Используйте _ для подавления.")
|
||||||
|
utils.Note("Имя пакета и имя директории могут отличаться, но по соглашению совпадают.")
|
||||||
|
utils.Code(`// Путь импорта vs имя пакета:
|
||||||
|
import "math/rand" // путь: math/rand
|
||||||
|
rand.Intn(10) // имя пакета: rand
|
||||||
|
|
||||||
|
import "crypto/rand" // другой пакет, то же имя!
|
||||||
|
// Нужен алиас:
|
||||||
|
import (
|
||||||
|
mrand "math/rand"
|
||||||
|
crand "crypto/rand"
|
||||||
|
)`)
|
||||||
|
|
||||||
|
// ─── Инициализация пакетов ────────────────────────────────────
|
||||||
|
utils.Section("Инициализация пакетов")
|
||||||
|
|
||||||
|
utils.Println("Инициализация переменных пакетного уровня происходит в порядке объявления,")
|
||||||
|
utils.Println("с учётом зависимостей между ними.")
|
||||||
|
utils.Code(`// Порядок гарантирован: a, b, c
|
||||||
|
var a = 1
|
||||||
|
var b = a + 1 // 2
|
||||||
|
var c = b * 2 // 4
|
||||||
|
|
||||||
|
// Сложные случаи — через функцию init():
|
||||||
|
var lookup = buildLookup()
|
||||||
|
|
||||||
|
func buildLookup() map[string]int {
|
||||||
|
// ...
|
||||||
|
return m
|
||||||
|
}`)
|
||||||
|
|
||||||
|
utils.Println(utils.ColorBold + "Функция init():" + utils.ColorReset)
|
||||||
|
utils.Code(`// init — специальная функция инициализации пакета:
|
||||||
|
// - вызывается автоматически при запуске программы
|
||||||
|
// - нельзя вызвать явно
|
||||||
|
// - в одном файле может быть несколько init()
|
||||||
|
// - в одном пакете — несколько init() в разных файлах
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// подготовка состояния, регистрация, настройка
|
||||||
|
log.SetFlags(log.Lshortfile)
|
||||||
|
}`)
|
||||||
|
|
||||||
|
utils.Println(utils.ColorBold + "Порядок инициализации всей программы:" + utils.ColorReset)
|
||||||
|
utils.Code(`// 1. Инициализируются импортируемые пакеты (рекурсивно, от листьев к корню)
|
||||||
|
// 2. Переменные пакетного уровня в порядке объявления / зависимостей
|
||||||
|
// 3. Вызываются все функции init() данного пакета
|
||||||
|
// 4. ... переход к следующему пакету
|
||||||
|
// 5. Последним инициализируется пакет main
|
||||||
|
// 6. Вызывается main.main()
|
||||||
|
|
||||||
|
// import _ "pkg" — импортировать только ради init()
|
||||||
|
// Пример: регистрация драйвера БД:
|
||||||
|
import _ "github.com/lib/pq" // PostgreSQL driver`)
|
||||||
|
|
||||||
|
utils.Note("Порядок вызова init() между файлами одного пакета определяется алфавитным порядком имён файлов.")
|
||||||
|
|
||||||
|
// ─── Область видимости ───────────────────────────────────────
|
||||||
|
utils.Section("Область видимости (Scope)")
|
||||||
|
|
||||||
|
utils.Println("Область видимости — часть кода, в которой объявление действует.")
|
||||||
|
utils.Println("Это " + utils.ColorBold + "статическое" + utils.ColorReset + " свойство (на уровне компиляции), в отличие от времени жизни (runtime).")
|
||||||
|
fmt.Println()
|
||||||
|
utils.Code(`// Уровни области видимости (от широкого к узкому):
|
||||||
|
// 1. Вселенная (universe) — встроенные имена: int, len, nil...
|
||||||
|
// 2. Пакет — объявления на верхнем уровне файла
|
||||||
|
// 3. Файл — только import-пути
|
||||||
|
// 4. Локальный — блок { }, параметры функции, for, if, switch...
|
||||||
|
|
||||||
|
var x = "пакет" // область видимости — весь пакет
|
||||||
|
|
||||||
|
func f() {
|
||||||
|
fmt.Println(x) // "пакет"
|
||||||
|
x := "локальный" // новая переменная x (shadowing!)
|
||||||
|
fmt.Println(x) // "локальный"
|
||||||
|
{
|
||||||
|
x := "вложенный"
|
||||||
|
fmt.Println(x) // "вложенный"
|
||||||
|
}
|
||||||
|
fmt.Println(x) // "локальный"
|
||||||
|
}`)
|
||||||
|
|
||||||
|
utils.Println(utils.ColorBold + "Затенение (shadowing) в управляющих конструкциях:" + utils.ColorReset)
|
||||||
|
utils.Code(`// if-инициализатор:
|
||||||
|
if x := compute(); x > 0 {
|
||||||
|
// x видна только здесь...
|
||||||
|
} else {
|
||||||
|
// ...и здесь
|
||||||
|
}
|
||||||
|
// x здесь уже не видна
|
||||||
|
|
||||||
|
// Распространённая ошибка с := в if:
|
||||||
|
var err error
|
||||||
|
if err := doA(); err != nil { // новая err!
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// err (внешняя) всё ещё nil — doA не изменил её!
|
||||||
|
fmt.Println(err) // nil`)
|
||||||
|
|
||||||
|
utils.Println(utils.ColorBold + "Экспортируемые имена — публичный API пакета:" + utils.ColorReset)
|
||||||
|
utils.Code(`package geometry
|
||||||
|
|
||||||
|
// Экспортируется (заглавная буква):
|
||||||
|
type Point struct {
|
||||||
|
X, Y float64 // поля тоже экспортируются
|
||||||
|
}
|
||||||
|
|
||||||
|
func Distance(p, q Point) float64 { ... }
|
||||||
|
|
||||||
|
// НЕ экспортируется (строчная буква):
|
||||||
|
type circle struct { center Point; radius float64 }
|
||||||
|
func area(c circle) float64 { ... }`)
|
||||||
|
|
||||||
|
utils.Note("Экспортируемость — единственный механизм инкапсуляции в Go. Нет private/protected/public ключевых слов.")
|
||||||
|
utils.Note("Правило: экспортируй только то, что является частью публичного API. Всё остальное — строчными буквами.")
|
||||||
|
|
||||||
|
utils.Footer()
|
||||||
|
}
|
||||||
BIN
rubric
Executable file
BIN
rubric
Executable file
Binary file not shown.
76
utils/display.go
Normal file
76
utils/display.go
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Цвета для терминала
|
||||||
|
const (
|
||||||
|
ColorReset = "\033[0m"
|
||||||
|
ColorBold = "\033[1m"
|
||||||
|
ColorDim = "\033[2m"
|
||||||
|
ColorCyan = "\033[36m"
|
||||||
|
ColorGreen = "\033[32m"
|
||||||
|
ColorYellow = "\033[33m"
|
||||||
|
ColorBlue = "\033[34m"
|
||||||
|
ColorMagenta = "\033[35m"
|
||||||
|
ColorRed = "\033[31m"
|
||||||
|
ColorWhite = "\033[97m"
|
||||||
|
BgBlue = "\033[44m"
|
||||||
|
BgGray = "\033[100m"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Header(title string) {
|
||||||
|
width := 60
|
||||||
|
line := strings.Repeat("─", width)
|
||||||
|
padding := (width - len([]rune(title))) / 2
|
||||||
|
padStr := strings.Repeat(" ", padding)
|
||||||
|
fmt.Printf("\n%s%s┌%s┐%s\n", ColorBold, ColorCyan, line, ColorReset)
|
||||||
|
fmt.Printf("%s%s│%s%s%s%s%s│%s\n",
|
||||||
|
ColorBold, ColorCyan,
|
||||||
|
ColorReset, ColorBold, ColorWhite, padStr+title, ColorReset+ColorBold+ColorCyan,
|
||||||
|
ColorReset)
|
||||||
|
fmt.Printf("%s%s└%s┘%s\n\n", ColorBold, ColorCyan, line, ColorReset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Section(title string) {
|
||||||
|
fmt.Printf("\n%s%s▶ %s%s\n", ColorBold, ColorYellow, title, ColorReset)
|
||||||
|
fmt.Printf("%s%s%s\n", ColorDim, strings.Repeat("─", 50), ColorReset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Code(code string) {
|
||||||
|
lines := strings.Split(strings.TrimSpace(code), "\n")
|
||||||
|
fmt.Printf("%s%s ┌─ код ────────────────────────────────────%s\n", ColorDim, ColorBold, ColorReset)
|
||||||
|
for _, line := range lines {
|
||||||
|
fmt.Printf("%s%s │%s %s%s%s\n", ColorDim, ColorBold, ColorReset, ColorGreen, line, ColorReset)
|
||||||
|
}
|
||||||
|
fmt.Printf("%s%s └───────────────────────────────────────────%s\n", ColorDim, ColorBold, ColorReset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Command(cmd, description string) {
|
||||||
|
fmt.Printf(" %s%-40s%s %s%s%s\n",
|
||||||
|
ColorCyan+ColorBold, cmd, ColorReset,
|
||||||
|
ColorDim, description, ColorReset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Note(text string) {
|
||||||
|
fmt.Printf("\n %s%s💡 %s%s\n", ColorBold, ColorYellow, text, ColorReset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Info(label, value string) {
|
||||||
|
fmt.Printf(" %s%s%-20s%s %s\n", ColorBold, ColorMagenta, label+":", ColorReset, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Println(text string) {
|
||||||
|
fmt.Printf(" %s\n", text)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Separator() {
|
||||||
|
fmt.Printf("\n%s%s%s\n\n", ColorDim, strings.Repeat("═", 60), ColorReset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Footer() {
|
||||||
|
fmt.Printf("\n%s%s%s%s\n\n", ColorDim, ColorCyan,
|
||||||
|
strings.Repeat("─", 60), ColorReset)
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue