-
Notifications
You must be signed in to change notification settings - Fork 0
/
gouse.go
148 lines (137 loc) · 3 KB
/
gouse.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
140
141
142
143
144
145
146
147
148
// gouse toggles ‘declared and not used’ errors by using idiomatic
// _ = notUsedVar and leaving a TODO comment.
//
// Usage:
//
// gouse [-w] [file paths...]
//
// By default, gouse accepts code from stdin or from a file provided as a path
// argument and writes the toggled version to stdout. ‘-w’ flag writes the
// result back to the file. If multiple paths provided, ‘-w’ flag is required.
//
// First it tries to remove previously created fake usages. If there is nothing
// to remove, it tries to build an input and checks the build stdout for
// ‘declared and not used’ errors. If there is any, it creates fake usages for
// unused variables from the errors.
//
// Examples
//
// $ gouse
// ...input...
// notUsed = true
// ...input...
//
// ...output...
// notUsed = true; _ = notUsed /* TODO: gouse */
// ...output...
//
// $ gouse main.go
// ...
// notUsed = true; _ = notUsed /* TODO: gouse */
// ...
//
// $ gouse -w main.go io.go core.go
// $ cat main.go io.go core.go
// ...
// notUsedFromMain = true; _ = notUsedFromMain /* TODO: gouse */
// ...
// notUsedFromIo = true; _ = notUsedFromIo /* TODO: gouse */
// ...
// notUsedFromCore = true; _ = notUsedFromCore /* TODO: gouse */
// ...
package main
import (
"context"
"errors"
"flag"
"fmt"
"log"
"os"
"os/signal"
)
const (
errorLogPrefix = "error: "
logFlag = 0
currentVersion = "1.3.2"
)
var (
errCannotWriteToStdin = errors.New(
"cannot use ‘-w’ flag with standard input",
)
errMustWriteToFiles = errors.New(
"must use ‘-w’ flag with more than one path",
)
)
func main() {
ctx := context.Background()
os.Exit(run(
ctx,
os.Args[1:],
os.Stdin, os.Stdout, os.Stderr,
openFile,
))
}
// run manages logging, parses arguments and toggles the passed files.
func run(
ctx context.Context,
args []string,
stdin, stdout, stderr file,
openFile osOpenFile,
) int {
ctx, cancel := signal.NotifyContext(ctx, os.Interrupt, os.Kill)
defer cancel()
errorLog := log.New(stderr, errorLogPrefix, logFlag)
infoLog := log.New(stderr, "", logFlag)
conf, msg, err := parseArgs(args)
if err == flag.ErrHelp {
infoLog.Print(msg)
return 2
} else if err != nil {
errorLog.Print(
fmt.Errorf("run: in parseArgs: %s\n%s", err, msg),
)
return 1
}
if conf.version {
infoLog.Print(currentVersion)
return 0
}
if len(conf.paths) == 0 {
if conf.write {
errorLog.Print(errCannotWriteToStdin)
return 1
}
if err := toggleFile(ctx, stdin, stdout); err != nil {
errorLog.Print(err)
return 1
}
return 0
}
if len(conf.paths) > 1 && !conf.write {
errorLog.Print(errMustWriteToFiles)
return 1
}
for _, p := range conf.paths {
var in file
var out *file
var access int
if conf.write {
out = &in
access = os.O_RDWR
} else {
out = &stdout
access = os.O_RDONLY
}
in, err := openFile(p, access, os.ModeExclusive)
if err != nil {
errorLog.Print(err)
return 1
}
defer in.Close()
if err := toggleFile(ctx, in, *out); err != nil {
errorLog.Print(err)
return 1
}
}
return 0
}