github.com/lmorg/murex@v0.0.0-20240217211045-e081c89cd4ef/lang/variables.go (about) 1 package lang 2 3 import ( 4 "fmt" 5 "os" 6 "strconv" 7 "strings" 8 "sync" 9 "time" 10 11 "github.com/lmorg/murex/builtins/pipes/streams" 12 "github.com/lmorg/murex/lang/ref" 13 "github.com/lmorg/murex/lang/types" 14 "github.com/lmorg/murex/utils/alter" 15 "github.com/lmorg/murex/utils/envvars" 16 "github.com/lmorg/murex/utils/json" 17 "github.com/lmorg/murex/utils/lists" 18 ) 19 20 func errVariableReserved(name string) error { 21 return fmt.Errorf("cannot set a reserved variable: %s", name) 22 } 23 24 const ErrDoesNotExist = "does not exist" 25 26 func errVarNotExist(name string) error { 27 return fmt.Errorf("variable '%s' %s", name, ErrDoesNotExist) 28 } 29 30 func errVarCannotUpdateNested(name string, err error) error { 31 return fmt.Errorf("cannot update element inside %s: %s", name, err.Error()) 32 } 33 34 func errVarCannotUpdateIndexOrElement(name string) error { 35 return fmt.Errorf("cannot update `[ indexes ]` nor `[[ elements ]]`, these are immutable objects.\nPlease reference values using dot notation instead, eg $variable_name.path.to.element\nVariable : %s", name) 36 } 37 38 func errVarNoParam(i int, err error) error { 39 return fmt.Errorf("variable '%d' is not set because the scope returned the following error when querying parameter %d: %s", i, i, err.Error()) 40 } 41 42 func errVarZeroLengthPath(name string) error { 43 return fmt.Errorf("zero length path in variable name `%s`", name) 44 } 45 46 func errVarCannotGetProperty(name, path string, err error) error { 47 return fmt.Errorf("cannot get property '%s' for variable '%s'.\n%s.\nif this wasn't intended to be a property then try surrounding your variable name with parenthesis, eg:\n` ... $(%s).%s ... `", 48 path, name, err.Error(), name, path) 49 } 50 51 func errVarCannotStoreVariable(name string, err error) error { 52 return fmt.Errorf("cannot store variable '%s': %s", name, err.Error()) 53 } 54 55 // Reserved variable names. Set as constants so any typos of these names within 56 // the code will be raised as compiler errors 57 const ( 58 DOT = "" 59 SELF = "SELF" 60 ARGV = "ARGV" 61 ARGS = "ARGS" 62 PARAMS = "PARAMS" 63 MUREX_EXE = "MUREX_EXE" 64 MUREX_ARGS = "MUREX_ARGS" 65 MUREX_ARGV = "MUREX_ARGV" 66 HOSTNAME = "HOSTNAME" 67 PWD = "PWD" 68 ENV = "ENV" 69 GLOBAL = "GLOBAL" 70 MODULE = "MOD" 71 COLUMNS = "COLUMNS" 72 ) 73 74 var ReservedVariableNames = []string{ 75 SELF, ARGV, ARGS, PARAMS, MUREX_EXE, MUREX_ARGS, MUREX_ARGV, 76 HOSTNAME, PWD, ENV, MODULE, GLOBAL, COLUMNS, 77 } 78 79 // Variables is a table of all the variables. This will be local to the scope's 80 // process 81 type Variables struct { 82 process *Process // only needed for variables 83 vars map[string]*variable 84 mutex sync.Mutex 85 global bool 86 } 87 88 // NewVariables creates a new variable table 89 func NewVariables(p *Process) *Variables { 90 v := new(Variables) 91 v.vars = make(map[string]*variable) 92 v.process = p 93 return v 94 } 95 96 // NewGlobals creates a new global variable table 97 func NewGlobals() *Variables { 98 v := new(Variables) 99 v.vars = make(map[string]*variable) 100 v.process = ShellProcess 101 v.global = true 102 return v 103 } 104 105 // variable is an individual variable or global variable 106 type variable struct { 107 DataType string 108 Value interface{} 109 String string 110 IsInterface bool 111 Modify time.Time 112 FileRef *ref.File // only needed for globals 113 } 114 115 // GetValue return the value of a variable. If a variable does not exist then 116 // GetValue will return nil. Please check if p.Config.Get("proc", "strict-vars", "bool") 117 // matters for your usage of GetValue because this API doesn't care. If in doubt 118 // use GetString instead. 119 func (v *Variables) GetValue(path string) (interface{}, error) { 120 if path == "." { 121 return v.getValue(DOT) 122 } 123 124 split := strings.Split(path, ".") 125 switch len(split) { 126 case 0: 127 return nil, errVarZeroLengthPath(path) 128 case 1: 129 return v.getValue(split[0]) 130 default: 131 val, err := v.getValue(split[0]) 132 if err != nil { 133 return nil, err 134 } 135 136 propertyPath := strings.Join(split[1:], ".") 137 value, err := ElementLookup(val, "."+propertyPath) 138 if err != nil { 139 err = errVarCannotGetProperty(split[0], propertyPath, err) 140 } 141 return value, err 142 } 143 } 144 145 func (v *Variables) getValue(name string) (interface{}, error) { 146 switch name { 147 case ENV: 148 return getEnvVarValue(v), nil 149 150 case GLOBAL: 151 return getGlobalValues(), nil 152 153 case MODULE: 154 return ModuleVariables.GetValues(v.process), nil 155 156 case SELF: 157 return getVarSelf(v.process), nil 158 159 case ARGV, ARGS: 160 return getVarArgs(v.process), nil 161 162 case PARAMS: 163 return v.process.Scope.Parameters.StringArray(), nil 164 165 case MUREX_EXE: 166 return getVarMurexExeValue() 167 168 case MUREX_ARGV, MUREX_ARGS: 169 return getVarMurexArgs(), nil 170 171 case HOSTNAME: 172 return getHostname(), nil 173 174 case PWD: 175 return getPwdValue() 176 177 case COLUMNS: 178 return getVarColumnsValue(), nil 179 180 case "0": 181 return v.process.Scope.Name.String(), nil 182 } 183 184 if i, err := strconv.Atoi(name); err == nil && i > 0 { 185 s, err := v.process.Scope.Parameters.String(i - 1) 186 if err != nil { 187 return nil, nil 188 } 189 return s, nil 190 } 191 192 if v.global { 193 return v.getValueValue(name), nil 194 } 195 196 value := v.getValueValue(name) 197 if value != nil { 198 return value, nil 199 } 200 201 value = GlobalVariables.getValueValue(name) 202 if value != nil { 203 return value, nil 204 } 205 206 // variable not found so lets fallback to the environmental variables 207 s, exists := os.LookupEnv(name) 208 if exists { 209 return v.getEnvValueValue(name, s) 210 } 211 212 strictVars, err := v.process.Config.Get("proc", "strict-vars", "bool") 213 if err != nil || strictVars.(bool) { 214 return nil, errVarNotExist(name) 215 } 216 return nil, nil 217 } 218 219 func (v *Variables) getValueValue(name string) interface{} { 220 v.mutex.Lock() 221 variable := v.vars[name] 222 if variable == nil { 223 v.mutex.Unlock() 224 return nil 225 } 226 227 if variable.IsInterface { 228 value := variable.Value.(MxInterface).GetValue() 229 230 v.mutex.Unlock() 231 return value 232 } 233 234 value := variable.Value 235 236 v.mutex.Unlock() 237 return value 238 } 239 240 func (v *Variables) getEnvValueValue(name, str string) (interface{}, error) { 241 dt := getEnvVarDataType(name) 242 if dt == types.String { 243 return str, nil 244 } 245 246 value, err := UnmarshalDataBuffered(v.process, []byte(str), dt) 247 return value, err 248 } 249 250 // GetString returns a string representation of the data stored in the requested variable 251 func (v *Variables) GetString(path string) (string, error) { 252 if path == "." { 253 return v.getString(DOT) 254 } 255 256 split := strings.Split(path, ".") 257 switch len(split) { 258 case 0: 259 return "", errVarZeroLengthPath(path) 260 case 1: 261 return v.getString(split[0]) 262 default: 263 val, err := v.getValue(split[0]) 264 if err != nil { 265 return "", err 266 } 267 268 propertyPath := strings.Join(split[1:], ".") 269 val, err = ElementLookup(val, "."+propertyPath) 270 if err != nil { 271 return "", errVarCannotGetProperty(split[0], propertyPath, err) 272 } 273 274 switch val.(type) { 275 case int, float64, string, bool, nil: 276 s, err := types.ConvertGoType(val, types.String) 277 if err != nil { 278 return "", err 279 } 280 return s.(string), nil 281 default: 282 dataType := v.GetDataType(split[0]) 283 b, err := MarshalData(v.process, dataType, val) 284 return string(b), err 285 } 286 } 287 } 288 289 func (v *Variables) getString(name string) (string, error) { 290 switch name { 291 case ENV: 292 b, err := json.Marshal(getEnvVarString(), v.process.Stdout.IsTTY()) 293 return string(b), err 294 295 case GLOBAL: 296 b, err := json.Marshal(getGlobalValues(), v.process.Stdout.IsTTY()) 297 return string(b), err 298 299 case MODULE: 300 b, err := json.Marshal(ModuleVariables.GetValues(v.process), v.process.Stdout.IsTTY()) 301 return string(b), err 302 303 case SELF: 304 b, err := json.Marshal(getVarSelf(v.process), v.process.Stdout.IsTTY()) 305 return string(b), err 306 307 case ARGV, ARGS: 308 b, err := json.Marshal(getVarArgs(v.process), v.process.Stdout.IsTTY()) 309 return string(b), err 310 311 case PARAMS: 312 b, err := json.Marshal(v.process.Scope.Parameters.StringArray(), v.process.Stdout.IsTTY()) 313 return string(b), err 314 315 case MUREX_EXE: 316 return os.Executable() 317 318 case MUREX_ARGV, MUREX_ARGS: 319 b, err := json.Marshal(getVarMurexArgs(), v.process.Stdout.IsTTY()) 320 return string(b), err 321 322 case HOSTNAME: 323 return getHostname(), nil 324 325 case PWD: 326 return os.Getwd() 327 328 case COLUMNS: 329 return strconv.Itoa(getVarColumnsValue()), nil 330 331 case "0": 332 return v.process.Scope.Name.String(), nil 333 } 334 335 if i, err := strconv.Atoi(name); err == nil && i > 0 { 336 s, err := v.process.Scope.Parameters.String(i - 1) 337 if err != nil { 338 return "", errVarNoParam(i, err) 339 } 340 return s, nil 341 } 342 343 if v.global { 344 val, _ := v.getStringValue(name) 345 return val, nil 346 } 347 348 s, exists := v.getStringValue(name) 349 if exists { 350 return s, nil 351 } 352 353 s, exists = GlobalVariables.getStringValue(name) 354 if exists { 355 return s, nil 356 } 357 358 // variable not found so lets fallback to the environmental variables 359 s, exists = os.LookupEnv(name) 360 361 strictVars, err := v.process.Config.Get("proc", "strict-vars", "bool") 362 if (err != nil || strictVars.(bool)) && !exists { 363 return "", errVarNotExist(name) 364 } 365 366 return s, nil 367 } 368 369 func (v *Variables) getStringValue(name string) (string, bool) { 370 v.mutex.Lock() 371 variable := v.vars[name] 372 if variable == nil { 373 v.mutex.Unlock() 374 return "", false 375 } 376 377 if variable.IsInterface { 378 s := variable.Value.(MxInterface).GetString() 379 380 v.mutex.Unlock() 381 return s, true 382 } 383 384 s := variable.String 385 v.mutex.Unlock() 386 return s, true 387 } 388 389 // GetDataType returns the data type of the variable stored in the referenced VarTable 390 func (v *Variables) GetDataType(path string) string { 391 if path == "." { 392 return v.getDataType(DOT) 393 } 394 395 split := strings.Split(path, ".") 396 switch len(split) { 397 case 0: 398 return "" 399 case 1: 400 return v.getDataType(split[0]) 401 default: 402 switch split[0] { 403 case ENV: 404 return getEnvVarDataType(split[1]) 405 case GLOBAL: 406 return getGlobalDataType(split[1]) 407 case MODULE: 408 return ModuleVariables.GetDataType(v.process, split[1]) 409 } 410 411 val, err := v.getValue(split[0]) 412 if err != nil { 413 return v.getDataType(split[0]) 414 } 415 416 val, err = ElementLookup(val, "."+strings.Join(split[1:], ".")) 417 if err != nil { 418 return v.getDataType(split[0]) 419 } 420 421 switch val.(type) { 422 case int: 423 return types.Integer 424 case float64: 425 return types.Number 426 case string, []byte, []rune: 427 return types.String 428 case bool: 429 return types.Boolean 430 case nil: 431 return types.Null 432 default: 433 return v.getDataType(split[0]) 434 } 435 } 436 } 437 438 func (v *Variables) getDataType(name string) string { 439 switch name { 440 case ENV, GLOBAL, MODULE: 441 return types.Json 442 443 case SELF: 444 return types.Json 445 446 case ARGV, ARGS: 447 return types.Json 448 449 case PARAMS: 450 return types.Json 451 452 case MUREX_EXE: 453 return types.Path 454 455 case MUREX_ARGV, MUREX_ARGS: 456 return types.Json 457 458 case PWD: 459 return types.Path 460 461 case COLUMNS: 462 return types.Integer 463 464 case "0": 465 return types.String 466 } 467 468 if i, err := strconv.Atoi(name); err == nil && i > 0 { 469 if i >= v.process.Scope.Parameters.Len() { 470 return "" 471 } 472 return types.String 473 } 474 475 if v.global { 476 dt, _ := v.getDataTypeValue(name) 477 return dt 478 } 479 480 s, exists := v.getDataTypeValue(name) 481 if exists { 482 return s 483 } 484 485 s, exists = GlobalVariables.getDataTypeValue(name) 486 if exists { 487 return s 488 } 489 490 // variable not found so lets fallback to the environmental variables 491 _, exists = os.LookupEnv(name) 492 if exists { 493 return getEnvVarDataType(name) 494 } 495 496 return types.Null 497 } 498 499 func (v *Variables) getDataTypeValue(name string) (string, bool) { 500 v.mutex.Lock() 501 variable := v.vars[name] 502 if variable == nil { 503 v.mutex.Unlock() 504 return "", false 505 } 506 507 dt := variable.DataType 508 v.mutex.Unlock() 509 return dt, true 510 } 511 512 func getGlobalDataType(name string) (dt string) { 513 GlobalVariables.mutex.Lock() 514 v := GlobalVariables.vars[name] 515 if v != nil { 516 dt = v.DataType 517 } 518 GlobalVariables.mutex.Unlock() 519 return 520 } 521 522 func getEnvVarDataType(name string) string { 523 for dt, v := range envDataTypes { 524 if lists.Match(v, name) { 525 return dt 526 } 527 } 528 529 return types.String 530 } 531 532 func (v *Variables) Set(p *Process, path string, value interface{}, dataType string) error { 533 if strings.Contains(path, "[") { 534 return errVarCannotUpdateIndexOrElement(path) 535 } 536 537 split := strings.Split(path, ".") 538 switch len(split) { 539 case 0: 540 return errVarZeroLengthPath(path) 541 case 1: 542 return v.set(p, split[0], value, dataType, nil) 543 default: 544 variable, err := v.getValue(split[0]) 545 if err != nil { 546 return errVarCannotUpdateNested(split[0], err) 547 } 548 549 variable, err = alter.Alter(p.Context, variable, split[1:], value) 550 if err != nil { 551 return errVarCannotUpdateNested(split[0], err) 552 } 553 err = v.set(p, split[0], variable, v.getNestedDataType(split[0], dataType), split[1:]) 554 if err != nil { 555 return errVarCannotUpdateNested(split[0], err) 556 } 557 return nil 558 } 559 } 560 561 func (v *Variables) getNestedDataType(name string, dataType string) string { 562 switch name { 563 case GLOBAL, MODULE: 564 return dataType 565 default: 566 return v.GetDataType(name) 567 } 568 } 569 570 // Set writes a variable 571 func (v *Variables) set(p *Process, name string, value interface{}, dataType string, changePath []string) error { 572 switch name { 573 case SELF, ARGV, ARGS, PARAMS, MUREX_EXE, MUREX_ARGS, MUREX_ARGV, HOSTNAME, PWD, COLUMNS, "_": 574 return errVariableReserved(name) 575 case ENV: 576 return setEnvVar(value, changePath) 577 case GLOBAL: 578 return setGlobalVar(p, value, changePath, dataType) 579 case MODULE: 580 return ModuleVariables.Set(p, value, changePath, dataType) 581 case DOT: 582 goto notReserved 583 } 584 for _, r := range name { 585 if r < '0' || r > '9' { 586 goto notReserved 587 } 588 } 589 return errVariableReserved(name) 590 591 notReserved: 592 593 fileRef := v.process.FileRef 594 if v.global { 595 fileRef = p.FileRef 596 } 597 598 mxi := MxInterfaces[dataType] 599 if mxi != nil { 600 mxvar := v.vars[name] 601 if mxvar != nil && mxvar.IsInterface { 602 603 v.mutex.Lock() 604 605 err := mxvar.Value.(MxInterface).Set(value, changePath) 606 if err != nil { 607 v.vars[name].Modify = time.Now() 608 } 609 610 v.mutex.Unlock() 611 612 return err 613 } 614 615 s, _, err := convertDataType(p, value, dataType, &name) 616 if err != nil { 617 return err 618 } 619 620 mxi, err := mxi.New(s) 621 if err != nil { 622 return err 623 } 624 625 v.mutex.Lock() 626 627 v.vars[name] = &variable{ 628 Value: mxi, 629 DataType: dataType, 630 Modify: time.Now(), 631 FileRef: fileRef, 632 IsInterface: true, 633 } 634 635 v.mutex.Unlock() 636 637 return nil 638 } 639 640 s, iface, err := convertDataType(p, value, dataType, &name) 641 if err != nil { 642 return err 643 } 644 645 v.mutex.Lock() 646 647 v.vars[name] = &variable{ 648 Value: iface, 649 String: s, 650 DataType: dataType, 651 Modify: time.Now(), 652 FileRef: fileRef, 653 } 654 655 v.mutex.Unlock() 656 657 return nil 658 } 659 660 func setGlobalVar(p *Process, v interface{}, changePath []string, dataType string) (err error) { 661 if len(changePath) == 0 { 662 return fmt.Errorf("invalid use of $%s. Expecting a global variable name, eg `$%s.example`", GLOBAL, GLOBAL) 663 } 664 665 switch t := v.(type) { 666 case map[string]interface{}: 667 return GlobalVariables.Set(ShellProcess, changePath[0], t[changePath[0]], dataType) 668 669 default: 670 return fmt.Errorf("expecting a map of global variables. Instead got a %T", t) 671 } 672 } 673 674 func setEnvVar(v interface{}, changePath []string) (err error) { 675 var value interface{} 676 677 if len(changePath) == 0 { 678 return fmt.Errorf("invalid use of $%s. Expecting an environmental variable name, eg `$%s.EXAMPLE`", ENV, ENV) 679 } 680 681 switch t := v.(type) { 682 case map[string]interface{}: 683 value, err = types.ConvertGoType(t[changePath[0]], types.String) 684 if err != nil { 685 return err 686 } 687 688 default: 689 return fmt.Errorf("expecting a map of environmental variables. Instead got a %T", t) 690 } 691 692 return os.Setenv(changePath[0], value.(string)) 693 } 694 695 func convertDataType(p *Process, value interface{}, dataType string, name *string) (string, interface{}, error) { 696 var ( 697 s string 698 iface interface{} 699 err error 700 ) 701 702 switch v := value.(type) { 703 case float64, int, bool, nil: 704 s, err = varConvertPrimitive(value, name) 705 iface = value 706 case string: 707 s = v 708 if dataType != types.String && dataType != types.Generic { 709 iface, err = varConvertString(p, []byte(v), dataType, name) 710 } else { 711 iface = s 712 } 713 case []byte: 714 s = string(v) 715 if dataType != types.String && dataType != types.Generic { 716 iface, err = varConvertString(p, v, dataType, name) 717 } else { 718 iface = s 719 } 720 case []rune: 721 s = string(v) 722 if dataType != types.String && dataType != types.Generic { 723 iface, err = varConvertString(p, []byte(string(v)), dataType, name) 724 } else { 725 iface = s 726 } 727 default: 728 s, err = varConvertInterface(p, v, dataType, name) 729 iface = value 730 } 731 return s, iface, err 732 } 733 734 func varConvertPrimitive(value interface{}, name *string) (string, error) { 735 s, err := types.ConvertGoType(value, types.String) 736 if err != nil { 737 return "", errVarCannotStoreVariable(*name, err) 738 } 739 return s.(string), nil 740 } 741 742 func varConvertString(parent *Process, value []byte, dataType string, name *string) (interface{}, error) { 743 UnmarshalData := Unmarshallers[dataType] 744 745 // no unmarshaller exists so lets just return the bare string 746 if UnmarshalData == nil { 747 return string(value), nil 748 } 749 750 p := new(Process) 751 p.Config = parent.Config 752 p.Stdin = streams.NewStdin() 753 _, err := p.Stdin.Write([]byte(value)) 754 if err != nil { 755 return nil, errVarCannotStoreVariable(*name, err) 756 } 757 v, err := UnmarshalData(p) 758 if err != nil { 759 return nil, errVarCannotStoreVariable(*name, err) 760 } 761 return v, nil 762 } 763 764 func varConvertInterface(p *Process, value interface{}, dataType string, name *string) (string, error) { 765 MarshalData := Marshallers[dataType] 766 767 // no marshaller exists so lets just return the bare string 768 if MarshalData == nil { 769 s, err := types.ConvertGoType(value, types.String) 770 if err != nil { 771 return "", errVarCannotStoreVariable(*name, err) 772 } 773 return s.(string), nil 774 } 775 776 b, err := MarshalData(p, value) 777 if err != nil { 778 return "", errVarCannotStoreVariable(*name, err) 779 } 780 s, err := types.ConvertGoType(b, types.String) 781 if err != nil { 782 return "", errVarCannotStoreVariable(*name, err) 783 } 784 return s.(string), nil 785 } 786 787 // Unset removes a variable from the table 788 func (v *Variables) Unset(name string) error { 789 v.mutex.Lock() 790 variable := v.vars[name] 791 if variable == nil { 792 v.mutex.Unlock() 793 return errVarNotExist(name) 794 } 795 796 delete(v.vars, name) 797 v.mutex.Unlock() 798 return nil 799 } 800 801 // Dump returns a map of the structure of all variables in scope 802 func (v *Variables) Dump() interface{} { 803 v.mutex.Lock() 804 vars := v.vars // TODO: This isn't thread safe 805 v.mutex.Unlock() 806 807 return vars 808 } 809 810 // DumpVariables returns a map of the variables and values for all variables 811 // in scope. 812 func DumpVariables(p *Process) map[string]interface{} { 813 m := make(map[string]interface{}) 814 815 envvars.All(m) 816 817 GlobalVariables.mutex.Lock() 818 for name, v := range GlobalVariables.vars { 819 m[name] = v.Value 820 } 821 GlobalVariables.mutex.Unlock() 822 823 p.Variables.mutex.Lock() 824 for name, v := range p.Variables.vars { 825 m[name] = v.Value 826 } 827 p.Variables.mutex.Unlock() 828 829 m[SELF], _ = p.Variables.GetValue(SELF) 830 m[ARGV], _ = p.Variables.GetValue(ARGV) 831 m[PARAMS], _ = p.Variables.GetValue(PARAMS) 832 m[MUREX_EXE], _ = p.Variables.GetValue(MUREX_EXE) 833 m[MUREX_ARGV], _ = p.Variables.GetValue(MUREX_ARGV) 834 m[HOSTNAME], _ = p.Variables.GetValue(HOSTNAME) 835 m[PWD], _ = p.Variables.GetValue(PWD) 836 m[ENV], _ = p.Variables.GetValue(ENV) 837 m[GLOBAL] = ".." 838 m[COLUMNS], _ = p.Variables.GetValue(COLUMNS) 839 840 return m 841 }