commit fcc336bf3fde9b99247a786333178c976694c6c8 Author: Magnus Root Date: Tue Mar 24 16:57:17 2026 +0300 Started diff --git a/README.md b/README.md new file mode 100644 index 0000000..07cfd40 --- /dev/null +++ b/README.md @@ -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`. \ No newline at end of file diff --git a/catalog/catalog.go b/catalog/catalog.go new file mode 100644 index 0000000..4d1c403 --- /dev/null +++ b/catalog/catalog.go @@ -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" +} diff --git a/catalog/gobook.go b/catalog/gobook.go new file mode 100644 index 0000000..5b583dc --- /dev/null +++ b/catalog/gobook.go @@ -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) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..142fc13 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module rubric + +go 1.21 diff --git a/main.go b/main.go new file mode 100644 index 0000000..266f0e1 --- /dev/null +++ b/main.go @@ -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) +} diff --git a/modules/gomodules/go_basics.go b/modules/gomodules/go_basics.go new file mode 100644 index 0000000..67745a8 --- /dev/null +++ b/modules/gomodules/go_basics.go @@ -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() +} diff --git a/modules/gomodules/go_registry.go b/modules/gomodules/go_registry.go new file mode 100644 index 0000000..a6c3e86 --- /dev/null +++ b/modules/gomodules/go_registry.go @@ -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() +} diff --git a/modules/gomodules/go_structures.go b/modules/gomodules/go_structures.go new file mode 100644 index 0000000..06190dc --- /dev/null +++ b/modules/gomodules/go_structures.go @@ -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() +} diff --git a/rubric b/rubric new file mode 100755 index 0000000..a8755e4 Binary files /dev/null and b/rubric differ diff --git a/utils/display.go b/utils/display.go new file mode 100644 index 0000000..a327c4c --- /dev/null +++ b/utils/display.go @@ -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) +}