github.com/mithrandie/csvq@v1.18.1/lib/terminal/terminal.go (about) 1 package terminal 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "strings" 8 "unicode" 9 10 "github.com/mithrandie/csvq/lib/excmd" 11 "github.com/mithrandie/csvq/lib/option" 12 "github.com/mithrandie/csvq/lib/parser" 13 "github.com/mithrandie/csvq/lib/query" 14 "github.com/mithrandie/csvq/lib/value" 15 ) 16 17 const ( 18 DefaultPrompt string = "csvq > " 19 DefaultContinuousPrompt string = " > " 20 ) 21 22 type PromptEvaluationError struct { 23 Message string 24 } 25 26 func NewPromptEvaluationError(message string) error { 27 return &PromptEvaluationError{ 28 Message: message, 29 } 30 } 31 32 func (e PromptEvaluationError) Error() string { 33 return fmt.Sprintf("prompt: %s", e.Message) 34 } 35 36 type PromptElement struct { 37 Text string 38 Type excmd.ElementType 39 } 40 41 type Prompt struct { 42 scope *query.ReferenceScope 43 sequence []PromptElement 44 continuousSequence []PromptElement 45 46 buf bytes.Buffer 47 } 48 49 func NewPrompt(scope *query.ReferenceScope) *Prompt { 50 return &Prompt{ 51 scope: scope, 52 } 53 } 54 55 func (p *Prompt) LoadConfig() error { 56 p.sequence = nil 57 p.continuousSequence = nil 58 59 scanner := new(excmd.ArgumentScanner) 60 61 scanner.Init(p.scope.Tx.Environment.InteractiveShell.Prompt) 62 for scanner.Scan() { 63 p.sequence = append(p.sequence, PromptElement{ 64 Text: scanner.Text(), 65 Type: scanner.ElementType(), 66 }) 67 } 68 if err := scanner.Err(); err != nil { 69 p.sequence = nil 70 p.continuousSequence = nil 71 return NewPromptEvaluationError(err.Error()) 72 } 73 74 scanner.Init(p.scope.Tx.Environment.InteractiveShell.ContinuousPrompt) 75 for scanner.Scan() { 76 p.continuousSequence = append(p.continuousSequence, PromptElement{ 77 Text: scanner.Text(), 78 Type: scanner.ElementType(), 79 }) 80 } 81 if err := scanner.Err(); err != nil { 82 p.sequence = nil 83 p.continuousSequence = nil 84 return NewPromptEvaluationError(err.Error()) 85 } 86 return nil 87 } 88 89 func (p *Prompt) RenderPrompt(ctx context.Context) (string, error) { 90 s, err := p.Render(ctx, p.sequence) 91 if err != nil || len(s) < 1 { 92 s = DefaultPrompt 93 } 94 if p.scope.Tx.Flags.ExportOptions.Color { 95 if strings.IndexByte(s, 0x1b) < 0 { 96 s = p.scope.Tx.Palette.Render(option.PromptEffect, s) 97 } 98 } else { 99 s = p.StripEscapeSequence(s) 100 } 101 return s, err 102 } 103 104 func (p *Prompt) RenderContinuousPrompt(ctx context.Context) (string, error) { 105 s, err := p.Render(ctx, p.continuousSequence) 106 if err != nil || len(s) < 1 { 107 s = DefaultContinuousPrompt 108 } 109 if p.scope.Tx.Flags.ExportOptions.Color { 110 if strings.IndexByte(s, 0x1b) < 0 { 111 s = p.scope.Tx.Palette.Render(option.PromptEffect, s) 112 } 113 } else { 114 s = p.StripEscapeSequence(s) 115 } 116 return s, err 117 } 118 119 func (p *Prompt) Render(ctx context.Context, sequence []PromptElement) (string, error) { 120 p.buf.Reset() 121 var err error 122 123 for _, element := range sequence { 124 switch element.Type { 125 case excmd.FixedString: 126 p.buf.WriteString(element.Text) 127 case excmd.Variable: 128 if err = p.evaluate(ctx, parser.Variable{Name: element.Text}); err != nil { 129 return "", err 130 } 131 case excmd.EnvironmentVariable: 132 if err = p.evaluate(ctx, parser.EnvironmentVariable{Name: element.Text}); err != nil { 133 return "", err 134 } 135 case excmd.RuntimeInformation: 136 if err = p.evaluate(ctx, parser.RuntimeInformation{Name: element.Text}); err != nil { 137 return "", err 138 } 139 case excmd.CsvqExpression: 140 if 0 < len(element.Text) { 141 command := element.Text 142 statements, _, err := parser.Parse(command, "", false, p.scope.Tx.Flags.AnsiQuotes) 143 if err != nil { 144 syntaxErr := err.(*parser.SyntaxError) 145 return "", NewPromptEvaluationError(syntaxErr.Message) 146 } 147 148 switch len(statements) { 149 case 1: 150 expr, ok := statements[0].(parser.QueryExpression) 151 if !ok { 152 return "", NewPromptEvaluationError(fmt.Sprintf(query.ErrMsgInvalidValueExpression, command)) 153 } 154 if err = p.evaluate(ctx, expr); err != nil { 155 return "", err 156 } 157 default: 158 return "", NewPromptEvaluationError(fmt.Sprintf(query.ErrMsgInvalidValueExpression, command)) 159 } 160 } 161 } 162 } 163 164 return p.buf.String(), nil 165 } 166 167 func (p *Prompt) evaluate(ctx context.Context, expr parser.QueryExpression) error { 168 val, err := query.Evaluate(ctx, p.scope, expr) 169 if err != nil { 170 if ae, ok := err.(query.Error); ok { 171 err = NewPromptEvaluationError(ae.Message()) 172 } 173 return err 174 } 175 s, _ := query.NewStringFormatter().Format("%s", []value.Primary{val}) 176 _, err = p.buf.WriteString(s) 177 if err != nil { 178 err = NewPromptEvaluationError(err.Error()) 179 } 180 return err 181 } 182 183 func (p *Prompt) StripEscapeSequence(s string) string { 184 p.buf.Reset() 185 186 inEscSeq := false 187 for _, r := range s { 188 if inEscSeq { 189 if unicode.IsLetter(r) { 190 inEscSeq = false 191 } 192 } else if r == 27 { 193 inEscSeq = true 194 } else { 195 p.buf.WriteRune(r) 196 } 197 } 198 199 return p.buf.String() 200 }