223 lines
7 KiB
Go
Executable file
223 lines
7 KiB
Go
Executable file
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,
|
||
DevOpsBook,
|
||
InfoSecBook,
|
||
}
|
||
|
||
// 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) {
|
||
RunInteractiveWithSearch(scanner)
|
||
}
|
||
|
||
// RunInteractiveWithSearch — главный цикл с поддержкой поиска
|
||
func RunInteractiveWithSearch(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
|
||
}
|
||
|
||
// Поиск: /ansible или search ansible
|
||
query := ""
|
||
if strings.HasPrefix(input, "/") {
|
||
query = strings.TrimSpace(input[1:])
|
||
} else if strings.HasPrefix(strings.ToLower(input), "search ") {
|
||
query = strings.TrimSpace(input[7:])
|
||
} else if strings.ToLower(input) == "search" {
|
||
fmt.Printf("\n %s%sВведите поисковый запрос:%s ", utils.ColorBold, utils.ColorGreen, utils.ColorReset)
|
||
if scanner.Scan() {
|
||
query = strings.TrimSpace(scanner.Text())
|
||
}
|
||
}
|
||
|
||
if query != "" {
|
||
results := Search(query)
|
||
ShowSearchResults(query, results)
|
||
if len(results) > 0 {
|
||
fmt.Printf(" %s%sВведите номер раздела чтобы открыть (Enter — назад в меню):%s ",
|
||
utils.ColorBold, utils.ColorGreen, utils.ColorReset)
|
||
if scanner.Scan() {
|
||
choice := strings.TrimSpace(scanner.Text())
|
||
if n, err := strconv.Atoi(choice); err == nil && n >= 1 && n <= len(results) {
|
||
fmt.Println()
|
||
results[n-1].RunFunc()
|
||
fmt.Printf("\n %s%s[Enter — вернуться в меню]%s ", utils.ColorDim, utils.ColorCyan, utils.ColorReset)
|
||
scanner.Scan()
|
||
}
|
||
}
|
||
}
|
||
continue
|
||
}
|
||
|
||
// По номеру
|
||
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 -search ansible", "поиск по всем справочникам")
|
||
utils.Command("./rubric -search 'sql injection'", "поиск по нескольким словам")
|
||
utils.Command("./rubric -list", "список всех справочников и разделов")
|
||
utils.Command("./rubric -help", "эта справка")
|
||
fmt.Println()
|
||
|
||
utils.Section("Поиск в интерактивном режиме")
|
||
utils.Command("/ansible", "поиск по запросу (начать с /)")
|
||
utils.Command("search nmap", "поиск командой search")
|
||
utils.Command("search", "поиск (запросит строку)")
|
||
fmt.Println()
|
||
|
||
utils.Section("Доступные справочники")
|
||
for _, b := range All {
|
||
keys := append([]string{b.Key}, b.Aliases...)
|
||
fmt.Printf(" %s%s%-8s%s %-34s %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"
|
||
}
|