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 }