github.com/dop251/modtools@v0.0.0-20220314120634-3b2fc95d1790/cmd/util.go (about) 1 package cmd 2 3 import ( 4 "bufio" 5 "bytes" 6 "encoding/json" 7 "io" 8 "io/ioutil" 9 "os" 10 "os/exec" 11 "time" 12 13 "gopkg.in/yaml.v2" 14 ) 15 16 const frozendepsFilename = "modtools_frozen.yml" 17 18 type Update struct { 19 Path, Version string 20 } 21 22 type Module struct { 23 Path string 24 Version string 25 Update Update 26 Indirect bool 27 } 28 29 type Exception struct { 30 Path string 31 MinVersion string `yaml:"minVersion"` 32 ValidUntil time.Time `yaml:"validUntil"` 33 } 34 35 type Exceptions struct { 36 filename string 37 list []*Exception 38 m map[string]*Exception 39 needSaving bool 40 isNew bool 41 } 42 43 func runCommand(name string, arg ...string) ([]byte, error) { 44 var buf bytes.Buffer 45 cmd := exec.Command(name, arg...) 46 cmd.Stderr = os.Stderr 47 cmd.Stdout = &buf 48 err := cmd.Run() 49 return buf.Bytes(), err 50 } 51 52 func (e *Exceptions) Load() error { 53 f, err := ioutil.ReadFile(e.filename) 54 if err != nil { 55 if os.IsNotExist(err) { 56 e.isNew = true 57 return nil 58 } 59 return err 60 } 61 err = yaml.Unmarshal(f, &e.list) 62 if err == nil { 63 e.filterExpired() 64 e.m = make(map[string]*Exception) 65 for _, item := range e.list { 66 e.m[item.Path] = item 67 } 68 } 69 70 return err 71 } 72 73 func (e *Exceptions) IsNew() bool { 74 return e.isNew 75 } 76 77 func loadExceptions() (*Exceptions, error) { 78 e := &Exceptions{ 79 filename: frozendepsFilename, 80 } 81 err := e.Load() 82 if err != nil { 83 return nil, err 84 } 85 return e, nil 86 } 87 88 func (e *Exceptions) filterExpired() { 89 now := time.Now() 90 j := 0 91 for i, item := range e.list { 92 if item.ValidUntil.IsZero() || item.ValidUntil.Before(now) { 93 continue 94 } 95 if i != j { 96 e.list[j] = e.list[i] 97 } 98 j++ 99 } 100 if j < len(e.list) { 101 e.list = e.list[:j] 102 e.needSaving = true 103 } else { 104 e.needSaving = false 105 } 106 } 107 108 func (e *Exceptions) filterDuplicates() bool { 109 j := 0 110 for i, item := range e.list { 111 if e.m[item.Path] != item { 112 continue 113 } 114 if i != j { 115 e.list[j] = e.list[i] 116 } 117 j++ 118 } 119 if j < len(e.list) { 120 e.list = e.list[:j] 121 return true 122 } 123 return false 124 } 125 126 func (e *Exceptions) Save() error { 127 if !e.filterDuplicates() && !e.needSaving { 128 return nil 129 } 130 data, err := yaml.Marshal(e.list) 131 if err != nil { 132 return err 133 } 134 135 err = ioutil.WriteFile(e.filename, data, 0o644) 136 if err == nil { 137 e.needSaving = false 138 e.isNew = false 139 } 140 return err 141 } 142 143 func readDeps(updates, directOnly bool) ([]Module, error) { 144 format := "{{with .Module}}{{.Path}}{{end}}" 145 if directOnly { 146 format = "{{with .Module}}{{if not .Indirect}}{{.Path}}{{end}}{{end}}" 147 } 148 out, err := runCommand("go", "list", "-f", format, "all") 149 if err != nil { 150 return nil, err 151 } 152 args := []string{"list", "-m", "-json"} 153 if updates { 154 args = append(args, "-u", "-mod=readonly") 155 } 156 set := make(map[string]struct{}) 157 scanner := bufio.NewScanner(bytes.NewBuffer(out)) 158 for scanner.Scan() { 159 modpath := scanner.Text() 160 if _, exists := set[modpath]; !exists { 161 args = append(args, scanner.Text()) 162 set[modpath] = struct{}{} 163 } 164 } 165 if err := scanner.Err(); err != nil { 166 return nil, err 167 } 168 out, err = runCommand("go", args...) 169 if err != nil { 170 return nil, err 171 } 172 dec := json.NewDecoder(bytes.NewBuffer(out)) 173 var list []Module 174 for { 175 list = append(list, Module{}) 176 item := &list[len(list)-1] 177 err := dec.Decode(item) 178 if err != nil { 179 if err == io.EOF { 180 list = list[:len(list)-1] 181 break 182 } 183 return nil, err 184 } 185 } 186 return list, nil 187 } 188 189 func (e *Exceptions) Get(p string) *Exception { 190 return e.m[p] 191 } 192 193 func (e *Exceptions) Add(ex *Exception) { 194 e.list = append(e.list, ex) 195 if e.m == nil { 196 e.m = make(map[string]*Exception) 197 } 198 e.m[ex.Path] = ex 199 e.needSaving = true 200 } 201 202 func (e *Exceptions) Remove(p string) { 203 delete(e.m, p) 204 e.needSaving = true 205 }