github.com/kolbycrouch/elvish@v0.14.1-0.20210614162631-215b9ac1c423/pkg/eval/builtin_fn_str.go (about)

     1  package eval
     2  
     3  import (
     4  	"errors"
     5  	"regexp"
     6  	"strconv"
     7  	"strings"
     8  
     9  	"src.elv.sh/pkg/eval/vals"
    10  	"src.elv.sh/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{}) {
    96  	out := fm.OutputChan()
    97  	for _, a := range args {
    98  		out <- vals.ToString(a)
    99  	}
   100  }
   101  
   102  //elvdoc:fn base
   103  //
   104  // ```elvish
   105  // base $base $number...
   106  // ```
   107  //
   108  // Outputs a string for each `$number` written in `$base`. The `$base` must be
   109  // between 2 and 36, inclusive. Examples:
   110  //
   111  // ```elvish-transcript
   112  // ~> base 2 1 3 4 16 255
   113  // ▶ 1
   114  // ▶ 11
   115  // ▶ 100
   116  // ▶ 10000
   117  // ▶ 11111111
   118  // ~> base 16 1 3 4 16 255
   119  // ▶ 1
   120  // ▶ 3
   121  // ▶ 4
   122  // ▶ 10
   123  // ▶ ff
   124  // ```
   125  
   126  // ErrBadBase is thrown by the "base" builtin if the base is smaller than 2 or
   127  // greater than 36.
   128  var ErrBadBase = errors.New("bad base")
   129  
   130  func base(fm *Frame, b int, nums ...int) error {
   131  	if b < 2 || b > 36 {
   132  		return ErrBadBase
   133  	}
   134  
   135  	out := fm.OutputChan()
   136  	for _, num := range nums {
   137  		out <- strconv.FormatInt(int64(num), b)
   138  	}
   139  	return nil
   140  }
   141  
   142  var eawkWordSep = regexp.MustCompile("[ \t]+")
   143  
   144  //elvdoc:fn eawk
   145  //
   146  // ```elvish
   147  // eawk $f $input-list?
   148  // ```
   149  //
   150  // For each input, call `$f` with the input followed by all its fields. A
   151  // [`break`](./builtin.html#break) command will cause `eawk` to stop processing inputs. A
   152  // [`continue`](./builtin.html#continue) command will exit $f, but is ignored by `eawk`.
   153  //
   154  // It should behave the same as the following functions:
   155  //
   156  // ```elvish
   157  // fn eawk [f @rest]{
   158  //   each [line]{
   159  //     @fields = (re:split '[ \t]+'
   160  //     (re:replace '^[ \t]+|[ \t]+$' '' $line))
   161  //     $f $line $@fields
   162  //   } $@rest
   163  // }
   164  // ```
   165  //
   166  // This command allows you to write code very similar to `awk` scripts using
   167  // anonymous functions. Example:
   168  //
   169  // ```elvish-transcript
   170  // ~> echo ' lorem ipsum
   171  // 1 2' | awk '{ print $1 }'
   172  // lorem
   173  // 1
   174  // ~> echo ' lorem ipsum
   175  // 1 2' | eawk [line a b]{ put $a }
   176  // ▶ lorem
   177  // ▶ 1
   178  // ```
   179  
   180  func eawk(fm *Frame, f Callable, inputs Inputs) error {
   181  	broken := false
   182  	var err error
   183  	inputs(func(v interface{}) {
   184  		if broken {
   185  			return
   186  		}
   187  		line, ok := v.(string)
   188  		if !ok {
   189  			broken = true
   190  			err = ErrInputOfEawkMustBeString
   191  			return
   192  		}
   193  		args := []interface{}{line}
   194  		for _, field := range eawkWordSep.Split(strings.Trim(line, " \t"), -1) {
   195  			args = append(args, field)
   196  		}
   197  
   198  		newFm := fm.fork("fn of eawk")
   199  		// TODO: Close port 0 of newFm.
   200  		ex := f.Call(newFm, args, NoOpts)
   201  		newFm.Close()
   202  
   203  		if ex != nil {
   204  			switch Reason(ex) {
   205  			case nil, Continue:
   206  				// nop
   207  			case Break:
   208  				broken = true
   209  			default:
   210  				broken = true
   211  				err = ex
   212  			}
   213  		}
   214  	})
   215  	return err
   216  }