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  }