| // 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 |
| } |