gitlab.com/apertussolutions/u-root@v7.0.0+incompatible/cmds/core/tr/tr.go (about)

     1  // Copyright 2018 the u-root Authors. All rights reserved
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // tr - translate or delete characters
     6  
     7  // Synopsis:
     8  //     tr [OPTION]... SET1 [SET2]
     9  
    10  // Description:
    11  //     Translate, squeeze, and/or delete characters from standard input, writing
    12  //     to standard output.
    13  //
    14  //     -d, --delete: delete characters in SET1, do not translate
    15  //
    16  // SETs  are  specified  as  strings of characters. Most represent themselves.
    17  // Interpreted sequences are:
    18  //     \\        backslash
    19  //     \a        audible BEL
    20  //     \b        backspace
    21  //     \f        form feed
    22  //     \n        new line
    23  //     \r        return
    24  //     \t        horizontal tab
    25  //     \v        vertical tab
    26  //     [:alnum:] all letters and digits
    27  //     [:alpha:] all letters
    28  //     [:digit:] all digits
    29  //     [:graph:] all printable characters
    30  //     [:cntrl:] all control characters
    31  //     [:lower:] all lower case letters
    32  //     [:upper:] all upper case letters
    33  //     [:space:] all whitespaces
    34  
    35  package main
    36  
    37  import (
    38  	"bufio"
    39  	"fmt"
    40  	"io"
    41  	"log"
    42  	"os"
    43  	"unicode"
    44  
    45  	flag "github.com/spf13/pflag"
    46  )
    47  
    48  var delete = flag.BoolP("delete", "d", false, "delete characters in SET1, do not translate")
    49  
    50  const name = "tr"
    51  
    52  var escapeChars = map[rune]rune{
    53  	'\\': '\\',
    54  	'a':  '\a',
    55  	'b':  '\b',
    56  	'f':  '\f',
    57  	'n':  '\n',
    58  	'r':  '\r',
    59  	't':  '\t',
    60  	'v':  '\v',
    61  }
    62  
    63  type Set string
    64  
    65  const (
    66  	ALPHA Set = "[:alpha:]"
    67  	DIGIT Set = "[:digit:]"
    68  	GRAPH Set = "[:graph:]"
    69  	CNTRL Set = "[:cntrl:]"
    70  	PUNCT Set = "[:punct:]"
    71  	SPACE Set = "[:space:]"
    72  	ALNUM Set = "[:alnum:]"
    73  	LOWER Set = "[:lower:]"
    74  	UPPER Set = "[:upper:]"
    75  )
    76  
    77  var sets = map[Set]func(r rune) bool{
    78  	ALNUM: func(r rune) bool {
    79  		return unicode.IsLetter(r) || unicode.IsDigit(r)
    80  	},
    81  
    82  	ALPHA: unicode.IsLetter,
    83  	DIGIT: unicode.IsDigit,
    84  	GRAPH: unicode.IsGraphic,
    85  	CNTRL: unicode.IsControl,
    86  	PUNCT: unicode.IsPunct,
    87  	SPACE: unicode.IsSpace,
    88  	LOWER: unicode.IsLower,
    89  	UPPER: unicode.IsUpper,
    90  }
    91  
    92  type transformer struct {
    93  	transform func(r rune) rune
    94  }
    95  
    96  func setToRune(s Set, outRune rune) *transformer {
    97  	check := sets[s]
    98  	return &transformer{
    99  		transform: func(r rune) rune {
   100  			if check(r) {
   101  				return outRune
   102  			}
   103  			return r
   104  		},
   105  	}
   106  }
   107  
   108  func lowerToUpper() *transformer {
   109  	return &transformer{
   110  		transform: func(r rune) rune {
   111  			return unicode.ToUpper(r)
   112  		},
   113  	}
   114  }
   115  
   116  func upperToLower() *transformer {
   117  	return &transformer{
   118  		transform: func(r rune) rune {
   119  			return unicode.ToLower(r)
   120  		},
   121  	}
   122  }
   123  
   124  func runesToRunes(in []rune, out ...rune) *transformer {
   125  	convs := make(map[rune]rune)
   126  	l := len(out)
   127  	for i, r := range in {
   128  		ind := i
   129  		if i > l-1 {
   130  			ind = l - 1
   131  		}
   132  		convs[r] = out[ind]
   133  	}
   134  	return &transformer{
   135  		transform: func(r rune) rune {
   136  			if outRune, ok := convs[r]; ok {
   137  				return outRune
   138  			}
   139  			return r
   140  		},
   141  	}
   142  }
   143  
   144  func (t *transformer) run(r io.Reader, w io.Writer) error {
   145  	in := bufio.NewReader(r)
   146  	out := bufio.NewWriter(w)
   147  
   148  	defer out.Flush()
   149  
   150  	for {
   151  		inRune, size, err := in.ReadRune()
   152  		if inRune == unicode.ReplacementChar {
   153  			// can skip error handling here, because
   154  			// previous operation was in.ReadRune()
   155  			in.UnreadRune()
   156  
   157  			b, err := in.ReadByte()
   158  			if err != nil {
   159  				return fmt.Errorf("read error: %v", err)
   160  			}
   161  
   162  			if err := out.WriteByte(b); err != nil {
   163  				return fmt.Errorf("write error: %v", err)
   164  			}
   165  		} else if size > 0 {
   166  			if outRune := t.transform(inRune); outRune != unicode.ReplacementChar {
   167  				if _, err := out.WriteRune(outRune); err != nil {
   168  					return fmt.Errorf("write error: %v", err)
   169  				}
   170  			}
   171  		}
   172  
   173  		if err != nil {
   174  			if err == io.EOF {
   175  				return nil
   176  			}
   177  			return err
   178  		}
   179  	}
   180  }
   181  
   182  func parse() (*transformer, error) {
   183  	flag.Parse()
   184  
   185  	narg := flag.NArg()
   186  	args := flag.Args()
   187  	switch {
   188  	case narg == 0 || (narg == 1 && !*delete):
   189  		return nil, fmt.Errorf("missing operand")
   190  	case narg > 1 && *delete:
   191  		return nil, fmt.Errorf("extra operand after %q", args[0])
   192  	case narg > 2:
   193  		return nil, fmt.Errorf("extra operand after %q", args[1])
   194  	}
   195  
   196  	set1 := Set(args[0])
   197  	arg1, err := unescape(set1)
   198  	if err != nil {
   199  		return nil, err
   200  	}
   201  
   202  	var set2 Set
   203  	if *delete {
   204  		set2 = Set(unicode.ReplacementChar)
   205  	} else {
   206  		set2 = Set(args[1])
   207  	}
   208  
   209  	if set1 == LOWER && set2 == UPPER {
   210  		return lowerToUpper(), nil
   211  	}
   212  	if set1 == UPPER && set2 == LOWER {
   213  		return upperToLower(), nil
   214  	}
   215  
   216  	if (set2 == LOWER || set2 == UPPER) && (set1 != LOWER && set1 != UPPER) ||
   217  		(set1 == LOWER && set2 == LOWER) || (set1 == UPPER && set2 == UPPER) {
   218  		return nil, fmt.Errorf("misaligned [:upper:] and/or [:lower:] construct")
   219  	}
   220  
   221  	if _, ok := sets[set2]; ok {
   222  		return nil, fmt.Errorf(`the only character classes that may appear in SET2 are 'upper' and 'lower'`)
   223  	}
   224  
   225  	arg2, err := unescape(set2)
   226  	if err != nil {
   227  		return nil, err
   228  	}
   229  	if len(arg2) == 0 {
   230  		return nil, fmt.Errorf("SET2 must be non-empty")
   231  	}
   232  	if _, ok := sets[set1]; ok {
   233  		return setToRune(set1, arg2[0]), nil
   234  	}
   235  	return runesToRunes(arg1, arg2...), nil
   236  }
   237  
   238  func unescape(s Set) ([]rune, error) {
   239  	var out []rune
   240  	var escape bool
   241  	for _, r := range s {
   242  		if escape {
   243  			v, ok := escapeChars[r]
   244  			if !ok {
   245  				return nil, fmt.Errorf("unknown escape sequence '\\%c'", r)
   246  			}
   247  			out = append(out, v)
   248  			escape = false
   249  			continue
   250  		}
   251  
   252  		if r == '\\' {
   253  			escape = true
   254  			continue
   255  		}
   256  
   257  		out = append(out, r)
   258  	}
   259  	return out, nil
   260  }
   261  
   262  func main() {
   263  	t, err := parse()
   264  	if err != nil {
   265  		log.Fatalf("%s: %v\n", name, err)
   266  	}
   267  	if err := t.run(os.Stdin, os.Stdout); err != nil {
   268  		log.Fatalf("%s: %v\n", name, err)
   269  	}
   270  }