github.com/apache/skywalking-eyes@v0.6.0/pkg/header/config.go (about)

     1  // Licensed to the Apache Software Foundation (ASF) under one
     2  // or more contributor license agreements.  See the NOTICE file
     3  // distributed with this work for additional information
     4  // regarding copyright ownership.  The ASF licenses this file
     5  // to you under the Apache License, Version 2.0 (the
     6  // "License"); you may not use this file except in compliance
     7  // with the License.  You may obtain a copy of the License at
     8  //
     9  //     http://www.apache.org/licenses/LICENSE-2.0
    10  //
    11  // Unless required by applicable law or agreed to in writing,
    12  // software distributed under the License is distributed on an
    13  // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    14  // KIND, either express or implied.  See the License for the
    15  // specific language governing permissions and limitations
    16  // under the License.
    17  
    18  package header
    19  
    20  import (
    21  	"fmt"
    22  	"os"
    23  	"regexp"
    24  	"strconv"
    25  	"strings"
    26  	"time"
    27  
    28  	"github.com/apache/skywalking-eyes/assets"
    29  	"github.com/apache/skywalking-eyes/internal/logger"
    30  	"github.com/apache/skywalking-eyes/pkg/comments"
    31  	"github.com/apache/skywalking-eyes/pkg/license"
    32  
    33  	"github.com/bmatcuk/doublestar/v2"
    34  )
    35  
    36  type CommentOption string
    37  
    38  var (
    39  	Always    CommentOption = "always"
    40  	Never     CommentOption = "never"
    41  	OnFailure CommentOption = "on-failure"
    42  
    43  	ASFNames = regexp.MustCompile("(?i)(the )?(Apache Software Foundation|ASF)")
    44  )
    45  
    46  type LicenseConfig struct {
    47  	SpdxID         string `yaml:"spdx-id"`
    48  	CopyrightOwner string `yaml:"copyright-owner"`
    49  	CopyrightYear  string `yaml:"copyright-year"`
    50  	SoftwareName   string `yaml:"software-name"`
    51  	Content        string `yaml:"content"`
    52  	Pattern        string `yaml:"pattern"`
    53  }
    54  
    55  type ConfigHeader struct {
    56  	License     LicenseConfig `yaml:"license"`
    57  	Paths       []string      `yaml:"paths"`
    58  	PathsIgnore []string      `yaml:"paths-ignore"`
    59  	Comment     CommentOption `yaml:"comment"`
    60  
    61  	// LicenseLocationThreshold specifies the index threshold where the license header can be located,
    62  	// after all, a "header" cannot be TOO far from the file start.
    63  	LicenseLocationThreshold int                          `yaml:"license-location-threshold"`
    64  	Languages                map[string]comments.Language `yaml:"language"`
    65  }
    66  
    67  // NormalizedLicense returns the normalized string of the license content,
    68  // "normalized" means the linebreaks and Punctuations are all trimmed.
    69  func (config *ConfigHeader) NormalizedLicense() string {
    70  	return license.Normalize(config.GetLicenseContent())
    71  }
    72  
    73  func (config *ConfigHeader) LicensePattern(style *comments.CommentStyle) *regexp.Regexp {
    74  	pattern := config.License.Pattern
    75  
    76  	if pattern == "" || strings.TrimSpace(pattern) == "" {
    77  		return nil
    78  	}
    79  
    80  	// Trim leading and trailing newlines
    81  	pattern = strings.TrimSpace(pattern)
    82  	lines := strings.Split(pattern, "\n")
    83  	for i, line := range lines {
    84  		if line != "" {
    85  			lines[i] = fmt.Sprintf("%v %v", style.Middle, line)
    86  		} else {
    87  			lines[i] = style.Middle
    88  		}
    89  	}
    90  
    91  	lines = append(lines, "(("+style.Middle+"\n)*|\n*)")
    92  
    93  	if style.Start != style.Middle {
    94  		lines = append([]string{style.Start}, lines...)
    95  	}
    96  
    97  	if style.End != style.Middle {
    98  		lines = append(lines, style.End)
    99  	}
   100  
   101  	pattern = strings.Join(lines, "\n")
   102  
   103  	return regexp.MustCompile("(?s)" + pattern)
   104  }
   105  
   106  func (config *ConfigHeader) NormalizedPattern() *regexp.Regexp {
   107  	pattern := config.License.Pattern
   108  
   109  	if pattern == "" || strings.TrimSpace(pattern) == "" {
   110  		return nil
   111  	}
   112  
   113  	pattern = license.NormalizePattern(pattern)
   114  
   115  	return regexp.MustCompile("(?i).*" + pattern + ".*")
   116  }
   117  
   118  func (config *ConfigHeader) ShouldIgnore(path string) (bool, error) {
   119  	matched, err := tryMatchPatten(path, config.Paths)
   120  	if !matched || err != nil {
   121  		return !matched, err
   122  	}
   123  
   124  	ignored, err := tryMatchPatten(path, config.PathsIgnore)
   125  	if ignored || err != nil {
   126  		return ignored, err
   127  	}
   128  
   129  	return false, nil
   130  }
   131  
   132  func tryMatchPatten(path string, patterns []string) (bool, error) {
   133  	for _, pattern := range patterns {
   134  		if m, err := doublestar.Match(pattern, path); m || err != nil {
   135  			return m, err
   136  		}
   137  	}
   138  
   139  	if stat, err := os.Stat(path); err == nil {
   140  		for _, pattern := range patterns {
   141  			pattern = strings.TrimRight(pattern, "/")
   142  			if stat.Name() == pattern {
   143  				return true, nil
   144  			}
   145  			pattern += "/"
   146  			if strings.HasPrefix(path, pattern) {
   147  				return true, nil
   148  			}
   149  		}
   150  	}
   151  
   152  	return false, nil
   153  }
   154  
   155  func (config *ConfigHeader) Finalize() error {
   156  	if len(config.Paths) == 0 {
   157  		config.Paths = []string{"**"}
   158  	}
   159  
   160  	comments.OverrideLanguageCommentStyle(config.Languages)
   161  
   162  	logger.Log.Debugln("License header is:", config.NormalizedLicense())
   163  
   164  	if p := config.NormalizedPattern(); p != nil {
   165  		logger.Log.Debugln("Pattern is:", p)
   166  	}
   167  
   168  	if config.LicenseLocationThreshold <= 0 {
   169  		config.LicenseLocationThreshold = 80
   170  	}
   171  
   172  	return nil
   173  }
   174  
   175  func (config *ConfigHeader) GetLicenseContent() (c string) {
   176  	owner, name, year := config.License.CopyrightOwner, config.License.SoftwareName, config.License.CopyrightYear
   177  	if year == "" {
   178  		year = strconv.Itoa(time.Now().Year())
   179  	}
   180  
   181  	defer func() {
   182  		c = strings.ReplaceAll(c, "[year]", year)
   183  		c = strings.ReplaceAll(c, "[owner]", owner)
   184  		c = strings.ReplaceAll(c, "[software-name]", name)
   185  	}()
   186  
   187  	if c = strings.TrimSpace(config.License.Content); c != "" {
   188  		return config.License.Content // Do not change anything in user config
   189  	}
   190  	c, err := readLicenseFromSpdx(config)
   191  	if err != nil {
   192  		logger.Log.Warnln(err)
   193  		return ""
   194  	}
   195  
   196  	return c
   197  }
   198  
   199  func readLicenseFromSpdx(config *ConfigHeader) (string, error) {
   200  	spdxID, owner := config.License.SpdxID, config.License.CopyrightOwner
   201  	filename := fmt.Sprintf("header-templates/%v.txt", spdxID)
   202  
   203  	if spdxID == "Apache-2.0" && ASFNames.MatchString(owner) {
   204  		// Note that the Apache Software Foundation uses a different source header that is related to our use of a CLA.
   205  		// Our instructions for our project's source headers are here (https://www.apache.org/legal/src-headers.html#headers).
   206  		filename = "header-templates/Apache-2.0-ASF.txt"
   207  	}
   208  
   209  	content, err := assets.Asset(filename)
   210  	if err != nil {
   211  		return "", fmt.Errorf("failed to find a license template for spdx id %v, %w", spdxID, err)
   212  	}
   213  	return string(content), nil
   214  }