diff --git a/catalog/devopsbook.go b/catalog/devopsbook.go index 56f7b2d..5936c1a 100644 --- a/catalog/devopsbook.go +++ b/catalog/devopsbook.go @@ -37,7 +37,9 @@ func runDevOpsBook(scanner *bufio.Scanner) { if n, err := strconv.Atoi(input); err == nil { if n >= 1 && n <= len(devopsmodules.All) { devopsmodules.All[n-1].Run() - waitDevOpsBack(scanner) + if waitDevOpsBack(scanner) { + return + } continue } fmt.Printf("\n %s%sНомер %d вне диапазона (1–%d)%s\n", @@ -46,7 +48,9 @@ func runDevOpsBook(scanner *bufio.Scanner) { } if m, ok := devopsmodules.Find(input); ok { m.Run() - waitDevOpsBack(scanner) + if waitDevOpsBack(scanner) { + return + } continue } fmt.Printf("\n %s%sНе понял '%s'. Номер, ключ раздела, b — назад.%s\n", @@ -72,28 +76,11 @@ func showDevOpsMenu() { utils.ColorBold, utils.ColorGreen, utils.ColorReset) } -type devopsBackSignal struct{} - -func waitDevOpsBack(scanner *bufio.Scanner) { +func waitDevOpsBack(scanner *bufio.Scanner) bool { fmt.Printf("\n %s%s[Enter — назад в меню DevOps, b — главное меню]%s ", utils.ColorDim, utils.ColorCyan, utils.ColorReset) if scanner.Scan() { - if IsBack(strings.TrimSpace(scanner.Text())) { - panic(devopsBackSignal{}) - } - } -} - -func init() { - orig := DevOpsBook.Run - DevOpsBook.Run = func(scanner *bufio.Scanner) { - defer func() { - if r := recover(); r != nil { - if _, ok := r.(devopsBackSignal); !ok { - panic(r) - } - } - }() - orig(scanner) + return IsBack(strings.TrimSpace(scanner.Text())) } + return false } diff --git a/catalog/gobook.go b/catalog/gobook.go index 5b583dc..8650938 100644 --- a/catalog/gobook.go +++ b/catalog/gobook.go @@ -9,7 +9,6 @@ import ( "strings" ) -// GoBook — запись справочника Go в главном меню var GoBook = Book{ Key: "go", Aliases: []string{"g", "golang"}, @@ -32,19 +31,18 @@ func runGoBook(scanner *bufio.Scanner) { if IsQuit(input) { fmt.Printf("\n %s%sДо свидания!%s\n\n", utils.ColorBold, utils.ColorCyan, utils.ColorReset) - // сигнализируем главному циклу о выходе через os.Exit - // чтобы не усложнять — просто выходим return } if IsBack(input) { - return // назад в главное меню + return } - // По номеру if n, err := strconv.Atoi(input); err == nil { if n >= 1 && n <= len(gomodules.All) { gomodules.All[n-1].Run() - waitBack(scanner) + if goWaitBack(scanner) { + return // b — выйти в главное меню + } continue } fmt.Printf("\n %s%sНомер %d вне диапазона (1–%d)%s\n", @@ -52,10 +50,11 @@ func runGoBook(scanner *bufio.Scanner) { continue } - // По ключу if m, ok := gomodules.Find(input); ok { m.Run() - waitBack(scanner) + if goWaitBack(scanner) { + return + } continue } @@ -64,6 +63,16 @@ func runGoBook(scanner *bufio.Scanner) { } } +// goWaitBack возвращает true если пользователь нажал b (выйти в главное меню) +func goWaitBack(scanner *bufio.Scanner) bool { + fmt.Printf("\n %s%s[Enter — назад в меню Go, b — главное меню]%s ", + utils.ColorDim, utils.ColorCyan, utils.ColorReset) + if scanner.Scan() { + return IsBack(strings.TrimSpace(scanner.Text())) + } + return false +} + func showGoMenu() { utils.Header("Справочник Go") @@ -89,34 +98,3 @@ func showGoMenu() { 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/catalog/infosecbook.go b/catalog/infosecbook.go index 6f453c0..88d470f 100644 --- a/catalog/infosecbook.go +++ b/catalog/infosecbook.go @@ -37,7 +37,9 @@ func runInfoSecBook(scanner *bufio.Scanner) { if n, err := strconv.Atoi(input); err == nil { if n >= 1 && n <= len(infosecmodules.All) { infosecmodules.All[n-1].Run() - waitInfoSecBack(scanner) + if waitInfoSecBack(scanner) { + return + } continue } fmt.Printf("\n %s%sНомер %d вне диапазона (1–%d)%s\n", @@ -46,7 +48,9 @@ func runInfoSecBook(scanner *bufio.Scanner) { } if m, ok := infosecmodules.Find(input); ok { m.Run() - waitInfoSecBack(scanner) + if waitInfoSecBack(scanner) { + return + } continue } fmt.Printf("\n %s%sНе понял '%s'. Номер, ключ раздела, b — назад.%s\n", @@ -72,28 +76,11 @@ func showInfoSecMenu() { utils.ColorBold, utils.ColorGreen, utils.ColorReset) } -type infosecBackSignal struct{} - -func waitInfoSecBack(scanner *bufio.Scanner) { +func waitInfoSecBack(scanner *bufio.Scanner) bool { fmt.Printf("\n %s%s[Enter — назад в меню InfoSec, b — главное меню]%s ", utils.ColorDim, utils.ColorCyan, utils.ColorReset) if scanner.Scan() { - if IsBack(strings.TrimSpace(scanner.Text())) { - panic(infosecBackSignal{}) - } - } -} - -func init() { - orig := InfoSecBook.Run - InfoSecBook.Run = func(scanner *bufio.Scanner) { - defer func() { - if r := recover(); r != nil { - if _, ok := r.(infosecBackSignal); !ok { - panic(r) - } - } - }() - orig(scanner) + return IsBack(strings.TrimSpace(scanner.Text())) } + return false } diff --git a/catalog/linuxbook.go b/catalog/linuxbook.go index e28ccec..822a12c 100644 --- a/catalog/linuxbook.go +++ b/catalog/linuxbook.go @@ -42,7 +42,9 @@ func runLinuxBook(scanner *bufio.Scanner) { if n, err := strconv.Atoi(input); err == nil { if n >= 1 && n <= len(linuxmodules.All) { linuxmodules.All[n-1].Run() - waitLinuxBack(scanner) + if waitLinuxBack(scanner) { + return + } continue } fmt.Printf("\n %s%sНомер %d вне диапазона (1–%d)%s\n", @@ -53,7 +55,9 @@ func runLinuxBook(scanner *bufio.Scanner) { // По ключу if m, ok := linuxmodules.Find(input); ok { m.Run() - waitLinuxBack(scanner) + if waitLinuxBack(scanner) { + return + } continue } @@ -88,28 +92,11 @@ func showLinuxMenu() { utils.ColorBold, utils.ColorGreen, utils.ColorReset) } -type linuxBackSignal struct{} - -func waitLinuxBack(scanner *bufio.Scanner) { +func waitLinuxBack(scanner *bufio.Scanner) bool { fmt.Printf("\n %s%s[Enter — назад в меню Linux, b — главное меню]%s ", utils.ColorDim, utils.ColorCyan, utils.ColorReset) if scanner.Scan() { - if IsBack(strings.TrimSpace(scanner.Text())) { - panic(linuxBackSignal{}) - } - } -} - -func init() { - orig := LinuxBook.Run - LinuxBook.Run = func(scanner *bufio.Scanner) { - defer func() { - if r := recover(); r != nil { - if _, ok := r.(linuxBackSignal); !ok { - panic(r) - } - } - }() - orig(scanner) + return IsBack(strings.TrimSpace(scanner.Text())) } + return false } diff --git a/modules/gomodules/go_composite.go b/modules/gomodules/go_composite.go new file mode 100644 index 0000000..d0817c6 --- /dev/null +++ b/modules/gomodules/go_composite.go @@ -0,0 +1,617 @@ +package gomodules + +import ( + "fmt" + "rubric/utils" +) + +var CompositeModule = Module{ + Key: "composite", + Aliases: []string{"c", "comp", "slice", "map", "struct"}, + Title: "Составные типы", + Description: "Массивы, срезы, append, отображения, структуры, JSON и HTML", + Run: showComposite, +} + +func showComposite() { + utils.Header("Go — Составные типы") + + // ─── Массивы ────────────────────────────────────────────────── + utils.Section("Массивы (Arrays)") + + utils.Println("Массив — фиксированной длины последовательность элементов одного типа.") + utils.Println("Длина является частью типа: " + + utils.ColorBold + utils.ColorCyan + "[3]int" + utils.ColorReset + + " и " + + utils.ColorBold + utils.ColorCyan + "[4]int" + utils.ColorReset + + " — разные типы.") + fmt.Println() + utils.Code(`// Объявление: +var a [3]int // [0 0 0] — нулевые значения +var b [3]int = [3]int{1, 2, 3} +c := [3]int{1, 2, 3} +d := [...]int{1, 2, 3, 4, 5} // компилятор считает длину сам + +// Инициализация с индексами: +e := [5]string{1: "один", 3: "три"} // остальные — "" + +// Доступ: +fmt.Println(c[0]) // 1 +fmt.Println(len(c)) // 3 + +// Итерация: +for i, v := range c { + fmt.Printf("c[%d] = %d\n", i, v) +}`) + + utils.Println(utils.ColorBold + "Сравнение массивов:" + utils.ColorReset) + utils.Code(`// Массивы можно сравнивать через == и != +// (если элементы сравниваемы): +a := [3]int{1, 2, 3} +b := [3]int{1, 2, 3} +fmt.Println(a == b) // true + +// Передача в функцию — КОПИЯ: +func double(arr [3]int) { arr[0] *= 2 } // оригинал не меняется +// Чтобы изменить — передавать указатель: *[3]int`) + + utils.Note("Массивы в Go используются редко — чаще работают со срезами.") + + // ─── Срезы ──────────────────────────────────────────────────── + utils.Section("Срезы (Slices)") + + utils.Println("Срез — динамический вид на массив. Три компонента:") + utils.Code(`// Указатель на массив + длина (len) + ёмкость (cap) +// +// ┌─────────────────────────────────────────┐ +// │ ptr │ len │ cap │ +// └───┬───┴───────┴─────────────────────────┘ +// │ +// ▼ +// ┌───┬───┬───┬───┬───┐ +// │ 0 │ 1 │ 2 │ 3 │ 4 │ ← underlying array +// └───┴───┴───┴───┴───┘`) + + utils.Println(utils.ColorBold + "Создание срезов:" + utils.ColorReset) + utils.Code(`// Из литерала (неявно создаёт массив): +s := []int{1, 2, 3, 4, 5} + +// Срез нулевых значений через make(тип, len, cap): +s := make([]int, 5) // len=5, cap=5 +s := make([]int, 3, 10) // len=3, cap=10 + +// Нулевой срез (nil slice): +var s []int // s == nil, len=0, cap=0 +fmt.Println(s == nil) // true +// nil срез безопасен для append и range + +// Срез из массива: +a := [5]int{1, 2, 3, 4, 5} +s := a[1:4] // элементы 1,2,3 → [2 3 4] + // len=3, cap=4 (до конца массива)`) + + utils.Println(utils.ColorBold + "Операции срезов:" + utils.ColorReset) + utils.Code(`s := []int{1, 2, 3, 4, 5} + +// Взятие среза: s[low:high] (low включён, high — нет) +s[1:3] // [2 3] len=2 +s[:3] // [1 2 3] low=0 +s[2:] // [3 4 5] high=len +s[:] // копия ссылки на весь срез + +// Трёхиндексный срез: s[low:high:max] — ограничивает cap: +s[1:3:4] // len=2, cap=3 + +// len и cap: +fmt.Println(len(s), cap(s)) // 5 5 + +// Срезы разделяют память с оригиналом! +a := []int{1, 2, 3, 4, 5} +b := a[1:3] // b = [2 3] +b[0] = 99 +fmt.Println(a) // [1 99 3 4 5] — оригинал изменён!`) + + utils.Println(utils.ColorBold + "Копирование срезов (copy):" + utils.ColorReset) + utils.Code(`src := []int{1, 2, 3, 4, 5} +dst := make([]int, 3) +n := copy(dst, src) // копирует min(len(dst), len(src)) элементов +fmt.Println(dst, n) // [1 2 3] 3 + +// Полная независимая копия: +clone := make([]int, len(src)) +copy(clone, src) + +// Или через append: +clone := append([]int{}, src...)`) + + utils.Println(utils.ColorBold + "Итерация:" + utils.ColorReset) + utils.Code(`s := []string{"a", "b", "c"} + +for i, v := range s { // v — копия элемента + fmt.Println(i, v) +} + +for i := range s { // только индексы + s[i] = strings.ToUpper(s[i]) +} + +for _, v := range s { // только значения + fmt.Println(v) +}`) + + // ─── append ─────────────────────────────────────────────────── + utils.Section("Функция append") + + utils.Println("append добавляет элементы в срез, возвращая новый (возможно, с новым массивом).") + utils.Code(`// Добавить элементы: +s := []int{1, 2, 3} +s = append(s, 4) // [1 2 3 4] +s = append(s, 5, 6, 7) // [1 2 3 4 5 6 7] + +// Добавить другой срез (оператор ...): +other := []int{8, 9} +s = append(s, other...) // [1 2 3 4 5 6 7 8 9] + +// Удалить элемент по индексу i: +i := 2 +s = append(s[:i], s[i+1:]...) // порядок сохраняется +// Быстрое удаление (порядок не важен): +s[i] = s[len(s)-1] +s = s[:len(s)-1] + +// Вставить элемент по индексу i: +s = append(s[:i+1], s[i:]...) +s[i] = newValue + +// Стек (stack): +push := func(s []int, v int) []int { return append(s, v) } +pop := func(s []int) ([]int, int) { return s[:len(s)-1], s[len(s)-1] }`) + + utils.Println(utils.ColorBold + "Механизм роста ёмкости:" + utils.ColorReset) + utils.Code(`// При нехватке cap, append выделяет новый массив (~удваивает cap): +var s []int +for i := 0; i < 8; i++ { + s = append(s, i) + fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s) +} +// len=1 cap=1 [0] +// len=2 cap=2 [0 1] +// len=3 cap=4 [0 1 2] +// len=4 cap=4 [0 1 2 3] +// len=5 cap=8 [0 1 2 3 4] +// ...`) + + utils.Note("Всегда присваивай результат append обратно: s = append(s, v)") + utils.Note("Передавай срез с запасом через make([]T, 0, n) если знаешь итоговый размер — избежишь лишних аллокаций.") + + // ─── Отображения ────────────────────────────────────────────── + utils.Section("Отображения (Maps)") + + utils.Println("Map — ссылочный тип, хэш-таблица. Порядок итерации не гарантирован.") + fmt.Println() + utils.Println(utils.ColorBold + "Создание:" + utils.ColorReset) + utils.Code(`// Через литерал: +ages := map[string]int{ + "Alice": 30, + "Bob": 25, +} + +// Через make: +ages := make(map[string]int) +ages := make(map[string]int, 100) // с подсказкой ёмкости + +// Нулевое значение — nil: +var m map[string]int // m == nil +// m["key"] = 1 // паника! нельзя писать в nil map +// fmt.Println(m["key"]) // 0 — читать можно, не паникует`) + + utils.Println(utils.ColorBold + "Операции с map:" + utils.ColorReset) + utils.Code(`ages := map[string]int{"Alice": 30, "Bob": 25} + +// Запись: +ages["Carol"] = 28 +ages["Alice"] = 31 // обновить + +// Чтение: +age := ages["Alice"] // 31 +age = ages["unknown"] // 0 — нулевое значение, НЕ ошибка + +// Проверка наличия ключа (comma ok idiom): +age, ok := ages["Alice"] +if ok { + fmt.Println("Alice:", age) +} +// Коротко: +if age, ok := ages["Alice"]; ok { + fmt.Println("Alice:", age) +} + +// Удаление: +delete(ages, "Bob") +delete(ages, "nonexistent") // безопасно, нет ошибки + +// Длина: +fmt.Println(len(ages)) // 2 + +// Итерация (порядок случаен!): +for name, age := range ages { + fmt.Printf("%s: %d\n", name, age) +} + +// Итерация по отсортированным ключам: +keys := make([]string, 0, len(ages)) +for k := range ages { keys = append(keys, k) } +sort.Strings(keys) +for _, k := range keys { fmt.Println(k, ages[k]) }`) + + utils.Println(utils.ColorBold + "Сравнение map:" + utils.ColorReset) + utils.Code(`// Map нельзя сравнивать через == (только с nil): +var m map[string]int +fmt.Println(m == nil) // true + +// Сравнение содержимого — через reflect.DeepEqual или вручную: +import "reflect" +fmt.Println(reflect.DeepEqual(map1, map2)) + +// Или maps.Equal из Go 1.21: +import "maps" +fmt.Println(maps.Equal(map1, map2))`) + + utils.Println(utils.ColorBold + "Типичные паттерны:" + utils.ColorReset) + utils.Code(`// Множество (set): +seen := make(map[string]bool) +seen["Alice"] = true +if seen["Alice"] { ... } + +// Подсчёт слов: +words := strings.Fields(text) +count := make(map[string]int) +for _, w := range words { count[w]++ } + +// Группировка: +byDept := make(map[string][]Employee) +for _, e := range employees { + byDept[e.Dept] = append(byDept[e.Dept], e) +}`) + + // ─── Структуры ──────────────────────────────────────────────── + utils.Section("Структуры (Structs)") + + utils.Println("Struct — агрегатный тип, объединяет поля разных типов.") + utils.Code(`// Объявление типа: +type Point struct { + X, Y float64 +} + +type Employee struct { + ID int + Name string + Salary float64 + Manager *Employee // рекурсивный тип — только указатель +}`) + + utils.Println(utils.ColorBold + "Создание и доступ:" + utils.ColorReset) + utils.Code(`// Структурный литерал (по именам — рекомендуется): +p := Point{X: 1.0, Y: 2.0} +p := Point{1.0, 2.0} // по позиции (хрупко, не для публичных типов) + +// Нулевое значение: +var p Point // Point{X:0, Y:0} + +// Доступ к полям: +fmt.Println(p.X) // 1.0 +p.Y = 5.0 + +// Указатель на структуру: +pp := &Point{1.0, 2.0} +pp.X = 10 // (*pp).X = 10 — автодеференс +fmt.Println(pp.X) // 10 + +// Через new: +pp := new(Point) // *Point, поля = нули +pp.X = 1.0`) + + utils.Println(utils.ColorBold + "Сравнение структур:" + utils.ColorReset) + utils.Code(`// Структуры сравниваемы если все поля сравниваемы: +type Point struct{ X, Y int } +p1 := Point{1, 2} +p2 := Point{1, 2} +fmt.Println(p1 == p2) // true + +// Поля-срезы, map, func — делают структуру несравниваемой. +// Используй reflect.DeepEqual для глубокого сравнения. + +// Структура как ключ map (только если сравниваема): +type Coord struct{ Lat, Lon float64 } +visited := map[Coord]bool{} +visited[Coord{55.75, 37.62}] = true`) + + // ─── Встраивание и анонимные поля ──────────────────────────── + utils.Section("Встраивание структур и анонимные поля") + + utils.Println("Встраивание (embedding) — включение типа без имени поля.") + utils.Println("Поля и методы встроенного типа «поднимаются» во внешний тип.") + utils.Code(`type Animal struct { + Name string +} + +func (a Animal) Speak() string { + return a.Name + " говорит" +} + +type Dog struct { + Animal // анонимное поле — встраивание + Breed string +} + +d := Dog{ + Animal: Animal{Name: "Шарик"}, + Breed: "Лабрадор", +} + +// Продвинутый доступ (promotion): +fmt.Println(d.Name) // "Шарик" (d.Animal.Name) +fmt.Println(d.Speak()) // "Шарик говорит" (d.Animal.Speak()) +fmt.Println(d.Breed) // "Лабрадор" +// Явный доступ: +fmt.Println(d.Animal.Name)`) + + utils.Code(`// Несколько встраиваний: +type Logger struct{ Prefix string } +func (l Logger) Log(msg string) { fmt.Println(l.Prefix, msg) } + +type Server struct { + Logger // анонимное поле + net.Listener // встроен интерфейс + Host string +} + +s := Server{Logger: Logger{Prefix: "[server]"}, Host: "localhost"} +s.Log("started") // s.Logger.Log("started") + +// Конфликт имён — нужно явно указывать путь: +type A struct{ X int } +type B struct{ X int } +type C struct { A; B } +c := C{} +// c.X — ошибка компиляции: ambiguous +c.A.X = 1 // явно`) + + utils.Code(`// Встраивание указателя: +type Cache struct { + *sync.Mutex // анонимный указатель + data map[string]string +} + +c := Cache{ + Mutex: &sync.Mutex{}, + data: make(map[string]string), +} +c.Lock() // c.Mutex.Lock() +c.data["key"] = "value" +c.Unlock()`) + + utils.Note("Встраивание — это композиция, не наследование. Go не имеет наследования.") + + // ─── Работа с JSON ──────────────────────────────────────────── + utils.Section("Работа с JSON") + + utils.Println("Пакет " + utils.ColorBold + utils.ColorCyan + "encoding/json" + utils.ColorReset + + " — маршалинг и демаршалинг JSON.") + fmt.Println() + utils.Println(utils.ColorBold + "Теги структур:" + utils.ColorReset) + utils.Code(`type Person struct { + Name string ` + "`" + `json:"name"` + "`" + ` + Age int ` + "`" + `json:"age"` + "`" + ` + Email string ` + "`" + `json:"email,omitempty"` + "`" + ` // пропустить если пусто + Phone string ` + "`" + `json:"-"` + "`" + ` // никогда не включать + private string // неэкспортируемые — игнорируются +} + +// Теги: +// json:"name" — имя в JSON +// json:"name,omitempty"— пропустить нулевые значения +// json:"-" — исключить поле`) + + utils.Println(utils.ColorBold + "Маршалинг (структура → JSON):" + utils.ColorReset) + utils.Code(`import "encoding/json" + +p := Person{Name: "Alice", Age: 30, Email: "alice@example.com"} + +// Сериализация: +data, err := json.Marshal(p) +if err != nil { log.Fatal(err) } +fmt.Println(string(data)) +// {"name":"Alice","age":30,"email":"alice@example.com"} + +// Красивый вывод с отступами: +data, err = json.MarshalIndent(p, "", " ") +fmt.Println(string(data)) +// { +// "name": "Alice", +// "age": 30, +// "email": "alice@example.com" +// }`) + + utils.Println(utils.ColorBold + "Демаршалинг (JSON → структура):" + utils.ColorReset) + utils.Code(`jsonStr := ` + "`" + `{"name":"Bob","age":25}` + "`" + ` + +var p Person +if err := json.Unmarshal([]byte(jsonStr), &p); err != nil { + log.Fatal(err) +} +fmt.Println(p.Name, p.Age) // Bob 25 + +// Потоковое чтение из http.Response: +resp, _ := http.Get("https://api.example.com/users") +defer resp.Body.Close() +var users []Person +json.NewDecoder(resp.Body).Decode(&users) + +// Потоковая запись в http.ResponseWriter: +json.NewEncoder(w).Encode(users)`) + + utils.Println(utils.ColorBold + "Произвольный JSON (interface{} / any):" + utils.ColorReset) + utils.Code(`// Когда структура неизвестна: +var result map[string]any +json.Unmarshal(data, &result) + +name := result["name"].(string) // type assertion +age := result["age"].(float64) // числа → float64 по умолчанию + +// Проверка типа: +if s, ok := result["name"].(string); ok { + fmt.Println(s) +} + +// json.RawMessage — отложенный демаршалинг: +type Response struct { + Status string ` + "`" + `json:"status"` + "`" + ` + Payload json.RawMessage ` + "`" + `json:"payload"` + "`" + ` +} +var resp Response +json.Unmarshal(data, &resp) +var user Person +json.Unmarshal(resp.Payload, &user)`) + + utils.Println(utils.ColorBold + "Кастомные маршалеры:" + utils.ColorReset) + utils.Code(`// Реализовать json.Marshaler / json.Unmarshaler: +type Duration time.Duration + +func (d Duration) MarshalJSON() ([]byte, error) { + return json.Marshal(time.Duration(d).String()) +} + +func (d *Duration) UnmarshalJSON(b []byte) error { + var s string + if err := json.Unmarshal(b, &s); err != nil { return err } + dur, err := time.ParseDuration(s) + *d = Duration(dur) + return err +}`) + + // ─── Работа с HTML ──────────────────────────────────────────── + utils.Section("Работа с HTML") + + utils.Println("Пакет " + utils.ColorBold + utils.ColorCyan + "html/template" + utils.ColorReset + + " — безопасные HTML-шаблоны с автоматическим экранированием.") + fmt.Println() + utils.Code(`import "html/template" + +// Шаблон: +const tmpl = ` + "`" + ` + + + +

Привет, {{.Name}}!

+ + {{if .Admin}} +

Вы администратор

+ {{end}} + +` + "`" + ` + +type Data struct { + Name string + Items []string + Admin bool +} + +// Парсинг и выполнение: +t := template.Must(template.New("page").Parse(tmpl)) + +data := Data{ + Name: "Alice", + Items: []string{"Go", "Docker", "Kubernetes"}, + Admin: true, +} + +// В os.Stdout: +t.Execute(os.Stdout, data) + +// В http.ResponseWriter: +func handler(w http.ResponseWriter, r *http.Request) { + t.Execute(w, data) +}`) + + utils.Println(utils.ColorBold + "Экранирование и безопасность:" + utils.ColorReset) + utils.Code(`// html/template автоматически экранирует пользовательский ввод: +// → <script>alert(1)</script> + +// Если нужно вставить безопасный HTML — использовать template.HTML: +type Page struct { + Title string + Content template.HTML // не экранируется +} + +// Никогда не делай: +// template.HTML(userInput) — XSS! + +// text/template — без экранирования (для не-HTML): +import "text/template" +// Используй для генерации кода, конфигов, emails в plain text`) + + utils.Println(utils.ColorBold + "Шаблонные функции и Actions:" + utils.ColorReset) + utils.Code(`// Функции в шаблоне: +funcMap := template.FuncMap{ + "upper": strings.ToUpper, + "add": func(a, b int) int { return a + b }, +} +t := template.New("t").Funcs(funcMap).Parse(` + "`" + ` +{{.Name | upper}} +{{add 2 3}} +` + "`" + `) + +// Actions (действия): +{{/* комментарий */}} +{{.Field}} // вывести поле +{{.Method arg}} // вызов метода +{{if .Cond}} ... {{end}} +{{if .Cond}} ... {{else}} ... {{end}} +{{range .Slice}} {{.}} {{end}} +{{range .Map}} {{.}} {{end}} +{{with .Val}} {{.}} {{end}} // установить . = Val если не ноль +{{template "name" .}} // вложенный шаблон + +// Шаблоны из файлов: +t := template.Must(template.ParseFiles("index.html", "base.html")) +t := template.Must(template.ParseGlob("templates/*.html"))`) + + utils.Println(utils.ColorBold + "Парсинг HTML (html пакет):" + utils.ColorReset) + utils.Code(`import "html" + +// Экранирование и разэкранирование HTML-сущностей: +escaped := html.EscapeString("") +// <script>alert('xss')</script> + +unescaped := html.UnescapeString("<b>жирный</b>") +// жирный + +// Для разбора HTML-дерева — golang.org/x/net/html: +import "golang.org/x/net/html" + +resp, _ := http.Get("https://example.com") +doc, _ := html.Parse(resp.Body) + +// Рекурсивный обход: +var walk func(*html.Node) +walk = func(n *html.Node) { + if n.Type == html.ElementNode && n.Data == "a" { + for _, attr := range n.Attr { + if attr.Key == "href" { + fmt.Println(attr.Val) + } + } + } + for c := n.FirstChild; c != nil; c = c.NextSibling { + walk(c) + } +} +walk(doc)`) + + utils.Footer() +} diff --git a/modules/gomodules/go_registry.go b/modules/gomodules/go_registry.go index 63c7c61..ee334dc 100644 --- a/modules/gomodules/go_registry.go +++ b/modules/gomodules/go_registry.go @@ -19,6 +19,7 @@ type Module struct { var All = []Module{ BasicsModule, StructuresModule, + CompositeModule, // сюда будут добавляться новые модули } @@ -109,6 +110,13 @@ func Keywords(key string) []string { "компиляция", "кросс-компиляция", "GOOS", "GOARCH", "golangci-lint", "бенчмарк", "benchmark", "покрытие", "coverage", "go env", "GOPATH", }, + "composite": { + "массивы", "arrays", "срезы", "slices", "append", "make", "copy", + "отображения", "map", "maps", "структуры", "structs", "встраивание", + "embedding", "анонимные поля", "JSON", "html/template", "шаблоны", + "маршалинг", "marshal", "unmarshal", "json.Encoder", "json.Decoder", + "omitempty", "range", "len", "cap", "delete", "composite types", + }, "structs": { "переменные", "variables", "объявления", "declarations", "типы", "types", "указатели", "pointers", "new", "область видимости", "scope", diff --git a/rubric b/rubric index 5020b5c..7bbdf8e 100755 Binary files a/rubric and b/rubric differ