languages

A collection of programs made with different programming languages.
git clone git://evanalba.com/languages
Log | Files | Refs

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 }