blob: ef8abdffa7df98c7048a9f7faecd81d1171f090d [file] [log] [blame]
/*
Copyright © 2020 Hoshea Jiang <hoshea@apache.org>
Licensed 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 cmd
import (
"bufio"
"encoding/json"
"fmt"
"github.com/spf13/cobra"
"io/ioutil"
"license-checker/util"
"os"
"path/filepath"
"strings"
)
var (
// cfgFile is the path to the config file.
cfgFile string
// checkPath is the path to check license.
checkPath string
// loose is flag to enable loose mode.
loose bool
// verbose is flag to enable verbose mode.
verbose bool
)
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "license-checker [flags]",
Long: `license-checker walks the specified path recursively and checks
if the specified files have the license header in the config file.`,
Run: func(cmd *cobra.Command, args []string) {
config, err := LoadConfig()
if err != nil {
fmt.Println(err)
}
res, err := WalkAndCheck(checkPath, config)
if err != nil {
fmt.Println(err)
}
printResult(res)
},
}
// Execute sets flags to the root command appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", ".licenserc.json", "the config file")
rootCmd.PersistentFlags().StringVarP(&checkPath, "path", "p", ".", "the path to check")
rootCmd.PersistentFlags().BoolVarP(&loose, "loose", "l", false, "loose mode")
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose mode")
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
type excludeConfig struct {
Files []string `json:"files"`
Extensions []string `json:"extensions"`
Directories []string `json:"directories"`
}
type Config struct {
LicenseStrict []string `json:"licenseStrict"`
LicenseLoose []string `json:"licenseLoose"`
TargetFiles []string `json:"targetFiles"`
Exclude excludeConfig `json:"exclude"`
}
type Result struct {
Success []string `json:"success"`
Failure []string `json:"failure"`
}
// LoadConfig reads in config file.
func LoadConfig() (*Config, error) {
var config Config
bytes, err := ioutil.ReadFile(cfgFile)
if err != nil {
return nil, err
}
err = json.Unmarshal(bytes, &config)
if err != nil {
return nil, err
}
return &config, nil
}
// WalkAndCheck traverses the path p and check every target file's license.
func WalkAndCheck(p string, cfg *Config) (*Result, error) {
var license []string
if loose {
license = cfg.LicenseLoose
} else {
license = cfg.LicenseStrict
}
inExcludeDir := util.InStrSliceMapKeyFunc(cfg.Exclude.Directories)
inExcludeExt := util.InStrSliceMapKeyFunc(cfg.Exclude.Extensions)
inExcludeFiles := util.InStrSliceMapKeyFunc(cfg.Exclude.Files)
var result Result
err := filepath.Walk(p, func(path string, fi os.FileInfo, err error) error {
if err != nil {
fmt.Println(err) // can't walk here,
return nil // but continue walking elsewhere
}
if fi.IsDir() {
if inExcludeDir(fi.Name()) ||
inExcludeDir(util.CleanPathPrefixes(path, []string{p, string(os.PathSeparator)})) {
return filepath.SkipDir
}
} else {
ext := util.GetFileExtension(fi.Name())
if inExcludeFiles(fi.Name()) || inExcludeExt(ext) {
return nil
}
ok, err := CheckLicense(path, license)
if err != nil {
return err
}
if ok {
result.Success = append(result.Success, fmt.Sprintf("[Pass]: %s", path))
} else {
result.Failure = append(result.Failure, fmt.Sprintf("[No Specified License]: %s", path))
}
}
return nil
})
return &result, err
}
// CheckLicense checks license of single file.
func CheckLicense(filePath string, license []string) (bool, error) {
file, err := os.Open(filePath)
if err != nil {
return false, err
}
defer file.Close()
index := 0
scanner := bufio.NewScanner(file)
for scanner.Scan() && index < len(license) {
line := scanner.Text()
if strings.Contains(line, license[index]) {
index++
}
}
if index != len(license) {
return false, nil
}
return true, nil
}
// printResult prints license check result.
func printResult(r *Result) {
if verbose {
for _, s := range r.Success {
fmt.Println(s)
}
}
for _, s := range r.Failure {
fmt.Println(s)
}
fmt.Printf("Total check %d files, success: %d, failure: %d\n",
len(r.Success)+len(r.Failure), len(r.Success), len(r.Failure))
}