hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

client.go (20935B)

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