-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
139 lines (118 loc) · 3.15 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
package main
import (
"bufio"
"flag"
"fmt"
"io"
"io/ioutil"
"os"
"time"
"unicode/utf8"
)
var (
initial = flag.Duration("base", 1*time.Second, "the base delay per character")
step = flag.Duration("step", 100*time.Millisecond, "the amount of proportial delay added per rune")
bits = flag.Uint("bits", 3, "the number of bits per rune used to determine an appropriate delay")
debug = flag.Bool("debug", false, "print the input character and the calculated delay instead of the output unmodified")
)
func init() {
flag.Usage = func() {
fmt.Fprintf(flag.CommandLine.Output(), "as slow as possible\n\n")
flag.PrintDefaults()
}
}
func main() {
flag.Parse()
src, err := terminalInputs(os.Stdin, flag.Args())
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
dst := io.Writer(os.Stdout)
delay := bePatient(*bits, *initial, *step)
if *debug {
dst = ioutil.Discard
delay = printImpatiently(os.Stdout, delay)
}
copyRunesWithPatience(dst, src, delay)
}
func terminalInputs(src *os.File, fallbacks []string) (io.Reader, error) {
if stat, _ := src.Stat(); stat.Mode()&os.ModeCharDevice == 0 {
return os.Stdin, nil
}
var rdrs []io.Reader
for _, filename := range fallbacks {
f, err := os.Open(filename)
if err != nil {
if pathErr, isPathErr := err.(*os.PathError); isPathErr {
return nil, fmt.Errorf("error opening %s: %s", filename, pathErr.Err)
}
return nil, fmt.Errorf("error opening %s: %s", filename, err)
}
rdrs = append(rdrs, f)
}
return io.MultiReader(rdrs...), nil
}
// a func that determines how long to wait
type patience func(rune) time.Duration
// help debug patience by showing exactly how patient we're being
func printImpatiently(dst io.Writer, f patience) patience {
return func(b rune) time.Duration {
delay := f(b)
fmt.Fprintf(dst, "%q %U %s\n", string(b), b, delay)
return delay
}
}
// be patient
func bePatient(bits uint, initial, step time.Duration) patience {
if bits >= 8 {
panic("too many bits")
}
mask := rune((0x1 << bits) - 1)
return func(b rune) time.Duration {
return initial + step*time.Duration(mask&b)
}
}
// copy runes from src to dst, being patient about writing every byte.
func copyRunesWithPatience(dst io.Writer, src io.Reader, patience patience) error {
flush := makeFlush(dst)
scanner := bufio.NewScanner(src)
scanner.Split(bufio.ScanRunes)
for scanner.Scan() {
bs := scanner.Bytes()
for len(bs) > 0 {
r, size := utf8.DecodeRune(bs)
delay := patience(r)
if _, err := dst.Write(bs[:size]); err != nil {
return err
}
flush()
time.Sleep(delay)
bs = bs[size:]
}
}
return scanner.Err()
}
// returns a func that flushes this writer. if the writer is unflushable,
// returns a noop.
//
// this is here just in case you'd like to aslap an io.Writer that isn't
// an os.File
func makeFlush(w io.Writer) func() {
// http.Flusher
type flusher interface{ Flush() }
// bufio.Writer
type safeFlusher interface{ Flush() error }
// os.File
type syncer interface{ Sync() error }
switch t := w.(type) {
case flusher:
return t.Flush
case safeFlusher:
return func() { t.Flush() }
case syncer:
return func() { t.Sync() }
default:
return func() {}
}
}