203 lines
5.0 KiB
Go
203 lines
5.0 KiB
Go
// Copyright (C) 2017 JT Olds
|
|
// See LICENSE for copying information.
|
|
|
|
// Package qod should NOT be used in a serious software engineering
|
|
// environment. qod stands for Quick and Dirty bahaha I just realized I got the
|
|
// acronym wrong. It's fine. It's on brand. Quick AND Dirty.
|
|
//
|
|
// The context is I noticed that Go is my favorite language, but when a task
|
|
// gets too complicated for a shell pipeline or awk or something, I turn to
|
|
// Python. Why not Go?
|
|
//
|
|
// In Python, I'd frequently write something like:
|
|
//
|
|
// for line in sys.stdin:
|
|
// vals = map(int, line.split())
|
|
//
|
|
// Here that is in Go:
|
|
//
|
|
// package main
|
|
//
|
|
// import (
|
|
// "bufio"
|
|
// "fmt"
|
|
// "os"
|
|
// "strconv"
|
|
// "strings"
|
|
// )
|
|
//
|
|
// func main() {
|
|
// scanner := bufio.NewScanner(os.Stdin)
|
|
// for scanner.Scan() {
|
|
// var vals []int64
|
|
// for _, str := range strings.Fields(scanner.Text()) {
|
|
// val, err := strconv.ParseInt(str, 10, 64)
|
|
// if err != nil {
|
|
// panic(err)
|
|
// }
|
|
// vals = append(vals, val)
|
|
// }
|
|
// }
|
|
// if err := scanner.Err(); err != nil {
|
|
// panic(err)
|
|
// }
|
|
// }
|
|
//
|
|
// Ugh! Considering I don't care about this throwaway shell pipeline
|
|
// replacement, I'm clearly fine with it blowing up if something's wrong, and
|
|
// wow this was too much.
|
|
//
|
|
// Package qod allows me to write the same type of thing in Go. Here is a
|
|
// reimplementation of the Python code above using qod:
|
|
//
|
|
// package main
|
|
//
|
|
// import (
|
|
// "os"
|
|
// "strings"
|
|
//
|
|
// "github.com/jtolds/qod"
|
|
// )
|
|
//
|
|
// func main() {
|
|
// for line := range qod.Lines(os.Stdin) {
|
|
// vals := qod.Int64Slice(strings.Fields(line))
|
|
// }
|
|
// }
|
|
//
|
|
// Better! I'm more likely to use Go now for little scripts!
|
|
//
|
|
// Reminder: don't use this for anything real. Most of the stuff in here
|
|
// panics at the sight of any errors. That's obviously Bad and Wrong and you
|
|
// should actually handle your errors. Set up your build system's linter to
|
|
// reject anything that imports github.com/jtolds/qod please. If you have a
|
|
// build system for what you're doing at all this isn't for you. If you have
|
|
// some one-off tab-delimited data you need to process real quick like I seem
|
|
// to ALL THE TIME then okay.
|
|
package qod
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"unicode"
|
|
)
|
|
|
|
// ANE stands for Assert No Error. It panics if err != nil.
|
|
func ANE(err error) {
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
// AFH stands for Assert File Handle. It asserts there was no error and
|
|
// passes the file handle on through. Usage like:
|
|
//
|
|
// fh := qod.AFH(os.Open(path))
|
|
func AFH(f *os.File, err error) *os.File {
|
|
ANE(err)
|
|
return f
|
|
}
|
|
|
|
// AI stands for Assert Int. It asserts there was no error and
|
|
// passes the int on through. Usage like:
|
|
//
|
|
// qod.AI(fmt.Println("a line"))
|
|
func AI(i int, err error) int {
|
|
ANE(err)
|
|
return i
|
|
}
|
|
|
|
// Lines makes reading lines easier. Usage like:
|
|
//
|
|
// for line := range Lines(os.Stdin) {
|
|
// // do something with the line
|
|
// }
|
|
//
|
|
// Returned lines will be right-stripped of whitespace.
|
|
// If you care about the lifetime of the channel that you're reading from and
|
|
// don't want it to leak, you probably shouldn't be using this package at all.
|
|
func Lines(r io.Reader) <-chan string {
|
|
ch := make(chan string)
|
|
go func() {
|
|
defer close(ch)
|
|
br := bufio.NewReader(r)
|
|
for {
|
|
l, err := br.ReadString('\n')
|
|
if err == io.EOF {
|
|
if l != "" {
|
|
ch <- strings.TrimRightFunc(l, unicode.IsSpace)
|
|
}
|
|
break
|
|
}
|
|
ANE(err)
|
|
ch <- strings.TrimRightFunc(l, unicode.IsSpace)
|
|
}
|
|
}()
|
|
return ch
|
|
}
|
|
|
|
// Float64 converts a string to a float64
|
|
func Float64(val string) float64 {
|
|
casted, err := strconv.ParseFloat(val, 64)
|
|
ANE(err)
|
|
return casted
|
|
}
|
|
|
|
// Float64Slice converts a []string to a []float64
|
|
func Float64Slice(vals []string) (rv []float64) {
|
|
rv = make([]float64, 0, len(vals))
|
|
for _, val := range vals {
|
|
rv = append(rv, Float64(val))
|
|
}
|
|
return rv
|
|
}
|
|
|
|
// Int64 converts a string to an int64
|
|
func Int64(val string) int64 {
|
|
casted, err := strconv.ParseInt(val, 10, 64)
|
|
ANE(err)
|
|
return casted
|
|
}
|
|
|
|
// Int64Slice converts a []string to an []int64
|
|
func Int64Slice(vals []string) (rv []int64) {
|
|
rv = make([]int64, 0, len(vals))
|
|
for _, val := range vals {
|
|
rv = append(rv, Int64(val))
|
|
}
|
|
return rv
|
|
}
|
|
|
|
// Printlnf is just cause I constantly use Println, then turn it into Printf,
|
|
// then get frustrated I forgot the newline.
|
|
func Printlnf(format string, vals ...interface{}) {
|
|
AI(fmt.Printf(format+"\n", vals...))
|
|
}
|
|
|
|
// Bytes will take an integer amount of bytes and format it with units.
|
|
func Bytes(amount int64) string {
|
|
val := float64(amount)
|
|
moves := 0
|
|
for val >= 1024 {
|
|
val /= 1024
|
|
moves += 1
|
|
}
|
|
return fmt.Sprintf("%0.02f %s", val, []string{
|
|
"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"}[moves])
|
|
}
|
|
|
|
// SortedKeysBool returns the keys of a map[string]bool in sorted order.
|
|
func SortedKeysBool(v map[string]bool) []string {
|
|
rv := make([]string, 0, len(v))
|
|
for key := range v {
|
|
rv = append(rv, key)
|
|
}
|
|
sort.Strings(rv)
|
|
return rv
|
|
}
|