licensing/license.go (109 lines of code) (raw):

// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. 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 licensing import ( "bufio" "bytes" "errors" "io" "os" "reflect" "strings" "sync" ) var ( startPrefixes = []string{"// Copyright", "// copyright", "// Licensed", "// licensed", "// ELASTICSEARCH CONFIDENTIAL"} endPrefixes = []string{"package ", "// Package ", "// +build ", "// Code generated", "// code generated", "//go:"} errHeaderIsTooShort = errors.New("header is too short") defaulBufSize int bufPool = sync.Pool{ New: func() interface{} { buf := make([]byte, defaulBufSize) return buf }, } ) func init() { // Iterate over the supported licenses to make sure everything fit // without any additional allocation. for _, v := range Headers { var l int for _, v2 := range v { l += len(v2) } if l > defaulBufSize { defaulBufSize = l } } } // ContainsHeader reads the first N lines of a file and checks if the header // matches the one that is expected func ContainsHeader(r io.Reader, headerLines []string) bool { var scanner = bufio.NewScanner(r) var i int buf := bufPool.Get().([]byte) defer bufPool.Put(buf) scanner.Buffer(buf, defaulBufSize) for i = 0; scanner.Scan(); i++ { line := scanner.Bytes() // end of license, break out of the loop if i == len(headerLines) { break } // compare line by line without storing the whole file // in memory if !bytes.Equal(line, []byte(headerLines[i])) { return false } } // file is shorter than license if i < len(headerLines) { return false } return true } // RewriteFileWithHeader reads a file from a path and rewrites it with a header func RewriteFileWithHeader(path string, header []byte) error { if len(header) < 2 { return errHeaderIsTooShort } info, err := os.Stat(path) if err != nil { return err } origin, err := os.ReadFile(path) if err != nil { return err } data := RewriteWithHeader(origin, header) return os.WriteFile(path, data, info.Mode()) } // RewriteWithHeader rewrites the src byte buffers header with the new header. func RewriteWithHeader(src []byte, header []byte) []byte { // Ensures that the header includes two break lines as the last bytes for !reflect.DeepEqual(header[len(header)-2:], []byte("\n\n")) { header = append(header, []byte("\n")...) } var oldHeader = headerBytes(bytes.NewReader(src)) return bytes.Replace(src, oldHeader, header, 1) } // headerBytes detects the header lines of an io.Reader contents and returns // what it considerst to be the header as a slice of bytes. func headerBytes(r io.Reader) []byte { var scanner = bufio.NewScanner(r) var replaceableHeader []byte var continuedHeader bool for scanner.Scan() { var t = scanner.Text() for i := range endPrefixes { if strings.HasPrefix(t, endPrefixes[i]) { return replaceableHeader } } for i := range startPrefixes { if strings.HasPrefix(t, startPrefixes[i]) { continuedHeader = true } } if continuedHeader { replaceableHeader = append(replaceableHeader, []byte(t+"\n")...) } } return replaceableHeader } // containsHeaderLine reads the first N lines of a file and checks if the header // matches the one that is expected func containsHeaderLine(r io.Reader, headerLines []string) bool { var scanner = bufio.NewScanner(r) for scanner.Scan() { for i := range headerLines { if scanner.Text() == headerLines[i] { return true } } } return false }