github.com/octohelm/cuemod@v0.9.4/pkg/cuemod/modfile/modfile.go (about)

     1  package modfile
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"sort"
     8  	"strconv"
     9  
    10  	"cuelang.org/go/cue/ast"
    11  	"cuelang.org/go/cue/format"
    12  )
    13  
    14  const ModFilename = "cue.mod/module.cue"
    15  
    16  type ModVersion struct {
    17  	Version string
    18  	VcsRef  string
    19  }
    20  
    21  func (mv ModVersion) Exactly() bool {
    22  	return mv.Version != "" && mv.VcsRef == ""
    23  }
    24  
    25  type Requirement struct {
    26  	ModVersion
    27  	Indirect bool
    28  }
    29  
    30  type ReplaceTarget struct {
    31  	VersionedPathIdentity
    32  	Import string
    33  }
    34  
    35  type ModFile struct {
    36  	// Module name
    37  	Module string
    38  
    39  	// Replace
    40  	// version limit
    41  	Replace map[VersionedPathIdentity]ReplaceTarget
    42  	// Require same as go root
    43  	// require { module: version }
    44  	// indirect require { module:: version }
    45  	Require map[string]Requirement
    46  
    47  	comments map[string][]*ast.CommentGroup
    48  }
    49  
    50  func (m *ModFile) String() string {
    51  	return string(m.Bytes())
    52  }
    53  
    54  func (m *ModFile) Bytes() []byte {
    55  	buf := bytes.NewBuffer(nil)
    56  
    57  	_, _ = fmt.Fprintf(buf, "module: %s\n", strconv.Quote(m.Module))
    58  
    59  	if len(m.Require) > 0 {
    60  		modules := make([]string, 0)
    61  
    62  		for module := range m.Require {
    63  			modules = append(modules, module)
    64  		}
    65  
    66  		sort.Strings(modules)
    67  
    68  		m.writeRequires(buf, modules, func(r *Requirement) bool {
    69  			return !r.Indirect
    70  		})
    71  
    72  		m.writeRequires(buf, modules, func(r *Requirement) bool {
    73  			return r.Indirect
    74  		})
    75  	}
    76  
    77  	if len(m.Replace) > 0 {
    78  		replacements := make([]VersionedPathIdentity, 0)
    79  
    80  		for r := range m.Replace {
    81  			replacements = append(replacements, r)
    82  		}
    83  
    84  		sort.Slice(replacements, func(i, j int) bool {
    85  			return replacements[i].Path < replacements[j].Path
    86  		})
    87  
    88  		m.writeReplaces(buf, replacements, func(r *ReplaceTarget) bool {
    89  			return r.Import == ""
    90  		})
    91  
    92  		m.writeReplaces(buf, replacements, func(r *ReplaceTarget) bool {
    93  			return r.Import != ""
    94  		})
    95  	}
    96  
    97  	data, err := format.Source(buf.Bytes())
    98  	if err != nil {
    99  		panic(err)
   100  	}
   101  
   102  	return data
   103  }
   104  
   105  func (m *ModFile) writeReplaces(w io.Writer, replacements []VersionedPathIdentity, filter func(replaceTarget *ReplaceTarget) bool) {
   106  	fields := make([]interface{}, 0)
   107  
   108  	for _, replaceFrom := range replacements {
   109  		replaceTarget := m.Replace[replaceFrom]
   110  
   111  		if !filter(&replaceTarget) {
   112  			continue
   113  		}
   114  
   115  		i := replaceFrom.String()
   116  
   117  		f := &ast.Field{Label: ast.NewString(i)}
   118  
   119  		if replaceTarget.Path == replaceFrom.Path {
   120  			f.Value = ast.NewString((&VersionedPathIdentity{ModVersion: replaceTarget.ModVersion}).String())
   121  		} else {
   122  			f.Value = ast.NewString(replaceTarget.String())
   123  		}
   124  
   125  		if cg, ok := m.comments["replace://"+i]; ok {
   126  			for i := range cg {
   127  				f.AddComment(cg[i])
   128  			}
   129  		}
   130  
   131  		if replaceTarget.Import != "" {
   132  			f.Attrs = append(f.Attrs, &ast.Attribute{Text: attr("import", replaceTarget.Import)})
   133  		}
   134  
   135  		fields = append(fields, f)
   136  	}
   137  
   138  	if len(fields) == 0 {
   139  		return
   140  	}
   141  
   142  	data, _ := format.Node(ast.NewStruct(fields...))
   143  
   144  	_, _ = fmt.Fprintf(w, `
   145  replace: %s
   146  `, string(data))
   147  }
   148  
   149  func (m *ModFile) writeRequires(w io.Writer, modules []string, filter func(r *Requirement) bool) {
   150  	fields := make([]interface{}, 0)
   151  
   152  	// direct require
   153  	for _, module := range modules {
   154  		r := m.Require[module]
   155  
   156  		if !filter(&r) {
   157  			continue
   158  		}
   159  
   160  		f := &ast.Field{Label: ast.NewString(module)}
   161  		f.Value = ast.NewString(r.Version)
   162  
   163  		if r.Indirect {
   164  			f.Attrs = append(f.Attrs, &ast.Attribute{Text: attr("indirect")})
   165  		}
   166  
   167  		if cg, ok := m.comments["require://"+module]; ok {
   168  			for i := range cg {
   169  				f.AddComment(cg[i])
   170  			}
   171  		}
   172  
   173  		fields = append(fields, f)
   174  	}
   175  
   176  	if len(fields) > 0 {
   177  		data, _ := format.Node(ast.NewStruct(fields...))
   178  		_, _ = fmt.Fprintf(w, `
   179  require: %s
   180  `, string(data))
   181  	}
   182  }
   183  
   184  func attr(key string, values ...string) string {
   185  	buf := bytes.NewBufferString("@")
   186  	buf.WriteString(key)
   187  	buf.WriteString("(")
   188  
   189  	for i := range values {
   190  		if i > 0 {
   191  			buf.WriteString(",")
   192  		}
   193  
   194  		buf.WriteString(strconv.Quote(values[i]))
   195  	}
   196  
   197  	buf.WriteString(")")
   198  
   199  	return buf.String()
   200  }