pkg.tk-software.de/gotice@v0.4.1-0.20240224130243-6adec687b106/generate.go (about) 1 // Copyright 2023-2024 Tobias Koch. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package main 6 7 import ( 8 "flag" 9 "fmt" 10 "os" 11 "path/filepath" 12 "strings" 13 14 "pkg.tk-software.de/gotice/module" 15 "pkg.tk-software.de/gotice/notice" 16 "pkg.tk-software.de/spartan/io/file" 17 ) 18 19 // GenerateCommand implements the subcommand `generate`. 20 type GenerateCommand struct { 21 fs *flag.FlagSet 22 23 // The source directory containing the go.mod file. 24 srcd string 25 26 // The destination notice file that shall be created. 27 dstf string 28 } 29 30 // NewGenerateCommand creates and returns the subcommand `generate`. 31 func NewGenerateCommand() *GenerateCommand { 32 cmd := &GenerateCommand{ 33 fs: flag.NewFlagSet("generate", flag.ContinueOnError), 34 } 35 36 return cmd 37 } 38 39 // Name returns the name of the subcommand. 40 func (g *GenerateCommand) Name() string { 41 return g.fs.Name() 42 } 43 44 // Description returns the description of the subcommand. 45 func (g *GenerateCommand) Description() string { 46 return "Generates a notice file" 47 } 48 49 // Init initializes the subcommand with the given command line arguments. 50 func (g *GenerateCommand) Init(args []string) error { 51 if err := g.fs.Parse(args); err != nil { 52 return err 53 } 54 55 if g.fs.NArg() < 2 { 56 return ErrMissingArguments 57 } 58 59 g.srcd = g.fs.Arg(0) 60 g.dstf = g.fs.Arg(1) 61 62 return nil 63 } 64 65 // Usage prints a usage message documenting the subcommand. 66 func (g *GenerateCommand) Usage() { 67 fmt.Println("Usage: gotice generate [project dir] [output file]") 68 fmt.Println(g.Description()) 69 fmt.Println() 70 } 71 72 // Run executes the subcommand. 73 func (g *GenerateCommand) Run() error { 74 modf := filepath.Join(g.srcd, "go.mod") 75 76 if !file.Exists(modf) { 77 return fmt.Errorf("file %s not found", modf) 78 } 79 80 mods, err := module.NewFromGoModule(g.srcd) 81 if err != nil { 82 return fmt.Errorf("unable to parse %s: %w", modf, err) 83 } 84 85 opt := readOptionsOrDefault(g.srcd) 86 87 if err := generate(*mods, *opt, g.srcd, g.dstf); err != nil { 88 return err 89 } 90 91 return nil 92 } 93 94 func generate(mods module.Modules, opt notice.Options, srcd, dstf string) error { 95 ns, err := generateNotice(mods) 96 if err != nil { 97 return err 98 } 99 100 tmpl, err := readTemplate(srcd, opt.Template) 101 if err != nil { 102 return err 103 } 104 105 if err := writeNotice(dstf, tmpl, opt.Rendering, ns); err != nil { 106 return err 107 } 108 109 return nil 110 } 111 112 func readTemplate(dir, template string) (string, error) { 113 switch strings.ToLower(template) { 114 case "built-in:txt": 115 return notice.TextTemplate, nil 116 117 case "built-in:md": 118 return notice.MarkdownTemplate, nil 119 120 case "built-in:html": 121 return notice.HtmlTemplate, nil 122 123 default: 124 customTemplate := filepath.Join(dir, template) 125 126 if !file.Exists(customTemplate) { 127 return "", fmt.Errorf("template %s not found", template) 128 } 129 130 d, err := os.ReadFile(customTemplate) 131 if err != nil { 132 return "", err 133 } 134 135 return string(d), nil 136 } 137 } 138 139 func readOptionsOrDefault(d string) *notice.Options { 140 f := filepath.Join(d, notice.OptionsFileName) 141 if !file.Exists(f) { 142 return notice.NewOptions() 143 } 144 145 fh, err := os.Open(f) 146 if err != nil { 147 return notice.NewOptions() 148 } 149 defer fh.Close() 150 151 o, err := notice.ReadOptions(fh) 152 if err != nil { 153 return notice.NewOptions() 154 } 155 156 return o 157 } 158 159 func generateNotice(m module.Modules) ([]notice.Notice, error) { 160 var ns []notice.Notice 161 162 for _, mod := range m { 163 n := notice.New() 164 n.Path = mod.Path 165 n.Version = mod.Version 166 167 lt, err := notice.GetLicenseText(n.Path, n.Version) 168 if err != nil { 169 return nil, fmt.Errorf("unable to detect license text of %s@%s: %w", n.Path, n.Version, err) 170 } 171 n.LicenseText = lt 172 173 ns = append(ns, *n) 174 } 175 176 return ns, nil 177 } 178 179 func writeNotice(f, tmpl string, r notice.Rendering, n []notice.Notice) error { 180 of, err := os.OpenFile(f, os.O_CREATE|os.O_WRONLY, 0666) 181 if err != nil { 182 return fmt.Errorf("unable to open notice file %s: %w", f, err) 183 } 184 defer of.Close() 185 186 switch r { 187 case notice.Text: 188 if err := notice.WriteText(of, tmpl, n); err != nil { 189 return fmt.Errorf("unable to write text notice file %s: %w", f, err) 190 } 191 192 case notice.Html: 193 if err := notice.WriteHtml(of, tmpl, n); err != nil { 194 return fmt.Errorf("unable to write html notice file %s: %w", f, err) 195 } 196 197 default: 198 return fmt.Errorf("invalid rendering %q", r) 199 } 200 201 return nil 202 }