170 lines
5.4 KiB
Go
170 lines
5.4 KiB
Go
package main
|
||
|
||
import (
|
||
"bufio"
|
||
"fmt"
|
||
"os"
|
||
"strconv"
|
||
"strings"
|
||
)
|
||
|
||
// ─── ANSI цвета ───────────────────────────────────────────────────────────────
|
||
|
||
const (
|
||
reset = "\033[0m"
|
||
bold = "\033[1m"
|
||
dim = "\033[2m"
|
||
red = "\033[31m"
|
||
green = "\033[32m"
|
||
yellow = "\033[33m"
|
||
blue = "\033[34m"
|
||
magenta = "\033[35m"
|
||
cyan = "\033[36m"
|
||
white = "\033[37m"
|
||
)
|
||
|
||
func clr(color, text string) string { return color + text + reset }
|
||
func bld(text string) string { return bold + text + reset }
|
||
|
||
// ─── Ввод ─────────────────────────────────────────────────────────────────────
|
||
|
||
var reader = bufio.NewReader(os.Stdin)
|
||
|
||
func readLine() string {
|
||
line, _ := reader.ReadString('\n')
|
||
return strings.TrimSpace(line)
|
||
}
|
||
|
||
// ask выводит подсказку и читает строку. Пустая строка → "_не указано_"
|
||
func ask(prompt string) string {
|
||
fmt.Printf(" %s %s ", clr(cyan, "→"), prompt)
|
||
val := readLine()
|
||
if val == "" {
|
||
return "_не указано_"
|
||
}
|
||
return val
|
||
}
|
||
|
||
// askDefault выводит подсказку с дефолтным значением.
|
||
func askDefault(prompt, defaultVal string) string {
|
||
fmt.Printf(" %s %s %s ", clr(cyan, "→"), prompt, clr(dim, "(Enter = "+defaultVal+")"))
|
||
val := readLine()
|
||
if val == "" {
|
||
return defaultVal
|
||
}
|
||
return val
|
||
}
|
||
|
||
// choose выводит нумерованное меню и возвращает выбранный вариант.
|
||
func choose(prompt string, options []string) string {
|
||
fmt.Printf("\n %s %s\n", clr(magenta, "▸"), bld(prompt))
|
||
for i, opt := range options {
|
||
fmt.Printf(" %s %s\n", clr(yellow, fmt.Sprintf("[%d]", i+1)), opt)
|
||
}
|
||
for {
|
||
fmt.Printf(" %s Выберите (1-%d): ", clr(cyan, "→"), len(options))
|
||
input := readLine()
|
||
n, err := strconv.Atoi(input)
|
||
if err == nil && n >= 1 && n <= len(options) {
|
||
fmt.Printf(" %s %s\n", clr(green, "✔"), options[n-1])
|
||
return options[n-1]
|
||
}
|
||
fmt.Printf(" %s Введите число от 1 до %d\n", clr(red, "✘"), len(options))
|
||
}
|
||
}
|
||
|
||
// section печатает визуальный разделитель с заголовком.
|
||
func section(title string) {
|
||
fmt.Printf("\n%s\n%s\n%s\n",
|
||
clr(blue, strings.Repeat("─", 60)),
|
||
clr(bold+blue, " "+title),
|
||
clr(blue, strings.Repeat("─", 60)),
|
||
)
|
||
}
|
||
|
||
// ─── Коллекторы списков ───────────────────────────────────────────────────────
|
||
|
||
func collectList(prompt, itemLabel string) []string {
|
||
fmt.Printf("\n %s %s %s\n", clr(magenta, "▸"), bld(prompt), clr(dim, "(пустая строка = завершить)"))
|
||
var items []string
|
||
for i := 1; ; i++ {
|
||
fmt.Printf(" %s %s %d: ", clr(cyan, "→"), itemLabel, i)
|
||
val := readLine()
|
||
if val == "" {
|
||
break
|
||
}
|
||
items = append(items, val)
|
||
}
|
||
if len(items) == 0 {
|
||
return []string{"_не указано_"}
|
||
}
|
||
return items
|
||
}
|
||
|
||
func collectTimeline() []TimelineEvent {
|
||
fmt.Printf("\n %s %s %s\n", clr(magenta, "▸"), bld("Хронология событий"), clr(dim, "(пустое время = завершить)"))
|
||
var events []TimelineEvent
|
||
for i := 1; ; i++ {
|
||
fmt.Printf(" %s Событие %d — время (напр. 14:32): ", clr(cyan, "→"), i)
|
||
t := readLine()
|
||
if t == "" {
|
||
break
|
||
}
|
||
fmt.Printf(" %s Событие %d — описание: ", clr(cyan, "→"), i)
|
||
desc := readLine()
|
||
if desc == "" {
|
||
desc = "—"
|
||
}
|
||
events = append(events, TimelineEvent{Time: t, Event: desc})
|
||
}
|
||
if len(events) == 0 {
|
||
events = append(events, TimelineEvent{Time: "—", Event: "_не указано_"})
|
||
}
|
||
return events
|
||
}
|
||
|
||
func collectWhyAnalysis() []WhyItem {
|
||
fmt.Printf("\n %s %s %s\n", clr(magenta, "▸"), bld("Анализ «5 Почему»"), clr(dim, "(пустой ввод = завершить, макс. 5)"))
|
||
var items []WhyItem
|
||
for i := 1; i <= 5; i++ {
|
||
fmt.Printf(" %s Почему #%d: ", clr(cyan, "→"), i)
|
||
why := readLine()
|
||
if why == "" {
|
||
break
|
||
}
|
||
fmt.Printf(" %s Ответ #%d: ", clr(cyan, "→"), i)
|
||
answer := readLine()
|
||
items = append(items, WhyItem{Why: why, Answer: answer})
|
||
}
|
||
if len(items) == 0 {
|
||
items = append(items, WhyItem{Why: "_не указано_", Answer: "—"})
|
||
}
|
||
return items
|
||
}
|
||
|
||
func collectActionItems() []ActionItem {
|
||
fmt.Printf("\n %s %s %s\n", clr(magenta, "▸"), bld("Action Items"), clr(dim, "(пустая задача = завершить)"))
|
||
var items []ActionItem
|
||
for i := 1; ; i++ {
|
||
fmt.Printf("\n %s Задача %d (Enter = завершить): ", clr(cyan, "→"), i)
|
||
task := readLine()
|
||
if task == "" {
|
||
break
|
||
}
|
||
fmt.Printf(" %s Ответственный (@имя): ", clr(cyan, "→"))
|
||
assignee := readLine()
|
||
if assignee == "" {
|
||
assignee = "@tbd"
|
||
}
|
||
fmt.Printf(" %s Дедлайн (ДД.ММ): ", clr(cyan, "→"))
|
||
deadline := readLine()
|
||
if deadline == "" {
|
||
deadline = "—"
|
||
}
|
||
items = append(items, ActionItem{Task: task, Assignee: assignee, Deadline: deadline})
|
||
}
|
||
if len(items) == 0 {
|
||
items = append(items, ActionItem{Task: "_не указано_", Assignee: "@tbd", Deadline: "—"})
|
||
}
|
||
return items
|
||
}
|