blob: f7bc0fa151bc049d3ac17b391381208ee3c20cb4 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package header
import (
"fmt"
"os"
"regexp"
"strconv"
"strings"
"time"
"github.com/apache/skywalking-eyes/assets"
"github.com/apache/skywalking-eyes/internal/logger"
"github.com/apache/skywalking-eyes/pkg/comments"
"github.com/apache/skywalking-eyes/pkg/license"
"github.com/bmatcuk/doublestar/v2"
)
type CommentOption string
var (
Always CommentOption = "always"
Never CommentOption = "never"
OnFailure CommentOption = "on-failure"
ASFNames = regexp.MustCompile("(?i)(the )?(Apache Software Foundation|ASF)")
)
type LicenseConfig struct {
SpdxID string `yaml:"spdx-id"`
CopyrightOwner string `yaml:"copyright-owner"`
CopyrightYear string `yaml:"copyright-year"`
SoftwareName string `yaml:"software-name"`
Content string `yaml:"content"`
Pattern string `yaml:"pattern"`
}
type ConfigHeader struct {
License LicenseConfig `yaml:"license"`
Paths []string `yaml:"paths"`
PathsIgnore []string `yaml:"paths-ignore"`
Comment CommentOption `yaml:"comment"`
// LicenseLocationThreshold specifies the index threshold where the license header can be located,
// after all, a "header" cannot be TOO far from the file start.
LicenseLocationThreshold int `yaml:"license-location-threshold"`
Languages map[string]comments.Language `yaml:"language"`
}
// NormalizedLicense returns the normalized string of the license content,
// "normalized" means the linebreaks and Punctuations are all trimmed.
func (config *ConfigHeader) NormalizedLicense() string {
return license.Normalize(config.GetLicenseContent())
}
func (config *ConfigHeader) LicensePattern(style *comments.CommentStyle) *regexp.Regexp {
pattern := config.License.Pattern
if pattern == "" || strings.TrimSpace(pattern) == "" {
return nil
}
// Trim leading and trailing newlines
pattern = strings.TrimSpace(pattern)
lines := strings.Split(pattern, "\n")
for i, line := range lines {
if line != "" {
lines[i] = fmt.Sprintf("%v %v", style.Middle, line)
} else {
lines[i] = style.Middle
}
}
lines = append(lines, "(("+style.Middle+"\n)*|\n*)")
if style.Start != style.Middle {
lines = append([]string{style.Start}, lines...)
}
if style.End != style.Middle {
lines = append(lines, style.End)
}
pattern = strings.Join(lines, "\n")
return regexp.MustCompile("(?s)" + pattern)
}
func (config *ConfigHeader) NormalizedPattern() *regexp.Regexp {
pattern := config.License.Pattern
if pattern == "" || strings.TrimSpace(pattern) == "" {
return nil
}
pattern = license.NormalizePattern(pattern)
return regexp.MustCompile("(?i).*" + pattern + ".*")
}
func (config *ConfigHeader) ShouldIgnore(path string) (bool, error) {
matched, err := tryMatchPatten(path, config.Paths)
if !matched || err != nil {
return !matched, err
}
ignored, err := tryMatchPatten(path, config.PathsIgnore)
if ignored || err != nil {
return ignored, err
}
return false, nil
}
func tryMatchPatten(path string, patterns []string) (bool, error) {
for _, pattern := range patterns {
if m, err := doublestar.Match(pattern, path); m || err != nil {
return m, err
}
}
if stat, err := os.Stat(path); err == nil {
for _, pattern := range patterns {
pattern = strings.TrimRight(pattern, "/")
if stat.Name() == pattern {
return true, nil
}
pattern += "/"
if strings.HasPrefix(path, pattern) {
return true, nil
}
}
}
return false, nil
}
func (config *ConfigHeader) Finalize() error {
if len(config.Paths) == 0 {
config.Paths = []string{"**"}
}
comments.OverrideLanguageCommentStyle(config.Languages)
logger.Log.Debugln("License header is:", config.NormalizedLicense())
if p := config.NormalizedPattern(); p != nil {
logger.Log.Debugln("Pattern is:", p)
}
if config.LicenseLocationThreshold <= 0 {
config.LicenseLocationThreshold = 80
}
return nil
}
func (config *ConfigHeader) GetLicenseContent() (c string) {
owner, name, year := config.License.CopyrightOwner, config.License.SoftwareName, config.License.CopyrightYear
if year == "" {
year = strconv.Itoa(time.Now().Year())
}
defer func() {
c = strings.ReplaceAll(c, "[year]", year)
c = strings.ReplaceAll(c, "[owner]", owner)
c = strings.ReplaceAll(c, "[software-name]", name)
}()
if c = strings.TrimSpace(config.License.Content); c != "" {
return config.License.Content // Do not change anything in user config
}
c, err := readLicenseFromSpdx(config)
if err != nil {
logger.Log.Warnln(err)
return ""
}
return c
}
func readLicenseFromSpdx(config *ConfigHeader) (string, error) {
spdxID, owner := config.License.SpdxID, config.License.CopyrightOwner
filename := fmt.Sprintf("header-templates/%v.txt", spdxID)
if spdxID == "Apache-2.0" && ASFNames.MatchString(owner) {
// Note that the Apache Software Foundation uses a different source header that is related to our use of a CLA.
// Our instructions for our project's source headers are here (https://www.apache.org/legal/src-headers.html#headers).
filename = "header-templates/Apache-2.0-ASF.txt"
}
content, err := assets.Asset(filename)
if err != nil {
return "", fmt.Errorf("failed to find a license template for spdx id %v, %w", spdxID, err)
}
return string(content), nil
}