go.mway.dev/x@v0.0.0-20240520034138-950aede9a3fb/env/path.go (about)

     1  // Copyright (c) 2023 Matt Way
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to
     5  // deal in the Software without restriction, including without limitation the
     6  // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
     7  // sell copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    18  // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
    19  // IN THE THE SOFTWARE.
    20  
    21  package env
    22  
    23  import (
    24  	"bytes"
    25  	"fmt"
    26  	"os"
    27  )
    28  
    29  const _envKeyPath = "PATH"
    30  
    31  // Path represents $PATH within the environment.
    32  type Path struct {
    33  	v *Var
    34  }
    35  
    36  // NewPath returns a [Path] that holds the current value of $PATH.
    37  func NewPath() Path {
    38  	return Path{
    39  		v: NewVar(_envKeyPath),
    40  	}
    41  }
    42  
    43  // Prepend prepends paths to the current value of p.
    44  func (p Path) Prepend(paths ...string) (newpath Path, err error) {
    45  	defer func() {
    46  		if err != nil {
    47  			err = fmt.Errorf("failed to prepend path(s): %w", err)
    48  		}
    49  	}()
    50  
    51  	if len(paths) == 0 {
    52  		return p, nil
    53  	}
    54  
    55  	// Calculate how much extra storage is needed.
    56  	extra := len(paths) // for delimiters
    57  	for _, path := range paths {
    58  		extra += len(path)
    59  	}
    60  
    61  	// Prepend all of the path fragments to the current value.
    62  	var buf bytes.Buffer
    63  	buf.Grow(len(p.v.Value()) + extra)
    64  	for _, path := range paths {
    65  		if len(path) == 0 {
    66  			continue
    67  		}
    68  
    69  		buf.WriteString(path)               //nolint:errcheck
    70  		buf.WriteByte(os.PathListSeparator) //nolint:errcheck
    71  	}
    72  
    73  	// Write the current value after any prepended path(s).
    74  	buf.WriteString(p.Value()) //nolint:errcheck
    75  
    76  	// Create a copy of this Path and persist the change to the copy (and the
    77  	// environment).
    78  	tmp := Path{
    79  		v: p.v.Clone(),
    80  	}
    81  	if err = tmp.v.Set(buf.String()); err != nil {
    82  		return
    83  	}
    84  	return tmp, nil
    85  }
    86  
    87  // MustPrepend calls p.Prepend and panics if it returns an error.
    88  func (p Path) MustPrepend(paths ...string) Path {
    89  	newpath, err := p.Prepend(paths...)
    90  	if err != nil {
    91  		panic(err)
    92  	}
    93  	return newpath
    94  }
    95  
    96  // Append appends paths to the current value of p.
    97  func (p Path) Append(paths ...string) (newpath Path, err error) {
    98  	defer func() {
    99  		if err != nil {
   100  			err = fmt.Errorf("failed to append path(s): %w", err)
   101  		}
   102  	}()
   103  
   104  	if len(paths) == 0 {
   105  		return p, nil
   106  	}
   107  
   108  	// Calculate how much extra storage is needed.
   109  	extra := len(paths) // for delimiters
   110  	for _, path := range paths {
   111  		extra += len(path)
   112  	}
   113  
   114  	var buf bytes.Buffer
   115  	buf.Grow(len(p.v.Value()) + extra)
   116  
   117  	// Write the current value before any appended path(s).
   118  	buf.WriteString(p.v.Value()) //nolint:errcheck
   119  
   120  	// Append all of the path fragments to the current value.
   121  	for _, path := range paths {
   122  		if len(path) == 0 {
   123  			continue
   124  		}
   125  
   126  		buf.WriteByte(os.PathListSeparator) //nolint:errcheck
   127  		buf.WriteString(path)               //nolint:errcheck
   128  	}
   129  
   130  	// Create a copy of this Path and persist the change to the copy (and the
   131  	// environment).
   132  	tmp := Path{
   133  		v: p.v.Clone(),
   134  	}
   135  	if err = tmp.v.Set(buf.String()); err != nil {
   136  		return
   137  	}
   138  	return tmp, nil
   139  }
   140  
   141  // MustAppend calls p.Append and panics if it returns an error.
   142  func (p Path) MustAppend(paths ...string) Path {
   143  	newpath, err := p.Append(paths...)
   144  	if err != nil {
   145  		panic(err)
   146  	}
   147  	return newpath
   148  }
   149  
   150  // Restore PATH to its original state within the environment.
   151  func (p Path) Restore() error {
   152  	return p.v.Restore()
   153  }
   154  
   155  // MustRestore calls p.Restore and panics if it returns an error.
   156  func (p Path) MustRestore() {
   157  	if err := p.Restore(); err != nil {
   158  		panic(err)
   159  	}
   160  }
   161  
   162  // Value returns the current value of p.
   163  func (p Path) Value() string {
   164  	return p.v.Value()
   165  }
   166  
   167  // String returns p as a string.
   168  func (p Path) String() string {
   169  	return p.Value()
   170  }