`iogo` is a helper go library for handling input and output via stdin & stdout, and stylizing them.

  • By Jos Ahrens
  • Last update: Jun 7, 2022
  • Comments: 1


iogo is a helper go library for handling input and output, and stylizing them.

At it's core, iogo is a reader and a writer that doesn't do a whole more than fmt.Print and fmt.Scanf, and if that's all that you need, you probably don't need this library - or you're just dealing with very little input.

Where iogo helps you, however, is offering an extensive toolkit for history management, terminal colours, handling input, and providing styling for output.

Currently, it is not entirely feature complete (support for table outputting, better term detection & 256-colour support are things I'd really like to include).

io-go, get it?

Download the library:

go get github.com/zarthus/iogo


In essence: You want to either instantiate a reader, a writer or a io based on your purpose.

Refer to main.go for a detailed example.

Very basic usage

io := style.CreateDefaultIo()

io.GetWriter().WriteLine("What's your name?")
name, err := io.GetReader().ReadLine()
if err != nil {
io.GetWriter().WriteLine("Nice to meet you, " + name)

Input handling

io := style.CreateDefaultIo()

confirmed, err := io.Style().Input().Confirm("Do you want to go swimming today?", iogo.Options{Default: "y"})
if err != nil {

if confirmed {

Output handling

io := style.CreateDefaultIo()

io.Style().Output().Title("Welcome to iogo!")
io.Style().Output().Success("You have installed the software correctly!")


MIT license




  • 1

    Code review

    This looks really awesome! Works perfect in my terminal. There was definitely nothing majorly wrong with the Go code, you've got it down pretty good. I've taken note of some things. Some of it is just style; I believe that the style changes are in line with the spirit of Go, but that can always be up for debate :)

    Language notes:

    • switch can be nicer than if in Go because of no fallthrough.
    • Receiver names (func (s mystruct) ...) are almost always one letter.
    • Not much need to cache len(str) because it compiles to a pointer dereference (string is a struct{b *byte, len int} under the hood).
      • Speaking of string being a struct, I noticed a few bugs where a function would return string and then the code checks for &str == nil... this won't work because structs can't be nil. You can only check for str == "".
    • int should be preferred over uint or similar; use if...panic blocks for validation. (stdlib example: len(str) returns int even though uint makes sense too)
    • Only return exported types from exported functions. I think Go should enforce this but oh well /o\
    • Sadly Go's stdlib doesn't have integer math functions... you just have to implement min and such on your own. Go 1.8 generics make this less painful, but you still have to write them on your own.
    • I always pass structs larger than a couple fields as pointers... it might be a slight premature optimization, but most libraries I know do that, so in the spirit of consistency...
      • I did this for Options... I'm realizing now after making the PR that it's probably better to make your Options-accepting functions handle nil properly. Unless you handle nil, I would actually revert these changes.
      • There are actually two other routes that I would recommend trying instead of passing a plain struct as options:
        1. Functional options: https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis
        2. What MongoDB does with variadic options and a fluent API: https://github.com/mongodb/mongo-go-driver/blob/master/mongo/options/findoptions.go
    • panic is only for programmer errors, not user, IO, etc errors.
    • Go really loves its short variable names. There's a few notable ones that are used ubiquitously:
      • r for a reader
      • w for a writer
      • buf for a buffer
      • rw for a reader+writer
      • opts for options
    • I've renamed the Io interface to ReadWriter to align with the stdlib's io.ReadWriter. Also, the Io suffix makes it really tempting to use io as a variable name to hold it, but this conflicts with the io stdlib package.
    • I've renamed some IO functions to align with stdlib's io, fmt, and log packages (example WriteLine -> Writeln).
    • In main_test.go, note that the defer block doesn't run until the function returns. t.Fail() can panic, and I'm not sure if setting os.Stdout to nil will suppress its output, but resetting os.Stdout to its default after calling t.Fail() might cause issues, or it's not clear if it will.

    Architecture comments:

    • Favor few packages with many files over many packages with few files.
    • Since this package is largely IO, it should implement all the usual stdlib IO interfaces such as io.Reader, io.Writer, etc. to maximize portability.
      • To add, maybe consider functions like Writef to function like fmt.Sprintf, plus similar functions.
    • I wouldn't type os.Stdout or os.Stdin anywhere in the API except in tests. Have those be configurable, even if they're global-ish flags for the whole library. It helps with tests too.