github.com/anakojm/hugo-katex@v0.0.0-20231023141351-42d6f5de9c0b/modules/client.go (about) 1 // Copyright 2019 The Hugo Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package modules 15 16 import ( 17 "bufio" 18 "bytes" 19 "context" 20 "encoding/json" 21 "fmt" 22 "io" 23 "os" 24 "os/exec" 25 "path/filepath" 26 "regexp" 27 "strings" 28 "time" 29 30 "github.com/gohugoio/hugo/common/collections" 31 "github.com/gohugoio/hugo/common/herrors" 32 "github.com/gohugoio/hugo/common/hexec" 33 "github.com/gohugoio/hugo/common/loggers" 34 35 hglob "github.com/gohugoio/hugo/hugofs/glob" 36 37 "github.com/gobwas/glob" 38 39 "github.com/gohugoio/hugo/hugofs" 40 41 "github.com/gohugoio/hugo/hugofs/files" 42 43 "github.com/gohugoio/hugo/config" 44 45 "github.com/rogpeppe/go-internal/module" 46 47 "github.com/gohugoio/hugo/common/hugio" 48 49 "errors" 50 51 "github.com/spf13/afero" 52 ) 53 54 var fileSeparator = string(os.PathSeparator) 55 56 const ( 57 goBinaryStatusOK goBinaryStatus = iota 58 goBinaryStatusNotFound 59 goBinaryStatusTooOld 60 ) 61 62 // The "vendor" dir is reserved for Go Modules. 63 const vendord = "_vendor" 64 65 const ( 66 goModFilename = "go.mod" 67 goSumFilename = "go.sum" 68 ) 69 70 // NewClient creates a new Client that can be used to manage the Hugo Components 71 // in a given workingDir. 72 // The Client will resolve the dependencies recursively, but needs the top 73 // level imports to start out. 74 func NewClient(cfg ClientConfig) *Client { 75 fs := cfg.Fs 76 n := filepath.Join(cfg.WorkingDir, goModFilename) 77 goModEnabled, _ := afero.Exists(fs, n) 78 var goModFilename string 79 if goModEnabled { 80 goModFilename = n 81 } 82 83 var env []string 84 mcfg := cfg.ModuleConfig 85 86 config.SetEnvVars(&env, 87 "PWD", cfg.WorkingDir, 88 "GO111MODULE", "on", 89 "GOPROXY", mcfg.Proxy, 90 "GOPRIVATE", mcfg.Private, 91 "GONOPROXY", mcfg.NoProxy, 92 "GOPATH", cfg.CacheDir, 93 "GOWORK", mcfg.Workspace, // Requires Go 1.18, see https://tip.golang.org/doc/go1.18 94 // GOCACHE was introduced in Go 1.15. This matches the location derived from GOPATH above. 95 "GOCACHE", filepath.Join(cfg.CacheDir, "pkg", "mod"), 96 ) 97 98 logger := cfg.Logger 99 if logger == nil { 100 logger = loggers.NewDefault() 101 } 102 103 var noVendor glob.Glob 104 if cfg.ModuleConfig.NoVendor != "" { 105 noVendor, _ = hglob.GetGlob(hglob.NormalizePath(cfg.ModuleConfig.NoVendor)) 106 } 107 108 return &Client{ 109 fs: fs, 110 ccfg: cfg, 111 logger: logger, 112 noVendor: noVendor, 113 moduleConfig: mcfg, 114 environ: env, 115 GoModulesFilename: goModFilename, 116 } 117 } 118 119 // Client contains most of the API provided by this package. 120 type Client struct { 121 fs afero.Fs 122 logger loggers.Logger 123 124 noVendor glob.Glob 125 126 ccfg ClientConfig 127 128 // The top level module config 129 moduleConfig Config 130 131 // Environment variables used in "go get" etc. 132 environ []string 133 134 // Set when Go modules are initialized in the current repo, that is: 135 // a go.mod file exists. 136 GoModulesFilename string 137 138 // Set if we get a exec.ErrNotFound when running Go, which is most likely 139 // due to being run on a system without Go installed. We record it here 140 // so we can give an instructional error at the end if module/theme 141 // resolution fails. 142 goBinaryStatus goBinaryStatus 143 } 144 145 // Graph writes a module dependenchy graph to the given writer. 146 func (c *Client) Graph(w io.Writer) error { 147 mc, coll := c.collect(true) 148 if coll.err != nil { 149 return coll.err 150 } 151 for _, module := range mc.AllModules { 152 if module.Owner() == nil { 153 continue 154 } 155 156 dep := pathVersion(module.Owner()) + " " + pathVersion(module) 157 if replace := module.Replace(); replace != nil { 158 if replace.Version() != "" { 159 dep += " => " + pathVersion(replace) 160 } else { 161 // Local dir. 162 dep += " => " + replace.Dir() 163 } 164 } 165 fmt.Fprintln(w, dep) 166 } 167 168 return nil 169 } 170 171 // Tidy can be used to remove unused dependencies from go.mod and go.sum. 172 func (c *Client) Tidy() error { 173 tc, coll := c.collect(false) 174 if coll.err != nil { 175 return coll.err 176 } 177 178 if coll.skipTidy { 179 return nil 180 } 181 182 return c.tidy(tc.AllModules, false) 183 } 184 185 // Vendor writes all the module dependencies to a _vendor folder. 186 // 187 // Unlike Go, we support it for any level. 188 // 189 // We, by default, use the /_vendor folder first, if found. To disable, 190 // run with 191 // 192 // hugo --ignoreVendorPaths=".*" 193 // 194 // Given a module tree, Hugo will pick the first module for a given path, 195 // meaning that if the top-level module is vendored, that will be the full 196 // set of dependencies. 197 func (c *Client) Vendor() error { 198 vendorDir := filepath.Join(c.ccfg.WorkingDir, vendord) 199 if err := c.rmVendorDir(vendorDir); err != nil { 200 return err 201 } 202 if err := c.fs.MkdirAll(vendorDir, 0755); err != nil { 203 return err 204 } 205 206 // Write the modules list to modules.txt. 207 // 208 // On the form: 209 // 210 // # github.com/alecthomas/chroma v0.6.3 211 // 212 // This is how "go mod vendor" does it. Go also lists 213 // the packages below it, but that is currently not applicable to us. 214 // 215 var modulesContent bytes.Buffer 216 217 tc, coll := c.collect(true) 218 if coll.err != nil { 219 return coll.err 220 } 221 222 for _, t := range tc.AllModules { 223 if t.Owner() == nil { 224 // This is the project. 225 continue 226 } 227 228 if !c.shouldVendor(t.Path()) { 229 continue 230 } 231 232 if !t.IsGoMod() && !t.Vendor() { 233 // We currently do not vendor components living in the 234 // theme directory, see https://github.com/gohugoio/hugo/issues/5993 235 continue 236 } 237 238 // See https://github.com/gohugoio/hugo/issues/8239 239 // This is an error situation. We need something to vendor. 240 if t.Mounts() == nil { 241 return fmt.Errorf("cannot vendor module %q, need at least one mount", t.Path()) 242 } 243 244 fmt.Fprintln(&modulesContent, "# "+t.Path()+" "+t.Version()) 245 246 dir := t.Dir() 247 248 for _, mount := range t.Mounts() { 249 sourceFilename := filepath.Join(dir, mount.Source) 250 targetFilename := filepath.Join(vendorDir, t.Path(), mount.Source) 251 fi, err := c.fs.Stat(sourceFilename) 252 if err != nil { 253 return fmt.Errorf("failed to vendor module: %w", err) 254 } 255 256 if fi.IsDir() { 257 if err := hugio.CopyDir(c.fs, sourceFilename, targetFilename, nil); err != nil { 258 return fmt.Errorf("failed to copy module to vendor dir: %w", err) 259 } 260 } else { 261 targetDir := filepath.Dir(targetFilename) 262 263 if err := c.fs.MkdirAll(targetDir, 0755); err != nil { 264 return fmt.Errorf("failed to make target dir: %w", err) 265 } 266 267 if err := hugio.CopyFile(c.fs, sourceFilename, targetFilename); err != nil { 268 return fmt.Errorf("failed to copy module file to vendor: %w", err) 269 } 270 } 271 } 272 273 // Include the resource cache if present. 274 resourcesDir := filepath.Join(dir, files.FolderResources) 275 _, err := c.fs.Stat(resourcesDir) 276 if err == nil { 277 if err := hugio.CopyDir(c.fs, resourcesDir, filepath.Join(vendorDir, t.Path(), files.FolderResources), nil); err != nil { 278 return fmt.Errorf("failed to copy resources to vendor dir: %w", err) 279 } 280 } 281 282 // Include the config directory if present. 283 configDir := filepath.Join(dir, "config") 284 _, err = c.fs.Stat(configDir) 285 if err == nil { 286 if err := hugio.CopyDir(c.fs, configDir, filepath.Join(vendorDir, t.Path(), "config"), nil); err != nil { 287 return fmt.Errorf("failed to copy config dir to vendor dir: %w", err) 288 } 289 } 290 291 // Also include any theme.toml or config.* or hugo.* files in the root. 292 configFiles, _ := afero.Glob(c.fs, filepath.Join(dir, "config.*")) 293 configFiles2, _ := afero.Glob(c.fs, filepath.Join(dir, "hugo.*")) 294 configFiles = append(configFiles, configFiles2...) 295 configFiles = append(configFiles, filepath.Join(dir, "theme.toml")) 296 for _, configFile := range configFiles { 297 if err := hugio.CopyFile(c.fs, configFile, filepath.Join(vendorDir, t.Path(), filepath.Base(configFile))); err != nil { 298 if !herrors.IsNotExist(err) { 299 return err 300 } 301 } 302 } 303 } 304 305 if modulesContent.Len() > 0 { 306 if err := afero.WriteFile(c.fs, filepath.Join(vendorDir, vendorModulesFilename), modulesContent.Bytes(), 0666); err != nil { 307 return err 308 } 309 } 310 311 return nil 312 } 313 314 // Get runs "go get" with the supplied arguments. 315 func (c *Client) Get(args ...string) error { 316 if len(args) == 0 || (len(args) == 1 && strings.Contains(args[0], "-u")) { 317 update := len(args) != 0 318 patch := update && (args[0] == "-u=patch") // 319 320 // We need to be explicit about the modules to get. 321 var modules []string 322 // Update all active modules if the -u flag presents. 323 if update { 324 mc, coll := c.collect(true) 325 if coll.err != nil { 326 return coll.err 327 } 328 for _, m := range mc.AllModules { 329 if m.Owner() == nil { 330 continue 331 } 332 modules = append(modules, m.Path()) 333 } 334 } else { 335 for _, m := range c.moduleConfig.Imports { 336 if !isProbablyModule(m.Path) { 337 // Skip themes/components stored below /themes etc. 338 // There may be false positives in the above, but those 339 // should be rare, and they will fail below with an 340 // "cannot find module providing ..." message. 341 continue 342 } 343 modules = append(modules, m.Path) 344 } 345 } 346 347 for _, m := range modules { 348 var args []string 349 350 if update && !patch { 351 args = append(args, "-u") 352 } else if update && patch { 353 args = append(args, "-u=patch") 354 } 355 args = append(args, m) 356 357 if err := c.get(args...); err != nil { 358 return err 359 } 360 } 361 362 return nil 363 } 364 365 return c.get(args...) 366 } 367 368 func (c *Client) get(args ...string) error { 369 var hasD bool 370 for _, arg := range args { 371 if arg == "-d" { 372 hasD = true 373 break 374 } 375 } 376 if !hasD { 377 // go get without the -d flag does not make sense to us, as 378 // it will try to build and install go packages. 379 args = append([]string{"-d"}, args...) 380 } 381 if err := c.runGo(context.Background(), c.logger.Out(), append([]string{"get"}, args...)...); err != nil { 382 return fmt.Errorf("failed to get %q: %w", args, err) 383 } 384 return nil 385 } 386 387 // Init initializes this as a Go Module with the given path. 388 // If path is empty, Go will try to guess. 389 // If this succeeds, this project will be marked as Go Module. 390 func (c *Client) Init(path string) error { 391 err := c.runGo(context.Background(), c.logger.Out(), "mod", "init", path) 392 if err != nil { 393 return fmt.Errorf("failed to init modules: %w", err) 394 } 395 396 c.GoModulesFilename = filepath.Join(c.ccfg.WorkingDir, goModFilename) 397 398 return nil 399 } 400 401 var verifyErrorDirRe = regexp.MustCompile(`dir has been modified \((.*?)\)`) 402 403 // Verify checks that the dependencies of the current module, 404 // which are stored in a local downloaded source cache, have not been 405 // modified since being downloaded. 406 func (c *Client) Verify(clean bool) error { 407 // TODO(bep) add path to mod clean 408 err := c.runVerify() 409 if err != nil { 410 if clean { 411 m := verifyErrorDirRe.FindAllStringSubmatch(err.Error(), -1) 412 if m != nil { 413 for i := 0; i < len(m); i++ { 414 c, err := hugofs.MakeReadableAndRemoveAllModulePkgDir(c.fs, m[i][1]) 415 if err != nil { 416 return err 417 } 418 fmt.Println("Cleaned", c) 419 } 420 } 421 // Try to verify it again. 422 err = c.runVerify() 423 } 424 } 425 return err 426 } 427 428 func (c *Client) Clean(pattern string) error { 429 mods, err := c.listGoMods() 430 if err != nil { 431 return err 432 } 433 434 var g glob.Glob 435 436 if pattern != "" { 437 var err error 438 g, err = hglob.GetGlob(pattern) 439 if err != nil { 440 return err 441 } 442 } 443 444 for _, m := range mods { 445 if m.Replace != nil || m.Main { 446 continue 447 } 448 449 if g != nil && !g.Match(m.Path) { 450 continue 451 } 452 dirCount, err := hugofs.MakeReadableAndRemoveAllModulePkgDir(c.fs, m.Dir) 453 if err == nil { 454 c.logger.Printf("hugo: removed %d dirs in module cache for %q", dirCount, m.Path) 455 } 456 } 457 return err 458 } 459 460 func (c *Client) runVerify() error { 461 return c.runGo(context.Background(), io.Discard, "mod", "verify") 462 } 463 464 func isProbablyModule(path string) bool { 465 return module.CheckPath(path) == nil 466 } 467 468 func (c *Client) listGoMods() (goModules, error) { 469 if c.GoModulesFilename == "" || !c.moduleConfig.hasModuleImport() { 470 return nil, nil 471 } 472 473 downloadModules := func(modules ...string) error { 474 args := []string{"mod", "download", "-modcacherw"} 475 args = append(args, modules...) 476 out := io.Discard 477 err := c.runGo(context.Background(), out, args...) 478 if err != nil { 479 return fmt.Errorf("failed to download modules: %w", err) 480 } 481 return nil 482 } 483 484 if err := downloadModules(); err != nil { 485 return nil, err 486 } 487 488 listAndDecodeModules := func(handle func(m *goModule) error, modules ...string) error { 489 b := &bytes.Buffer{} 490 args := []string{"list", "-m", "-json"} 491 if len(modules) > 0 { 492 args = append(args, modules...) 493 } else { 494 args = append(args, "all") 495 } 496 err := c.runGo(context.Background(), b, args...) 497 if err != nil { 498 return fmt.Errorf("failed to list modules: %w", err) 499 } 500 501 dec := json.NewDecoder(b) 502 for { 503 m := &goModule{} 504 if err := dec.Decode(m); err != nil { 505 if err == io.EOF { 506 break 507 } 508 return fmt.Errorf("failed to decode modules list: %w", err) 509 } 510 511 if err := handle(m); err != nil { 512 return err 513 } 514 } 515 return nil 516 } 517 518 var modules goModules 519 err := listAndDecodeModules(func(m *goModule) error { 520 modules = append(modules, m) 521 return nil 522 }) 523 if err != nil { 524 return nil, err 525 } 526 527 // From Go 1.17, go lazy loads transitive dependencies. 528 // That does not work for us. 529 // So, download these modules and update the Dir in the modules list. 530 var modulesToDownload []string 531 for _, m := range modules { 532 if m.Dir == "" { 533 modulesToDownload = append(modulesToDownload, fmt.Sprintf("%s@%s", m.Path, m.Version)) 534 } 535 } 536 537 if len(modulesToDownload) > 0 { 538 if err := downloadModules(modulesToDownload...); err != nil { 539 return nil, err 540 } 541 err := listAndDecodeModules(func(m *goModule) error { 542 if mm := modules.GetByPath(m.Path); mm != nil { 543 mm.Dir = m.Dir 544 } 545 return nil 546 }, modulesToDownload...) 547 if err != nil { 548 return nil, err 549 } 550 } 551 552 return modules, err 553 } 554 555 func (c *Client) rewriteGoMod(name string, isGoMod map[string]bool) error { 556 data, err := c.rewriteGoModRewrite(name, isGoMod) 557 if err != nil { 558 return err 559 } 560 if data != nil { 561 if err := afero.WriteFile(c.fs, filepath.Join(c.ccfg.WorkingDir, name), data, 0666); err != nil { 562 return err 563 } 564 } 565 566 return nil 567 } 568 569 func (c *Client) rewriteGoModRewrite(name string, isGoMod map[string]bool) ([]byte, error) { 570 if name == goModFilename && c.GoModulesFilename == "" { 571 // Already checked. 572 return nil, nil 573 } 574 575 modlineSplitter := getModlineSplitter(name == goModFilename) 576 577 b := &bytes.Buffer{} 578 f, err := c.fs.Open(filepath.Join(c.ccfg.WorkingDir, name)) 579 if err != nil { 580 if herrors.IsNotExist(err) { 581 // It's been deleted. 582 return nil, nil 583 } 584 return nil, err 585 } 586 defer f.Close() 587 588 scanner := bufio.NewScanner(f) 589 var dirty bool 590 591 for scanner.Scan() { 592 line := scanner.Text() 593 var doWrite bool 594 595 if parts := modlineSplitter(line); parts != nil { 596 modname, modver := parts[0], parts[1] 597 modver = strings.TrimSuffix(modver, "/"+goModFilename) 598 modnameVer := modname + " " + modver 599 doWrite = isGoMod[modnameVer] 600 } else { 601 doWrite = true 602 } 603 604 if doWrite { 605 fmt.Fprintln(b, line) 606 } else { 607 dirty = true 608 } 609 } 610 611 if !dirty { 612 // Nothing changed 613 return nil, nil 614 } 615 616 return b.Bytes(), nil 617 } 618 619 func (c *Client) rmVendorDir(vendorDir string) error { 620 modulestxt := filepath.Join(vendorDir, vendorModulesFilename) 621 622 if _, err := c.fs.Stat(vendorDir); err != nil { 623 return nil 624 } 625 626 _, err := c.fs.Stat(modulestxt) 627 if err != nil { 628 // If we have a _vendor dir without modules.txt it sounds like 629 // a _vendor dir created by others. 630 return errors.New("found _vendor dir without modules.txt, skip delete") 631 } 632 633 return c.fs.RemoveAll(vendorDir) 634 } 635 636 func (c *Client) runGo( 637 ctx context.Context, 638 stdout io.Writer, 639 args ...string) error { 640 if c.goBinaryStatus != 0 { 641 return nil 642 } 643 644 stderr := new(bytes.Buffer) 645 646 argsv := collections.StringSliceToInterfaceSlice(args) 647 argsv = append(argsv, hexec.WithEnviron(c.environ)) 648 argsv = append(argsv, hexec.WithStderr(io.MultiWriter(stderr, os.Stderr))) 649 argsv = append(argsv, hexec.WithStdout(stdout)) 650 argsv = append(argsv, hexec.WithDir(c.ccfg.WorkingDir)) 651 argsv = append(argsv, hexec.WithContext(ctx)) 652 653 cmd, err := c.ccfg.Exec.New("go", argsv...) 654 if err != nil { 655 return err 656 } 657 658 if err := cmd.Run(); err != nil { 659 if ee, ok := err.(*exec.Error); ok && ee.Err == exec.ErrNotFound { 660 c.goBinaryStatus = goBinaryStatusNotFound 661 return nil 662 } 663 664 if strings.Contains(stderr.String(), "invalid version: unknown revision") { 665 // See https://github.com/gohugoio/hugo/issues/6825 666 c.logger.Println(`An unknown revision most likely means that someone has deleted the remote ref (e.g. with a force push to GitHub). 667 To resolve this, you need to manually edit your go.mod file and replace the version for the module in question with a valid ref. 668 669 The easiest is to just enter a valid branch name there, e.g. master, which would be what you put in place of 'v0.5.1' in the example below. 670 671 require github.com/gohugoio/hugo-mod-jslibs/instantpage v0.5.1 672 673 If you then run 'hugo mod graph' it should resolve itself to the most recent version (or commit if no semver versions are available).`) 674 } 675 676 _, ok := err.(*exec.ExitError) 677 if !ok { 678 return fmt.Errorf("failed to execute 'go %v': %s %T", args, err, err) 679 } 680 681 // Too old Go version 682 if strings.Contains(stderr.String(), "flag provided but not defined") { 683 c.goBinaryStatus = goBinaryStatusTooOld 684 return nil 685 } 686 687 return fmt.Errorf("go command failed: %s", stderr) 688 689 } 690 691 return nil 692 } 693 694 func (c *Client) tidy(mods Modules, goModOnly bool) error { 695 isGoMod := make(map[string]bool) 696 for _, m := range mods { 697 if m.Owner() == nil { 698 continue 699 } 700 if m.IsGoMod() { 701 // Matching the format in go.mod 702 pathVer := m.Path() + " " + m.Version() 703 isGoMod[pathVer] = true 704 } 705 } 706 707 if err := c.rewriteGoMod(goModFilename, isGoMod); err != nil { 708 return err 709 } 710 711 if goModOnly { 712 return nil 713 } 714 715 if err := c.rewriteGoMod(goSumFilename, isGoMod); err != nil { 716 return err 717 } 718 719 return nil 720 } 721 722 func (c *Client) shouldVendor(path string) bool { 723 return c.noVendor == nil || !c.noVendor.Match(path) 724 } 725 726 func (c *Client) createThemeDirname(modulePath string, isProjectMod bool) (string, error) { 727 invalid := fmt.Errorf("invalid module path %q; must be relative to themesDir when defined outside of the project", modulePath) 728 729 modulePath = filepath.Clean(modulePath) 730 if filepath.IsAbs(modulePath) { 731 if isProjectMod { 732 return modulePath, nil 733 } 734 return "", invalid 735 } 736 737 moduleDir := filepath.Join(c.ccfg.ThemesDir, modulePath) 738 if !isProjectMod && !strings.HasPrefix(moduleDir, c.ccfg.ThemesDir) { 739 return "", invalid 740 } 741 return moduleDir, nil 742 } 743 744 // ClientConfig configures the module Client. 745 type ClientConfig struct { 746 Fs afero.Fs 747 Logger loggers.Logger 748 749 // If set, it will be run before we do any duplicate checks for modules 750 // etc. 751 HookBeforeFinalize func(m *ModulesConfig) error 752 753 // Ignore any _vendor directory for module paths matching the given pattern. 754 // This can be nil. 755 IgnoreVendor glob.Glob 756 757 // Absolute path to the project dir. 758 WorkingDir string 759 760 // Absolute path to the project's themes dir. 761 ThemesDir string 762 763 // Eg. "production" 764 Environment string 765 766 Exec *hexec.Exec 767 768 CacheDir string // Module cache 769 ModuleConfig Config 770 } 771 772 func (c ClientConfig) shouldIgnoreVendor(path string) bool { 773 return c.IgnoreVendor != nil && c.IgnoreVendor.Match(path) 774 } 775 776 type goBinaryStatus int 777 778 type goModule struct { 779 Path string // module path 780 Version string // module version 781 Versions []string // available module versions (with -versions) 782 Replace *goModule // replaced by this module 783 Time *time.Time // time version was created 784 Update *goModule // available update, if any (with -u) 785 Main bool // is this the main module? 786 Indirect bool // is this module only an indirect dependency of main module? 787 Dir string // directory holding files for this module, if any 788 GoMod string // path to go.mod file for this module, if any 789 Error *goModuleError // error loading module 790 } 791 792 type goModuleError struct { 793 Err string // the error itself 794 } 795 796 type goModules []*goModule 797 798 func (modules goModules) GetByPath(p string) *goModule { 799 if modules == nil { 800 return nil 801 } 802 803 for _, m := range modules { 804 if strings.EqualFold(p, m.Path) { 805 return m 806 } 807 } 808 809 return nil 810 } 811 812 func (modules goModules) GetMain() *goModule { 813 for _, m := range modules { 814 if m.Main { 815 return m 816 } 817 } 818 819 return nil 820 } 821 822 func getModlineSplitter(isGoMod bool) func(line string) []string { 823 if isGoMod { 824 return func(line string) []string { 825 if strings.HasPrefix(line, "require (") { 826 return nil 827 } 828 if !strings.HasPrefix(line, "require") && !strings.HasPrefix(line, "\t") { 829 return nil 830 } 831 line = strings.TrimPrefix(line, "require") 832 line = strings.TrimSpace(line) 833 line = strings.TrimSuffix(line, "// indirect") 834 835 return strings.Fields(line) 836 } 837 } 838 839 return func(line string) []string { 840 return strings.Fields(line) 841 } 842 } 843 844 func pathVersion(m Module) string { 845 versionStr := m.Version() 846 if m.Vendor() { 847 versionStr += "+vendor" 848 } 849 if versionStr == "" { 850 return m.Path() 851 } 852 return fmt.Sprintf("%s@%s", m.Path(), versionStr) 853 }