Skip to content

Commit

Permalink
Merge pull request #517 from essentialkaos/develop
Browse files Browse the repository at this point in the history
Version 13.10.0
  • Loading branch information
andyone authored Oct 31, 2024
2 parents 7e13517 + 3bc215f commit c3e3a3b
Show file tree
Hide file tree
Showing 9 changed files with 333 additions and 37 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
## Changelog

### [13.10.0](https://kaos.sh/ek/13.10.0)

> [!IMPORTANT]
> This release contains breaking changes to the `input.Read`, `input.ReadPassword`, and `input.ReadPasswordSecure` methods. Prior to this release, all of these methods took a boolean argument to disallow empty input. Since we are adding input validators, you will need to use the `NotEmpty` validator for the same behaviour.
- `[fmtutil/table]` Added automatic breaks feature
- `[terminal/input]` Added input validation feature
- `[terminal/input]` Fixed bug with hiding the password when `HidePassword` is set to true and an empty input error is displayed
- `[terminal/input]` Fixed bug with printing new line after input field on error

### [13.9.2](https://kaos.sh/ek/13.9.2)

- `[knf]` Added helper `Q`
Expand Down
16 changes: 16 additions & 0 deletions fmtutil/table/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ type Table struct {
// Width is table maximum width
Width int

// Breaks is an interval for separators between given number of rows
Breaks int

// SeparatorSymbol is symbol used for borders rendering
BorderSymbol string

Expand Down Expand Up @@ -91,6 +94,9 @@ type Table struct {

// Slice with auto calculated sizes
columnSizes []int

// Cursor is number of the latest record
cursor int
}

// ////////////////////////////////////////////////////////////////////////////////// //
Expand All @@ -116,6 +122,9 @@ var SeparatorColorTag = "{s}"
// ColumnSeparatorSymbol is default column separator symbol
var ColumnSeparatorSymbol = "|"

// Breaks is an interval for separators between given number of rows
var Breaks = -1

// FullScreen is a flag for full-screen table by default
var FullScreen = true

Expand All @@ -126,6 +135,7 @@ func NewTable(headers ...string) *Table {
return &Table{
HeaderCapitalize: HeaderCapitalize,
FullScreen: FullScreen,
Breaks: Breaks,
Headers: headers,
Processor: convertSlice,
}
Expand Down Expand Up @@ -354,6 +364,10 @@ func renderData(t *Table) {

// renderRowData render data in row
func renderRowData(t *Table, data []string, totalColumns int) {
if t.Breaks > 0 && t.cursor > 0 && t.cursor%t.Breaks == 0 {
renderSeparator(t)
}

for columnIndex, columnData := range data {
if columnIndex == totalColumns {
break
Expand All @@ -377,6 +391,8 @@ func renderRowData(t *Table, data []string, totalColumns int) {
}
}

t.cursor++

fmtc.NewLine()
}

Expand Down
1 change: 1 addition & 0 deletions fmtutil/table/table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ func (s *TableSuite) TestRender(c *C) {
t.BorderColorTag = "{b}"
t.SeparatorColorTag = "{g}"
t.HeaderCapitalize = true
t.Breaks = 2

c.Assert(t.Render(), NotNil)

Expand Down
30 changes: 20 additions & 10 deletions terminal/input/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,35 @@ import (

func ExampleRead() {
// User must enter name
input, err := Read("Please enter user name", true)
userName, err := Read("Please enter user name", NotEmpty)

if err != nil {
fmt.Printf("Error: %v\n", err)
return
}

fmt.Printf("User name: %s\n", input)
fmt.Printf("User name: %s\n", userName)

// You can read user input without providing any title
fmt.Println("Please enter user name")
input, err = Read("", true)
userName, err = Read("")

if err != nil {
fmt.Printf("Error: %v\n", err)
return
}

fmt.Printf("User name: %s\n", input)
fmt.Printf("User name: %s\n", userName)

// You can define many validators at once
userEmail, err := Read("Please enter user email", NotEmpty, IsEmail)

if err != nil {
fmt.Printf("Error: %v\n", err)
return
}

fmt.Printf("User email: %s\n", userEmail)
}

func ExampleReadPassword() {
Expand All @@ -44,7 +54,7 @@ func ExampleReadPassword() {
NewLine = true

// User must enter the password
password, err := ReadPassword("Please enter password", true)
password, err := ReadPassword("Please enter password", NotEmpty)

if err != nil {
fmt.Printf("Error: %v\n", err)
Expand All @@ -60,7 +70,7 @@ func ExampleReadPasswordSecure() {
MaskSymbolColorTag = "{s}"

// User must enter the password
password, err := ReadPasswordSecure("Please enter password", true)
password, err := ReadPasswordSecure("Please enter password", NotEmpty)

if err != nil {
fmt.Printf("Error: %v\n", err)
Expand All @@ -87,7 +97,7 @@ func ExampleReadAnswer() {
}

func ExampleAddHistory() {
input, err := Read("Please enter user name", true)
input, err := Read("Please enter user name", NotEmpty)

if err != nil {
fmt.Printf("Error: %v\n", err)
Expand All @@ -101,7 +111,7 @@ func ExampleAddHistory() {
}

func ExampleSetHistoryCapacity() {
input, err := Read("Please enter user name", true)
input, err := Read("Please enter user name", NotEmpty)

if err != nil {
fmt.Printf("Error: %v\n", err)
Expand Down Expand Up @@ -142,7 +152,7 @@ func ExampleSetCompletionHandler() {
return ""
})

input, err := Read("Please enter command", true)
input, err := Read("Please enter command", NotEmpty)

if err != nil {
fmt.Printf("Error: %v\n", err)
Expand Down Expand Up @@ -177,7 +187,7 @@ func ExampleSetHintHandler() {
return ""
})

input, err := Read("Please enter command", true)
input, err := Read("Please enter command", NotEmpty)

if err != nil {
fmt.Printf("Error: %v\n", err)
Expand Down
57 changes: 34 additions & 23 deletions terminal/input/input.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,18 @@ var NewLine = false

// ////////////////////////////////////////////////////////////////////////////////// //

// ErrInvalidAnswer is error for wrong answer for Y/N question
var ErrInvalidAnswer = fmt.Errorf("Please enter Y or N")

// ////////////////////////////////////////////////////////////////////////////////// //

var oldTMUXFlag int8

// ////////////////////////////////////////////////////////////////////////////////// //

// Read reads user input
func Read(title string, nonEmpty bool) (string, error) {
return readUserInput(title, nonEmpty, false)
func Read(title string, validators ...Validator) (string, error) {
return readUserInput(title, false, validators)
}

// ReadAnswer reads user's answer to yes/no question
Expand All @@ -93,7 +98,7 @@ func ReadAnswer(title string, defaultAnswers ...string) (bool, error) {
fmtc.Println(TitleColorTag + getAnswerTitle(title, defaultAnswer) + "{!}")
}

fmtc.Println(Prompt + "y")
fmtc.Println(Prompt + "{s}Y{!}")

if NewLine {
fmtc.NewLine()
Expand All @@ -104,7 +109,7 @@ func ReadAnswer(title string, defaultAnswers ...string) (bool, error) {

for {
answer, err := readUserInput(
getAnswerTitle(title, defaultAnswer), false, false,
getAnswerTitle(title, defaultAnswer), false, nil,
)

if err != nil {
Expand All @@ -121,22 +126,22 @@ func ReadAnswer(title string, defaultAnswers ...string) (bool, error) {
case "N":
return false, nil
default:
terminal.Warn("Please enter Y or N")
terminal.Warn(ErrInvalidAnswer)
fmtc.NewLine()
}
}
}

// ReadPassword reads password or some private input that will be hidden
// after pressing Enter
func ReadPassword(title string, nonEmpty bool) (string, error) {
return readUserInput(title, nonEmpty, true)
func ReadPassword(title string, validators ...Validator) (string, error) {
return readUserInput(title, true, validators)
}

// ReadPasswordSecure reads password or some private input that will be hidden
// after pressing Enter
func ReadPasswordSecure(title string, nonEmpty bool) (*secstr.String, error) {
password, err := readUserInput(title, nonEmpty, true)
func ReadPasswordSecure(title string, validators ...Validator) (*secstr.String, error) {
password, err := readUserInput(title, true, validators)

if err != nil {
return nil, err
Expand Down Expand Up @@ -202,41 +207,51 @@ func getAnswerTitle(title, defaultAnswer string) string {

switch strings.ToUpper(defaultAnswer) {
case "Y":
return fmt.Sprintf(TitleColorTag+"%s ({*}Y{!*}/n){!}", title)
return fmt.Sprintf(TitleColorTag+"%s (Y/n){!}", title)
case "N":
return fmt.Sprintf(TitleColorTag+"%s (y/{*}N{!*}){!}", title)
return fmt.Sprintf(TitleColorTag+"%s (y/N){!}", title)
default:
return fmt.Sprintf(TitleColorTag+"%s (y/n){!}", title)
}
}

// readUserInput reads user input
func readUserInput(title string, nonEmpty, private bool) (string, error) {
func readUserInput(title string, private bool, validators []Validator) (string, error) {
if title != "" {
fmtc.Println(TitleColorTag + title + "{!}")
}

var input string
var err error

if NewLine {
defer fmtc.NewLine()
}

if private && HidePassword {
linenoise.SetMaskMode(true)
defer linenoise.SetMaskMode(false)
}

INPUT_LOOP:
for {
input, err = linenoise.Line(fmtc.Sprintf(Prompt))

if private && HidePassword {
linenoise.SetMaskMode(false)
}

if err != nil {
return "", err
}

if nonEmpty && strings.TrimSpace(input) == "" {
terminal.Warn("\nYou must enter non-empty value\n")
continue
if len(validators) != 0 {
for _, validator := range validators {
input, err = validator.Validate(input)

if err != nil {
fmtc.NewLine()
terminal.Warn(err.Error())
fmtc.NewLine()
continue INPUT_LOOP
}
}
}

if private && input != "" {
Expand All @@ -256,10 +271,6 @@ func readUserInput(title string, nonEmpty, private bool) (string, error) {
break
}

if NewLine {
fmtc.NewLine()
}

return input, err
}

Expand Down
21 changes: 18 additions & 3 deletions terminal/input/input_stubs.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,19 @@ import (

// ////////////////////////////////////////////////////////////////////////////////// //

// CompletionHandler is completion handler
type CompletionHandler = func(input string) []string

// HintHandler is hint handler
type HintHandler = func(input string) string

// ////////////////////////////////////////////////////////////////////////////////// //

// ❗ ErrKillSignal is error type when user cancel input
var ErrKillSignal = errors.New("")

// ////////////////////////////////////////////////////////////////////////////////// //

// ❗ Prompt is prompt string
var Prompt = "> "

Expand Down Expand Up @@ -51,8 +61,13 @@ var NewLine = false

// ////////////////////////////////////////////////////////////////////////////////// //

// ErrInvalidAnswer is error for wrong answer for Y/N question
var ErrInvalidAnswer = errors.New("")

// ////////////////////////////////////////////////////////////////////////////////// //

// ❗ Read reads user input
func Read(title string, nonEmpty bool) (string, error) {
func Read(title string, validators ...Validator) (string, error) {
panic("UNSUPPORTED")
}

Expand All @@ -63,13 +78,13 @@ func ReadAnswer(title string, defaultAnswers ...string) (bool, error) {

// ❗ ReadPassword reads password or some private input that will be hidden
// after pressing Enter
func ReadPassword(title string, nonEmpty bool) (string, error) {
func ReadPassword(title string, validators ...Validator) (string, error) {
panic("UNSUPPORTED")
}

// ❗ ReadPasswordSecure reads password or some private input that will be hidden
// after pressing Enter
func ReadPasswordSecure(title string, nonEmpty bool) (*secstr.String, error) {
func ReadPasswordSecure(title string, validators ...Validator) (*secstr.String, error) {
panic("UNSUPPORTED")
}

Expand Down
Loading

0 comments on commit c3e3a3b

Please sign in to comment.