github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/eval/builtin_fn_str.go (about)

     1  package eval
     2  
     3  import (
     4  	"errors"
     5  	"regexp"
     6  	"strconv"
     7  	"strings"
     8  
     9  	"github.com/markusbkk/elvish/pkg/eval/vals"
    10  	"github.com/markusbkk/elvish/pkg/wcwidth"
    11  )
    12  
    13  // String operations.
    14  
    15  // ErrInputOfEawkMustBeString is thrown when eawk gets a non-string input.
    16  var ErrInputOfEawkMustBeString = errors.New("input of eawk must be string")
    17  
    18  //elvdoc:fn <s <=s ==s !=s >s >=s {#str-cmp}
    19  //
    20  // ```elvish
    21  // <s  $string... # less
    22  // <=s $string... # less or equal
    23  // ==s $string... # equal
    24  // !=s $string... # not equal
    25  // >s  $string... # greater
    26  // >=s $string... # greater or equal
    27  // ```
    28  //
    29  // String comparisons. They behave similarly to their number counterparts when
    30  // given multiple arguments. Examples:
    31  //
    32  // ```elvish-transcript
    33  // ~> >s lorem ipsum
    34  // ▶ $true
    35  // ~> ==s 1 1.0
    36  // ▶ $false
    37  // ~> >s 8 12
    38  // ▶ $true
    39  // ```
    40  
    41  //elvdoc:fn wcswidth
    42  //
    43  // ```elvish
    44  // wcswidth $string
    45  // ```
    46  //
    47  // Output the width of `$string` when displayed on the terminal. Examples:
    48  //
    49  // ```elvish-transcript
    50  // ~> wcswidth a
    51  // ▶ 1
    52  // ~> wcswidth lorem
    53  // ▶ 5
    54  // ~> wcswidth 你好,世界
    55  // ▶ 10
    56  // ```
    57  
    58  // TODO(xiaq): Document -override-wcswidth.
    59  
    60  func init() {
    61  	addBuiltinFns(map[string]interface{}{
    62  		"<s":  func(a, b string) bool { return a < b },
    63  		"<=s": func(a, b string) bool { return a <= b },
    64  		"==s": func(a, b string) bool { return a == b },
    65  		"!=s": func(a, b string) bool { return a != b },
    66  		">s":  func(a, b string) bool { return a > b },
    67  		">=s": func(a, b string) bool { return a >= b },
    68  
    69  		"to-string": toString,
    70  
    71  		"base": base,
    72  
    73  		"wcswidth":          wcwidth.Of,
    74  		"-override-wcwidth": wcwidth.Override,
    75  
    76  		"eawk": eawk,
    77  	})
    78  }
    79  
    80  //elvdoc:fn to-string
    81  //
    82  // ```elvish
    83  // to-string $value...
    84  // ```
    85  //
    86  // Convert arguments to string values.
    87  //
    88  // ```elvish-transcript
    89  // ~> to-string foo [a] [&k=v]
    90  // ▶ foo
    91  // ▶ '[a]'
    92  // ▶ '[&k=v]'
    93  // ```
    94  
    95  func toString(fm *Frame, args ...interface{}) error {
    96  	out := fm.ValueOutput()
    97  	for _, a := range args {
    98  		err := out.Put(vals.ToString(a))
    99  		if err != nil {
   100  			return err
   101  		}
   102  	}
   103  	return nil
   104  }
   105  
   106  //elvdoc:fn base
   107  //
   108  // ```elvish
   109  // base $base $number...
   110  // ```
   111  //
   112  // Outputs a string for each `$number` written in `$base`. The `$base` must be
   113  // between 2 and 36, inclusive. Examples:
   114  //
   115  // ```elvish-transcript
   116  // ~> base 2 1 3 4 16 255
   117  // ▶ 1
   118  // ▶ 11
   119  // ▶ 100
   120  // ▶ 10000
   121  // ▶ 11111111
   122  // ~> base 16 1 3 4 16 255
   123  // ▶ 1
   124  // ▶ 3
   125  // ▶ 4
   126  // ▶ 10
   127  // ▶ ff
   128  // ```
   129  
   130  // ErrBadBase is thrown by the "base" builtin if the base is smaller than 2 or
   131  // greater than 36.
   132  var ErrBadBase = errors.New("bad base")
   133  
   134  func base(fm *Frame, b int, nums ...int) error {
   135  	if b < 2 || b > 36 {
   136  		return ErrBadBase
   137  	}
   138  
   139  	out := fm.ValueOutput()
   140  	for _, num := range nums {
   141  		err := out.Put(strconv.FormatInt(int64(num), b))
   142  		if err != nil {
   143  			return err
   144  		}
   145  	}
   146  	return nil
   147  }
   148  
   149  var eawkWordSep = regexp.MustCompile("[ \t]+")
   150  
   151  //elvdoc:fn eawk
   152  //
   153  // ```elvish
   154  // eawk $f $inputs?
   155  // ```
   156  //
   157  // For each [value input](#value-inputs), calls `$f` with the input followed by
   158  // all its fields. A [`break`](./builtin.html#break) command will cause `eawk`
   159  // to stop processing inputs. A [`continue`](./builtin.html#continue) command
   160  // will exit $f, but is ignored by `eawk`.
   161  //
   162  // It should behave the same as the following functions:
   163  //
   164  // ```elvish
   165  // fn eawk {|f @rest|
   166  //   each {|line|
   167  //     var @fields = (re:split '[ \t]+' (str:trim $line " \t"))
   168  //     $f $line $@fields
   169  //   } $@rest
   170  // }
   171  // ```
   172  //
   173  // This command allows you to write code very similar to `awk` scripts using
   174  // anonymous functions. Example:
   175  //
   176  // ```elvish-transcript
   177  // ~> echo " lorem ipsum\n1 2" | awk '{ print $1 }'
   178  // lorem
   179  // 1
   180  // ~> echo " lorem ipsum\n1 2" | eawk {|line a b| put $a }
   181  // ▶ lorem
   182  // ▶ 1
   183  // ```
   184  //
   185  // **Note**: Since Elvish allows variable names consisting solely of digits, you
   186  // can also do the following:
   187  //
   188  // ```elvish-transcript
   189  // ~> echo " lorem ipsum\n1 2" | eawk {|0 1 2| put $1 }
   190  // ▶ lorem
   191  // ▶ 1
   192  // ```
   193  
   194  func eawk(fm *Frame, f Callable, inputs Inputs) error {
   195  	broken := false
   196  	var err error
   197  	inputs(func(v interface{}) {
   198  		if broken {
   199  			return
   200  		}
   201  		line, ok := v.(string)
   202  		if !ok {
   203  			broken = true
   204  			err = ErrInputOfEawkMustBeString
   205  			return
   206  		}
   207  		args := []interface{}{line}
   208  		for _, field := range eawkWordSep.Split(strings.Trim(line, " \t"), -1) {
   209  			args = append(args, field)
   210  		}
   211  
   212  		newFm := fm.Fork("fn of eawk")
   213  		// TODO: Close port 0 of newFm.
   214  		ex := f.Call(newFm, args, NoOpts)
   215  		newFm.Close()
   216  
   217  		if ex != nil {
   218  			switch Reason(ex) {
   219  			case nil, Continue:
   220  				// nop
   221  			case Break:
   222  				broken = true
   223  			default:
   224  				broken = true
   225  				err = ex
   226  			}
   227  		}
   228  	})
   229  	return err
   230  }