cinreader.go (5141B)
1 // Copyright 2019 J Boyd Trolinger. All rights reserved. 2 // Use of this source code is governed by a MIT 3 // license that can be found in the LICENSE file. 4 5 // Package cinreader implements a robust handler for a variety 6 // of types for inputs from os.Stdin. 7 8 // TODO(jboydt): needs additional testing 9 // TODO(jboydt): consider adding additional types? 10 // TODO(jboydt): consider allowing clients to change error messages? 11 12 package main 13 14 import ( 15 "bufio" 16 "fmt" 17 "os" 18 "strconv" 19 "strings" 20 ) 21 22 // MessageType encodes the error messages CinReader may emit. 23 type MessageType int 24 25 // Defined values for MessageType. 26 const ( 27 emptyStringNotAllowed = iota 28 invalidCharacter 29 invalidCharacterSet 30 invalidFloat 31 invalidInteger 32 invalidIntegerRange 33 invalidStringSet 34 ) 35 36 var default_messages = map[MessageType]string{ 37 emptyStringNotAllowed: "Empty input not allowed. Please re-enter: ", 38 invalidCharacter: "Not valid character input. Please re-enter: ", 39 invalidCharacterSet: "Input not in [%s]. Please re-enter: ", 40 invalidFloat: "Not a valid float. Please re-enter: ", 41 invalidInteger: "Not a valid integer. Please re-enter: ", 42 invalidIntegerRange: "Integer must be between %d - %d. Please re-enter: ", 43 invalidStringSet: "\"%s\" not valid input. Please re-enter: ", 44 } 45 46 // CinReader provides robust input handling from os.Stdin. 47 type CinReader struct { 48 reader *bufio.Reader 49 messages map[MessageType]string 50 } 51 52 // NewCinReader initializes a CinReader. 53 func NewCinReader() *CinReader { 54 return &CinReader{bufio.NewReader(os.Stdin), default_messages} 55 } 56 57 // ReadCharacter returns a rune/character from os.Stdin. 58 func (cin *CinReader) ReadCharacter() rune { 59 var charInput rune 60 var err error 61 var countCharacters int 62 for { 63 charInput, _, err = cin.reader.ReadRune() 64 // Added to handle multiple characters on input 65 countCharacters = cin.reader.Buffered() 66 cin.flush() 67 if err != nil || countCharacters > 1 { 68 fmt.Printf(cin.messages[invalidCharacter]) 69 continue 70 } else if charInput == '\n' { 71 fmt.Printf(cin.messages[emptyStringNotAllowed]) 72 continue 73 } 74 break 75 } 76 return charInput 77 } 78 79 // ReadCharacterSet returns a rune/character in charset 80 // from os.Stdin. 81 func (cin *CinReader) ReadCharacterSet(charset []rune) rune { 82 var charInput rune 83 var err error 84 var countCharacters int 85 for { 86 charInput, _, err = cin.reader.ReadRune() 87 // Added to handle multiple characters on input 88 countCharacters = cin.reader.Buffered() 89 cin.flush() 90 if err != nil || countCharacters > 1 { 91 fmt.Printf(cin.messages[invalidCharacter]) 92 continue 93 } else { 94 var inCharset bool 95 for _, char := range charset { 96 if charInput == char { 97 inCharset = true 98 break 99 } 100 } 101 if !inCharset { 102 fmt.Printf(cin.messages[invalidCharacterSet], string(charset)) 103 continue 104 } 105 } 106 break 107 } 108 return charInput 109 } 110 111 // ReadFloat returns a float64 from os.Stdin. 112 func (cin *CinReader) ReadFloat() float64 { 113 var floatInput float64 114 var err error 115 for { 116 stringInput := cin.ReadString(false) 117 floatInput, err = strconv.ParseFloat(stringInput, 64) 118 if err != nil { 119 fmt.Printf(cin.messages[invalidFloat]) 120 continue 121 } 122 break 123 } 124 return floatInput 125 } 126 127 // ReadInteger returns an int from os.Stdin. 128 func (cin *CinReader) ReadInteger() int { 129 var intInput int 130 var err error 131 for { 132 stringInput := cin.ReadString(false) 133 intInput, err = strconv.Atoi(stringInput) 134 if err != nil { 135 fmt.Printf(cin.messages[invalidInteger]) 136 continue 137 } 138 break 139 } 140 return intInput 141 } 142 143 // ReadIntegerRange returns an int between min and max (inclusive) 144 // from os.Stdin. 145 func (cin *CinReader) ReadIntegerRange(min, max int) int { 146 var intInput int 147 var err error 148 for { 149 stringInput := cin.ReadString(false) 150 intInput, err = strconv.Atoi(stringInput) 151 if err != nil { 152 fmt.Printf(cin.messages[invalidInteger]) 153 continue 154 } else if intInput < min || intInput > max { 155 fmt.Printf(cin.messages[invalidIntegerRange], min, max) 156 continue 157 } 158 break 159 } 160 return intInput 161 } 162 163 // ReadString returns a string from os.Stdin. 164 func (cin *CinReader) ReadString(allowEmpty bool) string { 165 166 var input string 167 var err error 168 for { 169 input, err = cin.reader.ReadString('\n') 170 input = strings.Trim(input, " \r\n") 171 if err == nil { 172 if len(input) == 0 && !allowEmpty { 173 fmt.Printf(cin.messages[emptyStringNotAllowed]) 174 continue 175 } 176 break 177 } 178 } 179 return input 180 } 181 182 // ReadStringSet returns a string in stringset from os.Stdin. 183 func (cin *CinReader) ReadStringSet(stringset []string, caseSensitive bool) string { 184 if !caseSensitive { 185 for i, str := range stringset { 186 stringset[i] = strings.ToUpper(str) 187 } 188 } 189 var strInput string 190 for { 191 strInput = cin.ReadString(false) 192 if !caseSensitive { 193 strInput = strings.ToUpper(strInput) 194 } 195 var validString bool 196 for _, str := range stringset { 197 if str == strInput { 198 validString = true 199 break 200 } 201 } 202 if !validString { 203 fmt.Printf(cin.messages[invalidStringSet], strInput) 204 continue 205 } 206 break 207 } 208 return strInput 209 } 210 211 // flush the input buffer. 212 func (cin *CinReader) flush() { 213 cin.reader.Discard(cin.reader.Buffered()) 214 }