github.com/lmorg/murex@v0.0.0-20240217211045-e081c89cd4ef/builtins/core/io/read.go (about)

     1  package io
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"unicode/utf8"
     7  
     8  	"github.com/lmorg/murex/lang"
     9  	"github.com/lmorg/murex/lang/parameters"
    10  	"github.com/lmorg/murex/lang/types"
    11  	"github.com/lmorg/murex/utils/ansi"
    12  	"github.com/lmorg/murex/utils/json"
    13  	"github.com/lmorg/murex/utils/lists"
    14  	"github.com/lmorg/murex/utils/readline"
    15  )
    16  
    17  func init() {
    18  	lang.DefineMethod("read", cmdRead, types.String, types.Null)
    19  	lang.DefineMethod("tread", cmdTread, types.String, types.Null)
    20  }
    21  
    22  func cmdRead(p *lang.Process) error {
    23  	return read(p, types.String, 0)
    24  }
    25  
    26  func cmdTread(p *lang.Process) error {
    27  	dt, err := p.Parameters.String(0)
    28  	if err != nil {
    29  		return err
    30  	}
    31  	return read(p, dt, 1)
    32  }
    33  
    34  const (
    35  	flagReadDefault  = "--default"
    36  	flagReadPrompt   = "--prompt"
    37  	flagReadVariable = "--variable"
    38  	flagReadDataType = "--datatype"
    39  	flagReadMask     = "--mask"
    40  	flagReadComplete = "--autocomplete"
    41  )
    42  
    43  var readArguments = parameters.Arguments{
    44  	Flags: map[string]string{
    45  		flagReadDefault:  types.String,
    46  		flagReadPrompt:   types.String,
    47  		flagReadVariable: types.String,
    48  		flagReadDataType: types.String,
    49  		flagReadMask:     types.String,
    50  		flagReadComplete: types.String,
    51  	},
    52  	AllowAdditional: true,
    53  }
    54  
    55  func read(p *lang.Process, dt string, paramAdjust int) error {
    56  	p.Stdout.SetDataType(types.Null)
    57  
    58  	if p.Background.Get() {
    59  		return errors.New("background processes cannot read from stdin")
    60  	}
    61  
    62  	var prompt, varName, defaultVal, mask, complete string
    63  
    64  	flags, additional, err := p.Parameters.ParseFlags(&readArguments)
    65  	if err != nil {
    66  		return fmt.Errorf("cannot parse parameters: %s", err.Error())
    67  	}
    68  
    69  	if len(additional) == 0 {
    70  		prompt = flags[flagReadPrompt]
    71  		varName = flags[flagReadVariable]
    72  		defaultVal = flags[flagReadDefault]
    73  		datatype := flags[flagReadDataType]
    74  		mask = flags[flagReadMask]
    75  		complete = flags[flagReadComplete]
    76  
    77  		if datatype != "" {
    78  			dt = datatype
    79  		}
    80  
    81  		if varName == "" {
    82  			varName = "read"
    83  		}
    84  
    85  	} else {
    86  		if p.IsMethod {
    87  			b, err := p.Stdin.ReadAll()
    88  			if err != nil {
    89  				return err
    90  			}
    91  			prompt = string(b)
    92  
    93  			varName, err = p.Parameters.String(0 + paramAdjust)
    94  			if err != nil {
    95  				return err
    96  			}
    97  		} else {
    98  			varName, err = p.Parameters.String(0 + paramAdjust)
    99  			if err != nil {
   100  				return err
   101  			}
   102  			prompt = p.Parameters.StringAllRange(1+paramAdjust, -1)
   103  		}
   104  	}
   105  
   106  	prompt = ansi.ExpandConsts(prompt)
   107  
   108  	rl := readline.NewInstance()
   109  	rl.SetPrompt(prompt)
   110  	rl.History = new(readline.NullHistory)
   111  
   112  	err = tabCompleter(rl, []byte(complete))
   113  	if err != nil {
   114  		return err
   115  	}
   116  
   117  	if len(mask) > 0 {
   118  		rl.PasswordMask, _ = utf8.DecodeRuneInString(mask)
   119  	}
   120  
   121  	s, err := rl.Readline()
   122  	if err != nil {
   123  		return err
   124  	}
   125  
   126  	if s == "" {
   127  		s = defaultVal
   128  		//tty.Stdout.WriteString(s)
   129  	}
   130  
   131  	v, err := types.ConvertGoType(s, dt)
   132  	if err != nil {
   133  		return err
   134  	}
   135  
   136  	return p.Variables.Set(p, varName, v, dt)
   137  }
   138  
   139  func tabCompleter(rl *readline.Instance, b []byte) error {
   140  	if len(b) == 0 {
   141  		return nil
   142  	}
   143  
   144  	maxRows, _ := lang.ShellProcess.Config.Get("shell", "max-suggestions", types.Integer)
   145  	rl.MaxTabCompleterRows = maxRows.(int)
   146  
   147  	var v interface{}
   148  	err := json.UnmarshalMurex(b, &v)
   149  	if err != nil {
   150  		return fmt.Errorf("cannot unmarshal JSON input for `read`'s autocomplete: %s", err.Error())
   151  	}
   152  
   153  	switch t := v.(type) {
   154  	case []string:
   155  		rl.TabCompleter = func(r []rune, i int, dtc readline.DelayedTabContext) *readline.TabCompleterReturnT {
   156  			tcr := new(readline.TabCompleterReturnT)
   157  			if i > len(r) {
   158  				return tcr
   159  			}
   160  			tcr.Prefix = string(r[:i])
   161  			tcr.Suggestions = lists.CropPartial(t, tcr.Prefix)
   162  			return tcr
   163  		}
   164  
   165  	case []interface{}:
   166  		// this is horribly inefficient POC code
   167  		s := make([]string, len(t))
   168  		for i := range t {
   169  			s[i] = fmt.Sprint(t[i])
   170  		}
   171  		rl.TabCompleter = func(r []rune, i int, dtc readline.DelayedTabContext) *readline.TabCompleterReturnT {
   172  			tcr := new(readline.TabCompleterReturnT)
   173  			if i > len(r) {
   174  				return tcr
   175  			}
   176  			tcr.Prefix = string(r[:i])
   177  			tcr.Suggestions = lists.CropPartial(s, tcr.Prefix)
   178  			return tcr
   179  		}
   180  
   181  	case map[string]string:
   182  		// this is horribly inefficient POC code
   183  		s := make([]string, len(t))
   184  		var i int
   185  		for key := range t {
   186  			s[i] = key
   187  			i++
   188  		}
   189  		rl.TabCompleter = func(r []rune, i int, dtc readline.DelayedTabContext) *readline.TabCompleterReturnT {
   190  			tcr := new(readline.TabCompleterReturnT)
   191  			if i > len(r) {
   192  				return tcr
   193  			}
   194  			tcr.Prefix = string(r[:i])
   195  			tcr.Suggestions = lists.CropPartial(s, tcr.Prefix)
   196  			tcr.Descriptions = lists.CropPartialMapKeys(t, tcr.Prefix)
   197  			tcr.DisplayType = readline.TabDisplayList
   198  			return tcr
   199  		}
   200  
   201  	case map[string]interface{}:
   202  		// this is horribly inefficient POC code
   203  		s := make([]string, len(t))
   204  		var i int
   205  		m := make(map[string]string)
   206  		for key, val := range t {
   207  			s[i] = key
   208  			m[key] = fmt.Sprint(val)
   209  			i++
   210  		}
   211  		rl.TabCompleter = func(r []rune, i int, dtc readline.DelayedTabContext) *readline.TabCompleterReturnT {
   212  			tcr := new(readline.TabCompleterReturnT)
   213  			if i > len(r) {
   214  				return tcr
   215  			}
   216  			tcr.Prefix = string(r[:i])
   217  			tcr.Suggestions = lists.CropPartial(s, tcr.Prefix)
   218  			tcr.Descriptions = lists.CropPartialMapKeys(m, tcr.Prefix)
   219  			tcr.DisplayType = readline.TabDisplayList
   220  			return tcr
   221  		}
   222  
   223  	default:
   224  		return fmt.Errorf("autocomplete JSON unmarshalled to unsupported object %T. Expecting either a string or a map", t)
   225  	}
   226  
   227  	return nil
   228  }