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 }