Added go composite

This commit is contained in:
Magnus Root 2026-04-13 17:25:34 +03:00
parent 0d3dae172a
commit 1a8c82d6db
7 changed files with 669 additions and 105 deletions

View file

@ -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
}

View file

@ -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)
}
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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 = ` + "`" + `
<!DOCTYPE html>
<html>
<body>
<h1>Привет, {{.Name}}!</h1>
<ul>
{{range .Items}}
<li>{{.}}</li>
{{end}}
</ul>
{{if .Admin}}
<p>Вы администратор</p>
{{end}}
</body>
</html>` + "`" + `
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> → &lt;script&gt;alert(1)&lt;/script&gt;
// Если нужно вставить безопасный 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>")
// &lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt;
unescaped := html.UnescapeString("&lt;b&gt;жирный&lt;/b&gt;")
// <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()
}

View file

@ -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",

BIN
rubric

Binary file not shown.