github.com/mvdan/u-root-coreutils@v0.0.0-20230122170626-c2eef2898555/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 del = flag.BoolP("delete", "d", false, "delete characters in SET1, do not translate")
    49  
    50  type command struct {
    51  	stdin  io.Reader
    52  	stdout io.Writer
    53  	del    bool
    54  	tr     *transformer
    55  }
    56  
    57  func newCommand(in io.Reader, out io.Writer, args []string, del bool) (*command, error) {
    58  	tr, err := parse(args, del)
    59  	if err != nil {
    60  		return nil, err
    61  	}
    62  
    63  	return &command{
    64  		stdin:  in,
    65  		stdout: out,
    66  		del:    del,
    67  		tr:     tr,
    68  	}, nil
    69  }
    70  
    71  const name = "tr"
    72  
    73  var escapeChars = map[rune]rune{
    74  	'\\': '\\',
    75  	'a':  '\a',
    76  	'b':  '\b',
    77  	'f':  '\f',
    78  	'n':  '\n',
    79  	'r':  '\r',
    80  	't':  '\t',
    81  	'v':  '\v',
    82  }
    83  
    84  type Set string
    85  
    86  const (
    87  	ALPHA Set = "[:alpha:]"
    88  	DIGIT Set = "[:digit:]"
    89  	GRAPH Set = "[:graph:]"
    90  	CNTRL Set = "[:cntrl:]"
    91  	PUNCT Set = "[:punct:]"
    92  	SPACE Set = "[:space:]"
    93  	ALNUM Set = "[:alnum:]"
    94  	LOWER Set = "[:lower:]"
    95  	UPPER Set = "[:upper:]"
    96  )
    97  
    98  var sets = map[Set]func(r rune) bool{
    99  	ALNUM: func(r rune) bool {
   100  		return unicode.IsLetter(r) || unicode.IsDigit(r)
   101  	},
   102  
   103  	ALPHA: unicode.IsLetter,
   104  	DIGIT: unicode.IsDigit,
   105  	GRAPH: unicode.IsGraphic,
   106  	CNTRL: unicode.IsControl,
   107  	PUNCT: unicode.IsPunct,
   108  	SPACE: unicode.IsSpace,
   109  	LOWER: unicode.IsLower,
   110  	UPPER: unicode.IsUpper,
   111  }
   112  
   113  type transformer struct {
   114  	transform func(r rune) rune
   115  }
   116  
   117  func setToRune(s Set, outRune rune) *transformer {
   118  	check := sets[s]
   119  	return &transformer{
   120  		transform: func(r rune) rune {
   121  			if check(r) {
   122  				return outRune
   123  			}
   124  			return r
   125  		},
   126  	}
   127  }
   128  
   129  func lowerToUpper() *transformer {
   130  	return &transformer{
   131  		transform: func(r rune) rune {
   132  			return unicode.ToUpper(r)
   133  		},
   134  	}
   135  }
   136  
   137  func upperToLower() *transformer {
   138  	return &transformer{
   139  		transform: func(r rune) rune {
   140  			return unicode.ToLower(r)
   141  		},
   142  	}
   143  }
   144  
   145  func runesToRunes(in []rune, out ...rune) *transformer {
   146  	convs := make(map[rune]rune)
   147  	l := len(out)
   148  	for i, r := range in {
   149  		ind := i
   150  		if i > l-1 {
   151  			ind = l - 1
   152  		}
   153  		convs[r] = out[ind]
   154  	}
   155  	return &transformer{
   156  		transform: func(r rune) rune {
   157  			if outRune, ok := convs[r]; ok {
   158  				return outRune
   159  			}
   160  			return r
   161  		},
   162  	}
   163  }
   164  
   165  func (c *command) run() error {
   166  	in := bufio.NewReader(c.stdin)
   167  	out := bufio.NewWriter(c.stdout)
   168  
   169  	defer out.Flush()
   170  
   171  	for {
   172  		inRune, size, err := in.ReadRune()
   173  		if inRune == unicode.ReplacementChar {
   174  			// can skip error handling here, because
   175  			// previous operation was in.ReadRune()
   176  			in.UnreadRune()
   177  
   178  			b, err := in.ReadByte()
   179  			if err != nil {
   180  				return fmt.Errorf("read error: %v", err)
   181  			}
   182  
   183  			if err := out.WriteByte(b); err != nil {
   184  				return fmt.Errorf("write error: %v", err)
   185  			}
   186  		} else if size > 0 {
   187  			if outRune := c.tr.transform(inRune); outRune != unicode.ReplacementChar {
   188  				if _, err := out.WriteRune(outRune); err != nil {
   189  					return fmt.Errorf("write error: %v", err)
   190  				}
   191  			}
   192  		}
   193  
   194  		if err != nil {
   195  			if err == io.EOF {
   196  				return nil
   197  			}
   198  			return err
   199  		}
   200  	}
   201  }
   202  
   203  func parse(args []string, del bool) (*transformer, error) {
   204  	narg := len(args)
   205  
   206  	switch {
   207  	case narg == 0 || (narg == 1 && !del):
   208  		return nil, fmt.Errorf("missing operand")
   209  	case narg > 1 && del:
   210  		return nil, fmt.Errorf("extra operand after %q", args[0])
   211  	case narg > 2:
   212  		return nil, fmt.Errorf("extra operand after %q", args[1])
   213  	}
   214  
   215  	set1 := Set(args[0])
   216  	arg1, err := unescape(set1)
   217  	if err != nil {
   218  		return nil, err
   219  	}
   220  
   221  	var set2 Set
   222  	if del {
   223  		set2 = Set(unicode.ReplacementChar)
   224  	} else {
   225  		set2 = Set(args[1])
   226  	}
   227  
   228  	if set1 == LOWER && set2 == UPPER {
   229  		return lowerToUpper(), nil
   230  	}
   231  	if set1 == UPPER && set2 == LOWER {
   232  		return upperToLower(), nil
   233  	}
   234  
   235  	if (set2 == LOWER || set2 == UPPER) && (set1 != LOWER && set1 != UPPER) ||
   236  		(set1 == LOWER && set2 == LOWER) || (set1 == UPPER && set2 == UPPER) {
   237  		return nil, fmt.Errorf("misaligned [:upper:] and/or [:lower:] construct")
   238  	}
   239  
   240  	if _, ok := sets[set2]; ok {
   241  		return nil, fmt.Errorf(`the only character classes that may appear in SET2 are 'upper' and 'lower'`)
   242  	}
   243  
   244  	arg2, err := unescape(set2)
   245  	if err != nil {
   246  		return nil, err
   247  	}
   248  	if len(arg2) == 0 {
   249  		return nil, fmt.Errorf("SET2 must be non-empty")
   250  	}
   251  	if _, ok := sets[set1]; ok {
   252  		return setToRune(set1, arg2[0]), nil
   253  	}
   254  	return runesToRunes(arg1, arg2...), nil
   255  }
   256  
   257  func unescape(s Set) ([]rune, error) {
   258  	var out []rune
   259  	var escape bool
   260  	for _, r := range s {
   261  		if escape {
   262  			v, ok := escapeChars[r]
   263  			if !ok {
   264  				return nil, fmt.Errorf("unknown escape sequence '\\%c'", r)
   265  			}
   266  			out = append(out, v)
   267  			escape = false
   268  			continue
   269  		}
   270  
   271  		if r == '\\' {
   272  			escape = true
   273  			continue
   274  		}
   275  
   276  		out = append(out, r)
   277  	}
   278  	return out, nil
   279  }
   280  
   281  func main() {
   282  	flag.Parse()
   283  	cmd, err := newCommand(os.Stdin, os.Stdout, flag.Args(), *del)
   284  	if err != nil {
   285  		log.Fatalf("%s: %v\n", name, err)
   286  	}
   287  	if err := cmd.run(); err != nil {
   288  		log.Fatalf("%s: %v\n", name, err)
   289  	}
   290  }