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  }