Skip to content

Commit

Permalink
Adding automation to add boilerplate license/copyrights headers
Browse files Browse the repository at this point in the history
Signed-off-by: Mohit Sharma <[email protected]>

Adding license info

Fixing some flags and test

Signed-off-by: Mohit Sharma <[email protected]>

Fixing boilerplate

Making few optimistaion

Signed-off-by: Mohit Sharma <[email protected]>

Modification and resolving review Comments

fixes

Adding recommendded fixes and resolving comments

Adding recommendded fixes

Minor Modifications

Fixing CI test

Resolving  Suggestions

Fixing unused imports from removing config_test

Adding Docs

Adding license Data

addding skip in existing boilerplate script

adding test case validation
  • Loading branch information
mohitsharma-in committed Jun 27, 2023
1 parent 0dc180c commit e37c5e7
Show file tree
Hide file tree
Showing 14 changed files with 460 additions and 10 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ peribolos: $(PERIBOLOS_CMD)

.PHONY: test
test: config
go test ./... --config=$(MERGED_CONFIG)
CONFIG_PATH=$(MERGED_CONFIG) go test ./...

.PHONY: verify
verify:
Expand Down
53 changes: 53 additions & 0 deletions cmd/k-license/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# k-license

## Prepends project files with given template.

* Can be used for adding licence or copyright information on src files of project.
* Skip directory, if template (as provided) already present
* Supports Golang/Python source files, Dockerfile, Makefiles and bash scripts

## Build

```
go build -o k-license k-license.go
```

Example
To Apply header from ./boilerplate
folder default

```
$ go run k-license.go add --help
Add Headers to files
Usage:
k-license add [flags]
Flags:
--confirm
-e, --exclude strings comma-separated list of directories to exclude (default [external/bazel_tools,.git,node_modules,_output,third_party,vendor,verify/boilerplate/test])
-h, --help help for add
--path string Defaults to Current directory (default ".")
--templates string directory containing license templates (default "../../hack/boilerplate")
```

## Help

```
$ go run k-license.go --help
Tool for Adding license Headers
Usage:
k-license [command]
Available Commands:
add Add Headers to files
help Help about any command
Flags:
-h, --help help for k-license
Use "k-license [command] --help" for more information about a command.
```
254 changes: 254 additions & 0 deletions cmd/k-license/k-license.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
/*
Copyright 2023 The Kubernetes Authors.
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 main

import (
"fmt"
"io/fs"
"os"
"path/filepath"
"strconv"
"strings"
"time"

"github.com/spf13/cobra"
)

var excludeDirsLocations = []string{
"external/bazel_tools",
".git",
"node_modules",
"_output",
"third_party",
"vendor",
"verify/boilerplate/test",
}
var GENERATED_GO_MARKERS = [7]string{
"// Code generated by client-gen. DO NOT EDIT.",
"// Code generated by controller-gen. DO NOT EDIT.",
"// Code generated by counterfeiter. DO NOT EDIT.",
"// Code generated by deepcopy-gen. DO NOT EDIT.",
"// Code generated by informer-gen. DO NOT EDIT.",
"// Code generated by lister-gen. DO NOT EDIT.",
"// Code generated by protoc-gen-go. DO NOT EDIT.",
}

var codeFileExts = map[string]bool{
".go": true,
".c": true,
".h": true,
".ipynb": true,
".py": true,
".java": true,
".cpp": true,
".sh": true,
}

var buildFileExts = map[string]bool{
"Makefile": true,
"Dockerfile": true,
}

type Options struct {
templatesDir string
excludeDirs []string
path string
confirm bool
}
type templateFileType struct {
fileExtension string // store file extension strings like ".sh", "Makefile", etc.
templateFileName string // store template file names like "boilerplate.sh.txt", etc
}

var templateFileTypes = []templateFileType{
{".sh", "boilerplate.sh.txt"},
{"Makefile", "boilerplate.Makefile.txt"},
{"Dockerfile", "boilerplate.Dockerfile.txt"},
{".py", "boilerplate.py.txt"},
{".go", "boilerplate.go.txt"},
}

var opts = &Options{}

func main() {
rootCmd := &cobra.Command{
Use: "k-license",
Short: "Tool for Adding license Headers",
}
addCmd := &cobra.Command{
Use: "add",
Short: "Add Headers to files",
RunE: func(cmd *cobra.Command, args []string) error {
return opts.Run()
},
}

addCmd.Flags().StringVar(&opts.templatesDir, "templates", "../../hack/boilerplate", "directory containing license templates")
addCmd.Flags().StringSliceVarP(&opts.excludeDirs, "exclude", "e", excludeDirsLocations, "comma-separated list of directories to exclude")
addCmd.Flags().StringVar(&opts.path, "path", ".", "Defaults to Current directory")
addCmd.Flags().BoolVar(&opts.confirm, "confirm", false, "confirm actually adding license boilerplate to files")
rootCmd.AddCommand(addCmd)
if err := rootCmd.Execute(); err != nil {
os.Exit(1)
}

}
func (opts *Options) Run() error {
files := 0
fileList := make([]string, 0)
err := filepath.WalkDir(opts.path, func(path string, info fs.DirEntry, err error) error {
if info.IsDir() && containsExcluded(opts.excludeDirs, info.Name()) {
return filepath.SkipDir
}
if !info.IsDir() && (isCodeFile(path) || isBuildFile(path)) && !isGenerateFile(path) {
hasLic, err := hasLicense(path)
if !hasLic {
currentYear := strconv.Itoa(time.Now().Year())
if opts.confirm {
err := addLicense(path, opts.templatesDir, currentYear)
if err != nil {
return err
}
fmt.Printf("Modified %s file\n", path)
}
fileList = append(fileList, path)
files++
}
return err

}
if err != nil {
return err
}
return nil
})
if opts.confirm {
fmt.Printf("Modified %v files\n", files)
} else {
fmt.Printf("DRY RUN: No file changes will be made! To make file modifications, rerun the command with \"--confirm\" flag\n")
if files == 0 {
fmt.Printf("All files have appropriate License Headers. No changes required.\n")
}
if files > 0 {
fmt.Printf("%v files will be modified to add License Headers\n", files)
fmt.Printf("Listing files to be modified:\n")
for _, file := range fileList {
fmt.Printf("%s\n", file)
}
}

}
return err
}

// Looks for the Excluded files/directroy
func containsExcluded(list []string, str string) bool {
for _, item := range list {
if item == str {
fmt.Printf("Skipping %s as this is Part of exclude list\n", str)
return true
}
}
return false
}

// Check if the file is code File
func isCodeFile(path string) bool {
return codeFileExts[strings.ToLower(filepath.Ext(path))]
}

// Check if the file is build File
func isBuildFile(path string) bool {
return buildFileExts[filepath.Base(path)]
}

// Checks if the file is auto generated
func isGenerateFile(path string) bool {
data, err := os.ReadFile(path)
if err != nil {
return false
}
for _, ft := range GENERATED_GO_MARKERS {
if strings.Contains(string(data), ft) {
fmt.Printf("Skipping File: %s since this is autogenerated file \n", path)
return true
}
}
return false
}

// Checks for license in the file
func hasLicense(path string) (bool, error) {
data, err := os.ReadFile(path)
if err != nil {
return false, err
}
if strings.Contains(string(data), "Copyright") && strings.Contains(string(data), "Licensed under the Apache License") {
fmt.Printf("Skipping File: %s Already have templates added\n", path)
return true, nil
} else {
return false, nil
}
}

// Reads templates from directory
func getTemplateFile(path string) string {
for _, file := range templateFileTypes {
if strings.HasSuffix(path, file.fileExtension) || filepath.Base(path) == file.fileExtension {
return file.templateFileName
}
}
return "boilerplate.tf.txt"
}

// Adds License Headers
func addLicense(path, templatesDir, year string) error {
tmplData, err := os.ReadFile(filepath.Join(templatesDir, getTemplateFile(path)))
if err != nil {
return err
}
// Replace placeholders with actual values
tmpl := strings.ReplaceAll(string(tmplData), "YEAR", year)

if fileSize(path) {
codeData, err := os.ReadFile(path)
if err != nil {
return err
}
newData := append([]byte(tmpl), []byte("\n")...)
newData = append(newData, codeData...)
return os.WriteFile(path, newData, 0644)
}
return nil
}

// Check for empty file
func fileSize(path string) bool {
fileInfo, err := os.Stat(path)
if err != nil {
fmt.Println(err)
return false
}

if fileInfo.Size() == 0 {
fmt.Println("The file is empty No Modification Required")
return false
} else {
fmt.Println("The file is not empty")
return true
}
}
Loading

0 comments on commit e37c5e7

Please sign in to comment.