This commit is contained in:
Magnus Root 2026-03-24 16:57:17 +03:00
commit fcc336bf3f
10 changed files with 1413 additions and 0 deletions

221
README.md Normal file
View 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
View 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
View 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
View file

@ -0,0 +1,3 @@
module rubric
go 1.21

68
main.go Normal file
View 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)
}

View 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()
}

View 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()
}

View 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

Binary file not shown.

76
utils/display.go Normal file
View 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)
}