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 package main
34
35 import (
36 "bytes"
37 "encoding/json"
38 "flag"
39 "fmt"
40 "go/parser"
41 "go/token"
42 "io/fs"
43 "log"
44 "os"
45 "os/exec"
46 "path"
47 "path/filepath"
48 "strconv"
49 "strings"
50
51 "golang.org/x/mod/modfile"
52 "golang.org/x/mod/module"
53 "golang.org/x/tools/internal/edit"
54 )
55
56 func usage() {
57 fmt.Fprintf(os.Stderr, "usage: gonew srcmod[@version] [dstmod [dir]]\n")
58 fmt.Fprintf(os.Stderr, "See https://pkg.go.dev/golang.org/x/tools/cmd/gonew.\n")
59 os.Exit(2)
60 }
61
62 func main() {
63 log.SetPrefix("gonew: ")
64 log.SetFlags(0)
65 flag.Usage = usage
66 flag.Parse()
67 args := flag.Args()
68
69 if len(args) < 1 || len(args) > 3 {
70 usage()
71 }
72
73 srcMod := args[0]
74 srcModVers := srcMod
75 if !strings.Contains(srcModVers, "@") {
76 srcModVers += "@latest"
77 }
78 srcMod, _, _ = strings.Cut(srcMod, "@")
79 if err := module.CheckPath(srcMod); err != nil {
80 log.Fatalf("invalid source module name: %v", err)
81 }
82
83 dstMod := srcMod
84 if len(args) >= 2 {
85 dstMod = args[1]
86 if err := module.CheckPath(dstMod); err != nil {
87 log.Fatalf("invalid destination module name: %v", err)
88 }
89 }
90
91 var dir string
92 if len(args) == 3 {
93 dir = args[2]
94 } else {
95 dir = "." + string(filepath.Separator) + path.Base(dstMod)
96 }
97
98
99 de, err := os.ReadDir(dir)
100 if err == nil && len(de) > 0 {
101 log.Fatalf("target directory %s exists and is non-empty", dir)
102 }
103 needMkdir := err != nil
104
105 var stdout, stderr bytes.Buffer
106 cmd := exec.Command("go", "mod", "download", "-json", srcModVers)
107 cmd.Stdout = &stdout
108 cmd.Stderr = &stderr
109 if err := cmd.Run(); err != nil {
110 log.Fatalf("go mod download -json %s: %v\n%s%s", srcModVers, err, stderr.Bytes(), stdout.Bytes())
111 }
112
113 var info struct {
114 Dir string
115 }
116 if err := json.Unmarshal(stdout.Bytes(), &info); err != nil {
117 log.Fatalf("go mod download -json %s: invalid JSON output: %v\n%s%s", srcMod, err, stderr.Bytes(), stdout.Bytes())
118 }
119
120 if needMkdir {
121 if err := os.MkdirAll(dir, 0777); err != nil {
122 log.Fatal(err)
123 }
124 }
125
126
127 filepath.WalkDir(info.Dir, func(src string, d fs.DirEntry, err error) error {
128 if err != nil {
129 log.Fatal(err)
130 }
131 rel, err := filepath.Rel(info.Dir, src)
132 if err != nil {
133 log.Fatal(err)
134 }
135 dst := filepath.Join(dir, rel)
136 if d.IsDir() {
137 if err := os.MkdirAll(dst, 0777); err != nil {
138 log.Fatal(err)
139 }
140 return nil
141 }
142
143 data, err := os.ReadFile(src)
144 if err != nil {
145 log.Fatal(err)
146 }
147
148 isRoot := !strings.Contains(rel, string(filepath.Separator))
149 if strings.HasSuffix(rel, ".go") {
150 data = fixGo(data, rel, srcMod, dstMod, isRoot)
151 }
152 if rel == "go.mod" {
153 data = fixGoMod(data, srcMod, dstMod)
154 }
155
156 if err := os.WriteFile(dst, data, 0666); err != nil {
157 log.Fatal(err)
158 }
159 return nil
160 })
161
162 log.Printf("initialized %s in %s", dstMod, dir)
163 }
164
165
166
167
168 func fixGo(data []byte, file string, srcMod, dstMod string, isRoot bool) []byte {
169 fset := token.NewFileSet()
170 f, err := parser.ParseFile(fset, file, data, parser.ImportsOnly)
171 if err != nil {
172 log.Fatalf("parsing source module:\n%s", err)
173 }
174
175 buf := edit.NewBuffer(data)
176 at := func(p token.Pos) int {
177 return fset.File(p).Offset(p)
178 }
179
180 srcName := path.Base(srcMod)
181 dstName := path.Base(dstMod)
182 if isRoot {
183 if name := f.Name.Name; name == srcName || name == srcName+"_test" {
184 dname := dstName + strings.TrimPrefix(name, srcName)
185 if !token.IsIdentifier(dname) {
186 log.Fatalf("%s: cannot rename package %s to package %s: invalid package name", file, name, dname)
187 }
188 buf.Replace(at(f.Name.Pos()), at(f.Name.End()), dname)
189 }
190 }
191
192 for _, spec := range f.Imports {
193 path, err := strconv.Unquote(spec.Path.Value)
194 if err != nil {
195 continue
196 }
197 if path == srcMod {
198 if srcName != dstName && spec.Name == nil {
199
200
201
202
203
204
205
206
207 buf.Insert(at(spec.Path.Pos()), srcName+" ")
208 }
209
210 buf.Replace(at(spec.Path.Pos()), at(spec.Path.End()), strconv.Quote(dstMod))
211 }
212 if strings.HasPrefix(path, srcMod+"/") {
213
214 buf.Replace(at(spec.Path.Pos()), at(spec.Path.End()), strconv.Quote(strings.Replace(path, srcMod, dstMod, 1)))
215 }
216 }
217 return buf.Bytes()
218 }
219
220
221
222 func fixGoMod(data []byte, srcMod, dstMod string) []byte {
223 f, err := modfile.ParseLax("go.mod", data, nil)
224 if err != nil {
225 log.Fatalf("parsing source module:\n%s", err)
226 }
227 f.AddModuleStmt(dstMod)
228 new, err := f.Format()
229 if err != nil {
230 return data
231 }
232 return new
233 }
234
View as plain text