github.com/blend/go-sdk@v1.20220411.3/logger/scopes.go (about)

     1  /*
     2  
     3  Copyright (c) 2022 - Present. Blend Labs, Inc. All rights reserved
     4  Use of this source code is governed by a MIT license that can be found in the LICENSE file.
     5  
     6  */
     7  
     8  package logger
     9  
    10  import (
    11  	"path/filepath"
    12  	"strings"
    13  
    14  	"github.com/blend/go-sdk/stringutil"
    15  )
    16  
    17  // NewScopes yields a scope set from a list of scopes.
    18  //
    19  // Each scope should be path formatted, e.g. `foo/bar/baz` and can include
    20  // wildcard segments for glob matching, e.g. `foo/*/baz`.
    21  //
    22  // A single `*` is interpretted as `All` and will match any scope path.
    23  func NewScopes(scopes ...string) *Scopes {
    24  	scopeSet := &Scopes{
    25  		scopes: make(map[string]bool),
    26  	}
    27  	for _, rawScope := range scopes {
    28  		parsedScope := strings.ToLower(strings.TrimSpace(rawScope))
    29  		if parsedScope == ScopeAll {
    30  			scopeSet.all = true
    31  			continue
    32  		}
    33  		if strings.HasPrefix(parsedScope, "-") {
    34  			scopeSet.scopes[strings.TrimPrefix(parsedScope, "-")] = false
    35  		} else {
    36  			scopeSet.scopes[parsedScope] = true
    37  		}
    38  	}
    39  	return scopeSet
    40  }
    41  
    42  // ScopesAll returns a preset scopes with the all flag flipped.
    43  func ScopesAll() *Scopes {
    44  	return &Scopes{scopes: make(map[string]bool), all: true}
    45  }
    46  
    47  // ScopesNone returns a preset empty scopes.
    48  func ScopesNone() *Scopes {
    49  	return &Scopes{scopes: make(map[string]bool)}
    50  }
    51  
    52  // Scopes is a set of scopes.
    53  type Scopes struct {
    54  	all    bool
    55  	scopes map[string]bool
    56  }
    57  
    58  // Enable enables a set of scopes.
    59  //
    60  // The scopes should be given in filepath form, e.g. `foo/bar/*`.
    61  func (s *Scopes) Enable(scopes ...string) {
    62  	for _, scope := range scopes {
    63  		s.scopes[strings.ToLower(strings.TrimSpace(scope))] = true
    64  	}
    65  }
    66  
    67  // Disable disables a set of scopes.
    68  //
    69  // The scopes should be given in filepath form, e.g. `foo/bar/*`.
    70  func (s *Scopes) Disable(scopes ...string) {
    71  	for _, scope := range scopes {
    72  		s.scopes[strings.ToLower(strings.TrimSpace(scope))] = false
    73  	}
    74  }
    75  
    76  // SetAll flips the `all` bit on the flag set to true.
    77  //
    78  // Note: flags that are explicitly disabled will remain disabled.
    79  func (s *Scopes) SetAll() {
    80  	s.all = true
    81  }
    82  
    83  // All returns if the all bit is flipped to true.
    84  func (s *Scopes) All() bool {
    85  	return s.all
    86  }
    87  
    88  // SetNone disables the `all` bit, and empties the scopes
    89  // set, resulting in calls to `None()` to return true.
    90  //
    91  // You should view this method as a way to reset or zero a scopes set.
    92  func (s *Scopes) SetNone() {
    93  	s.all = false
    94  	s.scopes = make(map[string]bool)
    95  }
    96  
    97  // None returns if the all bit is set to false, and
    98  // there are no scope specific overrides.
    99  //
   100  // It is functionally equivalent to an `IsZero()` method.
   101  func (s *Scopes) None() bool {
   102  	return !s.all && len(s.scopes) == 0
   103  }
   104  
   105  // IsEnabled returns if a given logger scope is enabled.
   106  func (s Scopes) IsEnabled(scopePath ...string) bool {
   107  	scopeJoined := filepath.Join(scopePath...)
   108  	if s.all {
   109  		// check if we explicitly disabled the scope
   110  		return !s.isScopeExplicitlyDisabled(scopeJoined)
   111  	}
   112  	// we treat empty scopes as `none`
   113  	if len(s.scopes) == 0 {
   114  		return false
   115  	}
   116  	// if there is no scope, assumed to pass
   117  	if len(scopeJoined) == 0 {
   118  		return true
   119  	}
   120  	// return if we either explicitly enabled or disabled the scope
   121  	// with path globs in the scopes map.
   122  	return s.isScopeEnabled(scopeJoined)
   123  }
   124  
   125  // String returns a string representation of the scopes.
   126  func (s Scopes) String() string {
   127  	return strings.Join(s.Scopes(), ", ")
   128  }
   129  
   130  // Scopes returns an array of scopes.
   131  func (s Scopes) Scopes() []string {
   132  	var scopes []string
   133  	if s.all {
   134  		scopes = []string{ScopeAll}
   135  	}
   136  	for key, enabled := range s.scopes {
   137  		if key != FlagAll {
   138  			if enabled {
   139  				if !s.all {
   140  					scopes = append(scopes, string(key))
   141  				}
   142  			} else {
   143  				scopes = append(scopes, "-"+string(key))
   144  			}
   145  		}
   146  	}
   147  	return scopes
   148  }
   149  
   150  //
   151  // internal helpers
   152  //
   153  
   154  // isScopeEnabled returns if a scopePath is enabled strictly by
   155  // a lookup to the underlying scopes map.
   156  func (s Scopes) isScopeEnabled(scopePath string) bool {
   157  	for pattern, enabled := range s.scopes {
   158  		if s.matches(scopePath, pattern) {
   159  			return enabled
   160  		}
   161  	}
   162  	// no matching entry is a failure.
   163  	return false
   164  }
   165  
   166  // isScopeDisabled returns if a scopePath is explicitly disabled
   167  // that is, has a matching glob in the scopes map that is set to false.
   168  //
   169  // it is differentiated from `isScopeEnabled`
   170  func (s Scopes) isScopeExplicitlyDisabled(subj string) bool {
   171  	for pattern, enabled := range s.scopes {
   172  		if !enabled && s.matches(subj, pattern) {
   173  			return true
   174  		}
   175  	}
   176  	// if we didn't find a matching scope, assume it's not disabled
   177  	return false
   178  }
   179  
   180  func (s Scopes) matches(subj, pattern string) (output bool) {
   181  	output = stringutil.Glob(subj, pattern)
   182  	return
   183  }