diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/TUI.iml b/.idea/TUI.iml new file mode 100644 index 0000000..5e764c4 --- /dev/null +++ b/.idea/TUI.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/discord.xml b/.idea/discord.xml new file mode 100644 index 0000000..104c42f --- /dev/null +++ b/.idea/discord.xml @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..c400fe6 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/ansi.go b/ansi.go new file mode 100644 index 0000000..84e41fe --- /dev/null +++ b/ansi.go @@ -0,0 +1,64 @@ +package tui + +import ( + "fmt" + "golang.org/x/term" + "os" + "strings" +) + +type Colors = string +type Fmt = string + +// Formatting +const ( + FmtReset Colors = "\033[0m" + FmtBold = "\033[1m" + FmtBoldReset = "\033[22m" + FmtDim = "\033[2m" + FmtDimReset = FmtBoldReset + FmtItalic = "\033[3m" + FmtItalicReset = "\033[23m" + FmtUnderline = "\033[4m" + FmtUnderlineReset = "\033[24m" + FmtBlink = "\033[5m" + FmtBlinkReset = "\033[25m" + FmtReverse = "\033[7m" + FmtReverseReset = "\033[27m" + FmtHidden = "\033[8m" + FmtHiddenReset = "\033[28m" + FmtStrikethrough = "\033[9m" + FmtStrikethroughReset = "\033[29m" +) + +// Foreground Colors +const ( + FgColorGrey Fmt = "\033[38;5;247m" + FgColorGold = "\033[38;5;214m" + FgColorGreen = "\033[38;5;34m" + FgColorRed = "\033[38;5;167m" +) + +func BlankLine() { + + width, _, _ := term.GetSize(int(os.Stdin.Fd())) + + // Assume ptero terminal if no width. + if width == 0 { + fmt.Print("\033[1B") + } else { + fmt.Println("") + } + +} + +func Format(codes ...string) string { + + var str strings.Builder + + for _, code := range codes { + str.WriteString(code) + } + + return str.String() +} diff --git a/confirmation/confirmation.go b/confirmation/confirmation.go new file mode 100644 index 0000000..a43e6b5 --- /dev/null +++ b/confirmation/confirmation.go @@ -0,0 +1,113 @@ +package confirmation + +import ( + "fmt" + "gitlab.com/omnibill/tui" + "strings" + + "golang.org/x/term" +) + +type InputData struct { + Notice string + Question string +} + +func New(data InputData) (*bool, error) { + + if data.Notice != "" { + fmt.Printf( + tui.Format(tui.FmtBold, tui.FgColorGrey) + + "[" + tui.FgColorGold + "!" + tui.FgColorGrey + "]" + tui.FmtBoldReset + " " + + tui.FmtItalic + data.Notice + tui.FmtReset + "\n", + ) + //fmt.Printf("\033[1m\033[38;5;247m[\033[38;5;214m!\033[38;5;247m]\033[22m \033[3m%s\033[0m\n", data.Notice) + } + + if data.Question != "" { + fmt.Printf( + tui.Format(tui.FmtBold, tui.FgColorGrey) + + "[" + tui.FgColorGold + "!" + tui.FgColorGrey + "]" + tui.FmtReset + " " + + data.Question + " " + + "(" + tui.FgColorGreen + "y" + tui.FmtReset + "/" + tui.FgColorRed + "n" + tui.FmtReset + ") " + + tui.FgColorGrey + ">>" + tui.Format(tui.FmtItalic, tui.FgColorGold) + "\n", + ) + //fmt.Printf("\033[1m\033[38;5;247m[\033[38;5;214m?\033[38;5;247m]\033[0m %s (\033[38;5;34my\033[0m/\033[38;5;167mn\033[0m) \033[38;5;247m>>\033[3m\033[38;5;214m\n", data.Question) + } + + var input string + var chosen bool + +inputLoop: + for { + if _, err := fmt.Scanln(&input); err != nil { + return nil, err + } + + width, _, err := term.GetSize(0) + if err != nil { + return nil, err + } + + switch strings.ToLower(input) { + case "y", "yes": + chosen = true + break inputLoop + case "n", "no": + chosen = false + break inputLoop + default: + var lineNum int + + if width == 0 { + if data.Notice != "" { + lineNum++ + } + lineNum += 2 + } else { + if data.Notice == "" { + lineNum = ((len(data.Question) + 5 + width - 1) / width) + 1 + } else { + lineNum = ((len(data.Notice) + 5 + width) / width) + ((len(data.Question) + 5 + width - 1) / width) + 1 + } + + } + + for i := 0; i < lineNum; i++ { + fmt.Printf("\033[A\033[K\033[0G") + } + fmt.Printf( + tui.Format(tui.FmtBold, tui.FgColorGrey) + + "[" + tui.FgColorGold + "!" + tui.FgColorGrey + "]" + tui.FmtReset + " " + + "Invalid input, please try again!\n", + ) + //fmt.Printf("\033[1m\033[38;5;247m[\033[38;5;167m!\033[38;5;247m]\033[0m Invalid input, please try again!\n") + + if data.Notice != "" { + fmt.Printf( + tui.Format(tui.FmtBold, tui.FgColorGrey) + + "[" + tui.FgColorGold + "!" + tui.FgColorGrey + "]" + tui.FmtBoldReset + " " + + tui.FmtItalic + data.Notice + tui.FmtReset + "\n", + ) + //fmt.Printf("\033[1m\033[38;5;247m[\033[38;5;214m!\033[38;5;247m]\033[22m \033[3m%s\033[0m\n", data.Notice) + } + + if data.Question != "" { + fmt.Printf( + tui.Format(tui.FmtBold, tui.FgColorGrey) + + "[" + tui.FgColorGold + "!" + tui.FgColorGrey + "]" + tui.FmtReset + " " + + data.Question + " " + + "(" + tui.FgColorGreen + "y" + tui.FmtReset + "/" + tui.FgColorRed + "n" + tui.FmtReset + ") " + + tui.FgColorGrey + ">>" + tui.Format(tui.FmtItalic, tui.FgColorGold) + "\n", + ) + //fmt.Printf("\033[1m\033[38;5;247m[\033[38;5;214m?\033[38;5;247m]\033[0m %s (\033[38;5;34my\033[0m/\033[38;5;167mn\033[0m) \033[38;5;247m>>\033[3m\033[38;5;214m\n", data.Question) + } + continue inputLoop + } + } + + fmt.Println("\033[0m") + + return &chosen, nil + +} diff --git a/displaylist/displaylist.go b/displaylist/displaylist.go new file mode 100644 index 0000000..e6f719e --- /dev/null +++ b/displaylist/displaylist.go @@ -0,0 +1,31 @@ +package displaylist + +import ( + "fmt" + + "git.eggactyl.cloud/Eggactyl/tui" +) + +type ListOptions struct { + Title string + Items []Item +} + +type Item struct { + Key string + Value string +} + +func New(opts ListOptions) { + + fmt.Println(tui.FgColorGrey + "[ " + tui.Format(tui.FgColorGold, tui.FmtUnderline) + opts.Title + tui.Format(tui.FmtUnderlineReset, tui.FgColorGrey) + " ]" + tui.FmtReset) + + for _, item := range opts.Items { + fmt.Println(itemString(item.Key, item.Value)) + } + +} + +func itemString(key string, value string) string { + return tui.FgColorGrey + "[ " + tui.FgColorGold + key + " " + tui.FgColorGrey + value + tui.FgColorGrey + " ]" + tui.FmtReset +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..5392218 --- /dev/null +++ b/go.mod @@ -0,0 +1,13 @@ +module gitlab.com/omnibill/tui + +go 1.23.2 + +require ( + github.com/nicksnyder/go-i18n/v2 v2.4.1 + golang.org/x/term v0.25.0 +) + +require ( + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.19.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..7ffa050 --- /dev/null +++ b/go.sum @@ -0,0 +1,14 @@ +git.eggactyl.cloud/Eggactyl/tui v0.0.0-20240808144724-4c617ba4b5e2 h1:Moamm0JZr2J+DnkxNonZMmTjIC6lZxMMKQ93sPs4OfU= +git.eggactyl.cloud/Eggactyl/tui v0.0.0-20240808144724-4c617ba4b5e2/go.mod h1:nDnIAL3nj3ZRfJIjESD752RtHbInjn2ASxBeeRgSv1g= +github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= +github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/nicksnyder/go-i18n/v2 v2.4.1 h1:zwzjtX4uYyiaU02K5Ia3zSkpJZrByARkRB4V3YPrr0g= +github.com/nicksnyder/go-i18n/v2 v2.4.1/go.mod h1:++Pl70FR6Cki7hdzZRnEEqdc2dJt+SAGotyFg/SvZMk= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/list/list.go b/list/list.go new file mode 100644 index 0000000..7823603 --- /dev/null +++ b/list/list.go @@ -0,0 +1,345 @@ +package list + +import ( + "bufio" + "fmt" + "gitlab.com/omnibill/tui" + "os" + "regexp" + "sort" + "strconv" + + "github.com/nicksnyder/go-i18n/v2/i18n" + "golang.org/x/term" +) + +var regex = regexp.MustCompile("\033\\[[0-9;]+m") + +func removeANSIEscapeCodes(input string) string { + return regex.ReplaceAllString(input, "") +} + +type ListData struct { + pages []ListPage + history []int + currentPage int + strLengths []int + localizer *i18n.Localizer +} + +type ListPage struct { + Title string + Items []ListItem + Render func() []ListItem + Cache []ListItem +} + +type ListItem struct { + Label string + Notice string + Order int + Render func() ListItem + LinkTo int + Value interface{} +} + +func New(localizer *i18n.Localizer) ListData { + return ListData{ + pages: []ListPage{}, + history: []int{}, + localizer: localizer, + } +} + +func (l *ListData) Execute() interface{} { + return l.listRunner() +} + +func (l *ListData) AddPage(page ListPage) { + l.pages = append(l.pages, page) +} + +func (l *ListData) listRunner() interface{} { + + var option interface{} + + for { + + var tempItemStore []ListItem + + for _, item := range l.pages[l.currentPage].Items { + + if len(l.pages[l.currentPage].Cache) == 0 { + if item.Render != nil { + renderedItem := item.Render() + renderedItem.Value = item.Value + tempItemStore = append(tempItemStore, renderedItem) + } else { + tempItemStore = append(tempItemStore, item) + } + + sort.SliceStable(tempItemStore, func(i, j int) bool { + return tempItemStore[i].Order < tempItemStore[j].Order + }) + } + + } + + if len(tempItemStore) != 0 { + l.pages[l.currentPage].Cache = append(l.pages[l.currentPage].Cache, tempItemStore...) + + if l.currentPage != 0 { + l.pages[l.currentPage].Cache = append( + l.pages[l.currentPage].Cache, + []ListItem{ + { + Label: "Back", + Value: "action_back", + }, + }..., + ) + } + + } + + l.renderList() + + chosen := l.inputHandler(l.pages[l.currentPage].Cache) + + if chosen.LinkTo != 0 { + + width, _, _ := term.GetSize(0) + + var totalLineNum int + + if width == 0 { + totalLineNum = len(l.strLengths) + } else { + for _, strLength := range l.strLengths { + totalLineNum += ((strLength) / width) + } + } + totalLineNum++ //User input line + + for i := 0; i < totalLineNum; i++ { + fmt.Printf("\033[A\033[K\033[0G") + } + + l.history = append(l.history, l.currentPage) + l.currentPage = chosen.LinkTo + + continue + + } + + if chosen.Value != nil { + + if _, ok := chosen.Value.(string); ok { + if chosen.Value == "action_home" { + + l.currentPage = 0 + l.history = []int{} + + width, _, _ := term.GetSize(0) + + var totalLineNum int + + if width == 0 { + totalLineNum = len(l.strLengths) + } else { + for _, strLength := range l.strLengths { + totalLineNum += ((strLength) / width) + } + } + totalLineNum++ //User input line + + for i := 0; i < totalLineNum; i++ { + fmt.Printf("\033[A\033[K\033[0G") + } + + continue + + } + + if chosen.Value == "action_back" { + + l.currentPage = l.history[len(l.history)-1] + + if len(l.history) == 1 { + l.history = []int{} + } else { + l.history = l.history[0 : len(l.history)-2] + } + + width, _, _ := term.GetSize(0) + + var totalLineNum int + + if width == 0 { + totalLineNum = len(l.strLengths) + } else { + for _, strLength := range l.strLengths { + totalLineNum += ((strLength) / width) + } + } + totalLineNum++ //User input line + + for i := 0; i < totalLineNum; i++ { + fmt.Printf("\033[A\033[K\033[0G") + } + + continue + + } + } + + option = chosen.Value + break + + } + + } + + return option + +} + +func (l *ListData) renderList() { + + l.strLengths = []int{} + + currentPage := l.pages[l.currentPage] + + listNotice := tui.Format(tui.FmtBold, tui.FgColorGrey) + + "[" + tui.FgColorGold + "!" + tui.FgColorGrey + "]" + tui.FmtBoldReset + " " + + tui.FmtItalic + fmt.Sprintf("Please choose an option from 1 - %d", len(currentPage.Cache)) + "\n" + + //listNotice := fmt.Sprintf("\033[1m\033[38;5;247m[\033[38;5;214m!\033[38;5;247m]\033[22m \033[3mPlease choose an option from 1 - %d\033[0m\n", len(currentPage.Cache)) + l.strLengths = append(l.strLengths, len(removeANSIEscapeCodes(listNotice))) + fmt.Print(listNotice) + + listTitle := tui.FgColorGrey + "[ " + tui.Format(tui.FgColorGold, tui.FmtUnderline) + currentPage.Title + ":" + tui.Format(tui.FmtUnderlineReset, tui.FgColorGrey) + " ]" + tui.FmtReset + "\n" + l.strLengths = append(l.strLengths, len(removeANSIEscapeCodes(listTitle))) + fmt.Print(listTitle) + + longestStrLength := 0 + + for _, item := range currentPage.Cache { + formattedLabel := removeANSIEscapeCodes(item.Label) + if len([]rune(formattedLabel)) > longestStrLength { + longestStrLength = len([]rune(formattedLabel)) + } + } + + for index, item := range currentPage.Cache { + var listItem string + + var userInputColor string + if index == len(currentPage.Cache)-1 { + userInputColor = tui.Format(tui.FmtItalic, tui.FgColorGrey) + } + + if _, ok := item.Value.(string); ok { + if item.Value == "action_back" { + listItem = " " + tui.FgColorGrey + "[" + tui.Format(tui.FmtBold, tui.FgColorRed) + strconv.Itoa(index+1) + tui.Format(tui.FmtBoldReset, tui.FgColorGrey) + "]" + tui.FmtReset + + fmt.Sprintf(" %-*s ", longestStrLength, item.Label) + tui.Format(tui.FmtItalic, tui.FgColorGrey) + item.Notice + tui.FmtReset + userInputColor + "\n" + //listItem = fmt.Sprintf(" \033[38;5;247m[\033[1m\033[38;5;167m%d\033[22m\033[38;5;247m]\033[0m %-*s \033[3m\033[38;5;247m%s\033[0m%s\n", index+1, longestStrLength, item.Label, item.Notice, userInputColor) + } else { + listItem = " " + tui.FgColorGrey + "[" + tui.Format(tui.FmtBold, tui.FgColorGold) + strconv.Itoa(index+1) + tui.Format(tui.FmtBoldReset, tui.FgColorGrey) + "]" + tui.FmtReset + + fmt.Sprintf(" %-*s ", longestStrLength, item.Label) + tui.Format(tui.FmtItalic, tui.FgColorGrey) + item.Notice + tui.FmtReset + userInputColor + "\n" + //listItem = fmt.Sprintf(" \033[38;5;247m[\033[1m\033[38;5;214m%d\033[22m\033[38;5;247m]\033[0m %-*s \033[3m\033[38;5;247m%s\033[0m%s\n", index+1, longestStrLength, item.Label, item.Notice, userInputColor) + } + } else { + listItem = " " + tui.FgColorGrey + "[" + tui.Format(tui.FmtBold, tui.FgColorGold) + strconv.Itoa(index+1) + tui.Format(tui.FmtBoldReset, tui.FgColorGrey) + "]" + tui.FmtReset + + fmt.Sprintf(" %-*s ", longestStrLength, item.Label) + tui.Format(tui.FmtItalic, tui.FgColorGrey) + item.Notice + tui.FmtReset + userInputColor + "\n" + //listItem = fmt.Sprintf(" \033[38;5;247m[\033[1m\033[38;5;214m%d\033[22m\033[38;5;247m]\033[0m %-*s \033[3m\033[38;5;247m%s\033[0m%s\n", index+1, longestStrLength, item.Label, item.Notice, userInputColor) + } + + l.strLengths = append(l.strLengths, len(removeANSIEscapeCodes(listItem))) + fmt.Print(listItem) + } + +} + +func (l *ListData) inputHandler(items []ListItem) ListItem { + + scanner := bufio.NewScanner(os.Stdin) + scanner.Split(bufio.ScanLines) + + var input int + + for scanner.Scan() { + + text := scanner.Text() + + inputAnswer, err := strconv.Atoi(text) + if err != nil { + width, _, _ := term.GetSize(0) + + var totalLineNum int + + if width == 0 { + totalLineNum = len(l.strLengths) + } else { + for _, strLength := range l.strLengths { + totalLineNum += (strLength) / width + } + } + totalLineNum++ //User input line + + for i := 0; i < totalLineNum; i++ { + fmt.Printf("\033[0m\033[A\033[K\033[0G") + } + + fmt.Printf( + tui.Format(tui.FmtBold, tui.FgColorGrey) + + "[" + tui.FgColorRed + "!" + tui.FgColorGrey + "]" + tui.FmtBoldReset + " " + + " Invalid input, please try again!\n", + ) + //fmt.Printf("\033[1m\033[38;5;247m[\033[38;5;167m!\033[38;5;247m]\033[0m Invalid input, please try again!\n") + l.strLengths = []int{} + + l.renderList() + continue + } + + if inputAnswer < 1 || inputAnswer > len(items) { + width, _, _ := term.GetSize(0) + + var totalLineNum int + + if width == 0 { + totalLineNum = len(l.strLengths) + } else { + for _, strLength := range l.strLengths { + totalLineNum += (strLength) / width + } + } + totalLineNum++ //User input line + + for i := 0; i < totalLineNum; i++ { + fmt.Printf("\033[A\033[K\033[0G") + } + + fmt.Printf( + tui.Format(tui.FmtBold, tui.FgColorGrey) + + "[" + tui.FgColorRed + "!" + tui.FgColorGrey + "]" + tui.FmtBoldReset + " " + + " Invalid input, please try again!\n", + ) + //fmt.Printf("\033[1m\033[38;5;247m[\033[38;5;167m!\033[38;5;247m]\033[0m Invalid input, please try again!\n") + l.strLengths = []int{} + + l.renderList() + continue + } + + input = inputAnswer + + break + + } + + chosenItem := items[input-1] + + return chosenItem +} diff --git a/progress/progress.go b/progress/progress.go new file mode 100644 index 0000000..7dfd15d --- /dev/null +++ b/progress/progress.go @@ -0,0 +1,190 @@ +package progress + +import ( + "fmt" + "git.eggactyl.cloud/Eggactyl/tui" + "math" + "regexp" + "strconv" + "strings" + "sync" + + "golang.org/x/term" +) + +var regex = regexp.MustCompile("\033\\[[0-9;]+m") + +//║░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░║ 40 characters + +type ProgressInfo struct { + Size int64 + Desc string + ClearOnFinish bool +} + +type ProgressBar struct { + lock sync.Mutex + maxBytes int64 + currentBytes int64 + hasStarted bool + currentPercent int + desc string + clearFinish bool + textWidth int +} + +func New(info ProgressInfo) *ProgressBar { + return &ProgressBar{ + maxBytes: info.Size, + desc: info.Desc, + clearFinish: info.ClearOnFinish, + } + +} + +func (p *ProgressBar) render(final bool) { + + p.lock.Lock() + defer p.lock.Unlock() + + var sb strings.Builder + + numFilled := math.Round((float64(p.currentBytes) / float64(p.maxBytes)) * 40) + numBlank := 40 - numFilled + donePercent := math.Round(float64(p.currentBytes) / float64(p.maxBytes) * 100) + + if int(donePercent) == p.currentPercent { + return + } + + var blockColor string + + if final { + blockColor = tui.FgColorGreen + } else { + blockColor = tui.FgColorGold + } + + sb.WriteString(p.desc) + sb.WriteString(" ") + + percentString := strconv.Itoa(int(donePercent)) + "%" + blankPercent := 4 - len(percentString) + + for i := 0; i < blankPercent; i++ { + sb.WriteString(" ") + } + + sb.WriteString(percentString) + + sb.WriteString(tui.Format(tui.FmtBold, tui.FgColorGrey) + " [" + tui.FmtReset) + //sb.WriteString("\033[1m\033[38;5;247m [\033[0m") + + for i := 0; i < int(numFilled); i++ { + + sb.WriteString(blockColor + "█" + tui.FmtReset) + //sb.WriteString(fmt.Sprintf("%sm█\033[0m", blockColor)) + + } + + if numFilled < 40 { + + numBlank = numBlank - 1 + + sb.WriteString(tui.FgColorGold + "▒" + tui.FmtReset) + //sb.WriteString("\033[38;5;214m▒\033[0m") + + for i := 0; i < int(numBlank); i++ { + sb.WriteString(tui.FgColorGrey + "░" + tui.FmtReset) + //sb.WriteString("\033[38;5;247m░\033[0m") + } + + } else { + + for i := 0; i < int(numBlank); i++ { + sb.WriteString(tui.FgColorGrey + "░" + tui.FmtReset) + //sb.WriteString("\033[38;5;247m░\033[0m") + } + + } + + sb.WriteString(tui.Format(tui.FmtBold, tui.FgColorGrey) + "]" + tui.FmtReset) + //sb.WriteString("\033[1m\033[38;5;247m]\033[0m") + + width, _, err := term.GetSize(0) + if err != nil { + return + } + + var lineNum int + + if width == 0 { + lineNum = 1 + } else { + lineNum = ((len(removeANSIEscapeCodes(sb.String())) + width - 1) / width) + } + + if p.hasStarted { + for i := 0; i < lineNum; i++ { + if i == lineNum-1 { + fmt.Println("\033[A\033[0G\033[K" + sb.String()) + } else { + fmt.Println("\033[A\033[0G\033[K") + } + } + } else { + fmt.Println(sb.String()) + p.hasStarted = true + } + + p.textWidth = len(removeANSIEscapeCodes(sb.String())) + +} + +func (p *ProgressBar) Add(num int64) { + p.currentBytes = p.currentBytes + num + + p.render(false) +} + +func (p *ProgressBar) Close() (err error) { + + if p.clearFinish { + + width, _, err := term.GetSize(0) + if err != nil { + return err + } + + var lineNum int + + if width == 0 { + lineNum = 1 + } else { + lineNum = ((p.textWidth + width - 1) / width) + } + + for i := 0; i < lineNum; i++ { + fmt.Println("\033[A\033[0G\033[K") + } + + } else { + p.render(true) + } + + return +} +func (p *ProgressBar) Write(b []byte) (n int, err error) { + n = len(b) + p.Add(int64(n)) + return +} + +func (p *ProgressBar) Read(b []byte) (n int, err error) { + p.Add(int64(n)) + return +} + +func removeANSIEscapeCodes(input string) string { + return regex.ReplaceAllString(input, "") +} diff --git a/textinput/text.go b/textinput/text.go new file mode 100644 index 0000000..6efb51f --- /dev/null +++ b/textinput/text.go @@ -0,0 +1,172 @@ +package textinput + +import ( + "fmt" + "git.eggactyl.cloud/Eggactyl/tui" + "git.eggactyl.cloud/Eggactyl/tui/validators" + + "golang.org/x/term" +) + +type InputData struct { + Notice string + Question string + Validator validators.TextInputValidator + ValidationFunc func(input string) bool +} + +func New(data InputData) (*string, error) { + + if data.Notice != "" { + fmt.Printf( + tui.Format(tui.FmtBold, tui.FgColorGrey) + + "[" + tui.FgColorGold + "!" + tui.FgColorGrey + "]" + tui.FmtBoldReset + " " + + tui.FmtItalic + data.Notice + tui.FmtReset + "\n", + ) + } else if data.Validator != nil && data.Validator.Notice() != "" { + fmt.Printf( + tui.Format(tui.FmtBold, tui.FgColorGrey) + + "[" + tui.FgColorGold + "!" + tui.FgColorGrey + "]" + tui.FmtBoldReset + " " + + tui.FmtItalic + data.Validator.Notice() + tui.FmtReset + "\n", + ) + //fmt.Printf("\033[1m\033[38;5;247m[\033[38;5;214m!\033[38;5;247m]\033[22m \033[3m%s\033[0m\n", data.Validator.Notice()) + } + + if data.Question != "" { + fmt.Printf( + tui.Format(tui.FmtBold, tui.FgColorGrey) + + "[" + tui.FgColorGold + "?" + tui.FgColorGrey + "]" + tui.FmtReset + " " + + data.Question + " " + tui.FgColorGrey + ">>" + tui.Format(tui.FmtItalic, tui.FgColorGold) + "\n", + ) + } + + var input string + + for { + + if _, err := fmt.Scanln(&input); err != nil { + return nil, err + } + + width, _, err := term.GetSize(0) + if err != nil { + return nil, err + } + + if data.ValidationFunc != nil { + if !data.ValidationFunc(input) { + var lineNum int + + if width == 0 { + if data.Notice != "" { + lineNum++ + } + lineNum += 2 + } else { + if data.Notice == "" { + lineNum = ((len(data.Question) + 5 + width - 1) / width) + 1 + } else { + lineNum = ((len(data.Notice) + 5 + width) / width) + ((len(data.Question) + 5 + width - 1) / width) + 1 + } + } + + for i := 0; i < lineNum; i++ { + fmt.Printf("\033[A\033[K\033[0G") + } + + fmt.Printf( + tui.Format(tui.FmtBold, tui.FgColorGrey) + + "[" + tui.FgColorRed + "!" + tui.FgColorGrey + "]" + tui.FmtBoldReset + " " + + " Invalid input, please try again!\n", + ) + + if data.Notice != "" { + fmt.Printf( + tui.Format(tui.FmtBold, tui.FgColorGrey) + + "[" + tui.FgColorGold + "!" + tui.FgColorGrey + "]" + tui.FmtBoldReset + " " + + tui.FmtItalic + data.Notice + tui.FmtReset + "\n", + ) + } else if data.Validator != nil { + fmt.Printf( + tui.Format(tui.FmtBold, tui.FgColorGrey) + + "[" + tui.FgColorGold + "!" + tui.FgColorGrey + "]" + tui.FmtBoldReset + " " + + tui.FmtItalic + data.Validator.Notice() + tui.FmtReset + "\n", + ) + } + + if data.Question != "" { + fmt.Printf( + tui.Format(tui.FmtBold, tui.FgColorGrey) + + "[" + tui.FgColorGold + "?" + tui.FgColorGrey + "]" + tui.FmtReset + " " + + data.Question + " " + tui.FgColorGrey + ">>" + tui.Format(tui.FmtItalic, tui.FgColorGold) + "\n", + ) + } + continue + } + } else if data.Validator != nil { + if !data.Validator.ValidationFunc(input) { + var lineNum int + + if width == 0 { + if data.Notice != "" || data.Validator.Notice() != "" { + lineNum++ + } + lineNum += 2 + } else { + if data.Notice == "" && data.Validator.Notice() == "" { + lineNum = ((len(data.Question) + 5 + width - 1) / width) + 1 + } else { + if data.Notice != "" { + lineNum = ((len(data.Notice) + 5 + width) / width) + ((len(data.Question) + 5 + width - 1) / width) + 1 + } else if data.Validator.Notice() != "" { + lineNum = ((len(data.Validator.Notice()) + 5 + width - 1) / width) + ((len(data.Question) + 5 + width - 1) / width) + 1 + } + } + } + + for i := 0; i < lineNum; i++ { + fmt.Printf("\033[A\033[K\033[0G") + } + + fmt.Printf( + tui.Format(tui.FmtBold, tui.FgColorGrey) + + "[" + tui.FgColorRed + "!" + tui.FgColorGrey + "]" + tui.FmtBoldReset + " " + + " Invalid input, please try again!\n", + ) + + if data.Notice != "" { + fmt.Printf( + tui.Format(tui.FmtBold, tui.FgColorGrey) + + "[" + tui.FgColorGold + "!" + tui.FgColorGrey + "]" + tui.FmtBoldReset + " " + + tui.FmtItalic + data.Notice + tui.FmtReset + "\n", + ) + //fmt.Printf("\033[1m\033[38;5;247m[\033[38;5;214m!\033[38;5;247m]\033[22m \033[3m%s\033[0m\n", data.Notice) + } else if data.Validator.Notice() != "" { + fmt.Printf( + tui.Format(tui.FmtBold, tui.FgColorGrey) + + "[" + tui.FgColorGold + "!" + tui.FgColorGrey + "]" + tui.FmtBoldReset + " " + + tui.FmtItalic + data.Validator.Notice() + tui.FmtReset + "\n", + ) + } + + if data.Question != "" { + fmt.Printf( + tui.Format(tui.FmtBold, tui.FgColorGrey) + + "[" + tui.FgColorGold + "?" + tui.FgColorGrey + "]" + tui.FmtReset + " " + + data.Question + " " + tui.FgColorGrey + ">>" + tui.Format(tui.FmtItalic, tui.FgColorGold) + "\n", + ) + //fmt.Printf("\033[1m\033[38;5;247m[\033[38;5;214m?\033[38;5;247m]\033[0m %s \033[38;5;247m>>\033[3m\033[38;5;214m\n", data.Question) + } + continue + } + } + + break + + } + + fmt.Println("\033[0m") + + return &input, nil + +} diff --git a/validators/interface.go b/validators/interface.go new file mode 100644 index 0000000..d0c6c67 --- /dev/null +++ b/validators/interface.go @@ -0,0 +1,6 @@ +package validators + +type TextInputValidator interface { + Notice() string + ValidationFunc(input string) bool +} diff --git a/validators/mc_version.go b/validators/mc_version.go new file mode 100644 index 0000000..6d562dc --- /dev/null +++ b/validators/mc_version.go @@ -0,0 +1,46 @@ +package validators + +import ( + "fmt" +) + +func McVersion(versions []string) TextInputValidator { + return TextInputMCVersion{ + versions: versions, + } +} + +type TextInputMCVersion struct { + versions []string +} + +func (d TextInputMCVersion) Notice() string { + var versionString string + + for index, version := range d.versions { + if index == (len(d.versions) - 1) { + versionString = versionString + version + } else { + versionString = versionString + version + ", " + } + } + + return fmt.Sprintf("Available Versions: %s", versionString) +} + +func (d TextInputMCVersion) ValidationFunc(input string) bool { + + isFound := false + + for _, ver := range d.versions { + + if ver == input { + isFound = true + break + } + + } + + return isFound + +} diff --git a/validators/range.go b/validators/range.go new file mode 100644 index 0000000..1cea93b --- /dev/null +++ b/validators/range.go @@ -0,0 +1,33 @@ +package validators + +import ( + "fmt" + "strconv" +) + +func Range(min int, max int) TextInputValidator { + return TextInputRange{ + min: min, + max: max, + } +} + +type TextInputRange struct { + min int + max int +} + +func (d TextInputRange) Notice() string { + return fmt.Sprintf("Valid values: (%d - %d)", d.min, d.max) +} + +func (d TextInputRange) ValidationFunc(input string) bool { + portNum, err := strconv.Atoi(input) + if err != nil { + return false + } + if portNum > 65535 || portNum < 1 { + return false + } + return true +}