Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,14 @@ project {
license = "MPL-2.0"

# (OPTIONAL) Represents the copyright holder used in all statements
# Default: HashiCorp, Inc.
# Default: IBM Corp.
# copyright_holder = ""

# (OPTIONAL) Represents the year that the project initially began
# This is used as the starting year in copyright statements
# If set and different from current year, headers will show: "copyright_year, current_year"
# If set and same as current year, headers will show: "current_year"
# If not set (0), it will be auto-detected from GitHub or use current year only
# Default: <the year the repo was first created>
# copyright_year = 0

Expand Down
4 changes: 2 additions & 2 deletions cmd/headers.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ config, see the "copywrite init" command.`,

// Construct the configuration addLicense needs to properly format headers
licenseData := addlicense.LicenseData{
Year: "", // by default, we don't include a year in copyright statements
Year: conf.FormatCopyrightYears(), // Format year(s) for copyright statements
Holder: conf.Project.CopyrightHolder,
SPDXID: conf.Project.License,
}
Expand Down Expand Up @@ -129,5 +129,5 @@ func init() {

// These flags will get mapped to keys in the the global Config
headersCmd.Flags().StringP("spdx", "s", "", "SPDX-compliant license identifier (e.g., 'MPL-2.0')")
headersCmd.Flags().StringP("copyright-holder", "c", "", "Copyright holder (default \"HashiCorp, Inc.\")")
headersCmd.Flags().StringP("copyright-holder", "c", "", "Copyright holder (default \"IBM Corp.\")")
}
7 changes: 3 additions & 4 deletions cmd/license.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"errors"
"fmt"
"path/filepath"
"strconv"

"github.com/hashicorp/copywrite/github"
"github.com/hashicorp/copywrite/licensecheck"
Expand Down Expand Up @@ -64,10 +63,10 @@ var licenseCmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) {

cmd.Printf("Licensing under the following terms: %s\n", conf.Project.License)
cmd.Printf("Using year of initial copyright: %v\n", conf.Project.CopyrightYear)
cmd.Printf("Using copyright years: %v\n", conf.FormatCopyrightYears())
cmd.Printf("Using copyright holder: %v\n\n", conf.Project.CopyrightHolder)

copyright := "Copyright (c) " + strconv.Itoa(conf.Project.CopyrightYear) + " " + conf.Project.CopyrightHolder
copyright := "Copyright " + conf.FormatCopyrightYears() + " " + conf.Project.CopyrightHolder

licenseFiles, err := licensecheck.FindLicenseFiles(dirPath)
if err != nil {
Expand Down Expand Up @@ -174,5 +173,5 @@ func init() {
// TODO: eventually, the copyrightYear should be dynamically inferred from the repo
licenseCmd.Flags().IntP("year", "y", 0, "Year that the copyright statement should include")
licenseCmd.Flags().StringP("spdx", "s", "", "SPDX License Identifier indicating what the LICENSE file should represent")
licenseCmd.Flags().StringP("copyright-holder", "c", "", "Copyright holder (default \"HashiCorp, Inc.\")")
licenseCmd.Flags().StringP("copyright-holder", "c", "", "Copyright holder (default \"IBM Corp.\")")
}
24 changes: 23 additions & 1 deletion config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"fmt"
"os"
"path/filepath"
"strconv"
"time"

"github.com/knadh/koanf"
"github.com/knadh/koanf/parsers/hcl"
Expand Down Expand Up @@ -88,7 +90,7 @@ func New() (*Config, error) {
// Preload default config values
defaults := map[string]interface{}{
"schema_version": 1,
"project.copyright_holder": "HashiCorp, Inc.",
"project.copyright_holder": "IBM Corp.",
}
err := c.LoadConfMap(defaults)
if err != nil {
Expand Down Expand Up @@ -237,3 +239,23 @@ func (c *Config) Sprint() string {
func (c *Config) GetConfigPath() string {
return c.absCfgPath
}

// FormatCopyrightYears returns a formatted year string for copyright statements.
// If copyrightYear is 0 or equals current year, returns current year only.
// Otherwise returns "copyrightYear, currentYear" format (e.g., "2023, 2025").
func (c *Config) FormatCopyrightYears() string {
currentYear := time.Now().Year()

// If no copyright year is set, use current year only
if c.Project.CopyrightYear == 0 {
return strconv.Itoa(currentYear)
}

// If copyright year equals current year, return single year
if c.Project.CopyrightYear == currentYear {
return strconv.Itoa(currentYear)
}

// Return year range: "startYear, currentYear"
return fmt.Sprintf("%d, %d", c.Project.CopyrightYear, currentYear)
}
69 changes: 58 additions & 11 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@
package config

import (
"fmt"
"path/filepath"
"strconv"
"strings"
"testing"
"time"

"github.com/knadh/koanf"
"github.com/spf13/pflag"
Expand All @@ -26,8 +29,8 @@ func Test_New(t *testing.T) {

// Validate the default value(s)
assert.Equal(t, 1, actualOutput.SchemaVersion, "Schema Version defaults to 1")
assert.Equal(t, "HashiCorp, Inc.", actualOutput.Project.CopyrightHolder, "Copyright Holder defaults to 'HashiCorp, Inc.'")
assert.Equal(t, "project.copyright_holder -> HashiCorp, Inc.\nschema_version -> 1\n", actualOutput.Sprint(), "Koanf object gets updated appropriately with defaults")
assert.Equal(t, "IBM Corp.", actualOutput.Project.CopyrightHolder, "Copyright Holder defaults to 'IBM Corp.'")
assert.Equal(t, "project.copyright_holder -> IBM Corp.\nschema_version -> 1\n", actualOutput.Sprint(), "Koanf object gets updated appropriately with defaults")
})
}

Expand All @@ -48,7 +51,7 @@ func Test_LoadConfMap(t *testing.T) {
globalKoanf: koanf.New(delim),
SchemaVersion: 12,
Project: Project{
CopyrightHolder: "HashiCorp, Inc.",
CopyrightHolder: "IBM Corp.",
CopyrightYear: 9001,
License: "MPL-2.0",
},
Expand All @@ -73,10 +76,11 @@ func Test_LoadConfMap(t *testing.T) {
func Test_LoadCommandFlags(t *testing.T) {
// Map command flags to config keys
mapping := map[string]string{
`schemaVersion`: `schema_version`,
`spdx`: `project.license`,
`year`: `project.copyright_year`,
`ignoredRepos`: `dispatch.ignored_repos`,
`schemaVersion`: `schema_version`,
`spdx`: `project.license`,
`year`: `project.copyright_year`,
`copyrightHolder`: `project.copyright_holder`,
`ignoredRepos`: `dispatch.ignored_repos`,
}

tests := []struct {
Expand All @@ -92,7 +96,7 @@ func Test_LoadCommandFlags(t *testing.T) {
expectedOutput: &Config{
SchemaVersion: 1,
Project: Project{
CopyrightHolder: "HashiCorp, Inc.",
CopyrightHolder: "IBM Corp.",
CopyrightYear: 9001,
License: "MPL-2.0",
},
Expand All @@ -108,7 +112,7 @@ func Test_LoadCommandFlags(t *testing.T) {
expectedOutput: &Config{
SchemaVersion: 12,
Project: Project{
CopyrightHolder: "HashiCorp, Inc.",
CopyrightHolder: "IBM Corp.",
CopyrightYear: 9001,
License: "MPL-2.0",
},
Expand All @@ -124,7 +128,7 @@ func Test_LoadCommandFlags(t *testing.T) {
expectedOutput: &Config{
SchemaVersion: 33,
Project: Project{
CopyrightHolder: "HashiCorp, Inc.",
CopyrightHolder: "IBM Corp.",
CopyrightYear: 9001,
License: "MPL-2.0",
},
Expand All @@ -140,7 +144,7 @@ func Test_LoadCommandFlags(t *testing.T) {
expectedOutput: &Config{
SchemaVersion: 33,
Project: Project{
CopyrightHolder: "HashiCorp, Inc.",
CopyrightHolder: "IBM Corp.",
CopyrightYear: 9001,
License: "MPL-2.0",
},
Expand All @@ -157,6 +161,7 @@ func Test_LoadCommandFlags(t *testing.T) {
flags.Int("schemaVersion", 12, "Config Schema Version")
flags.String("spdx", "MPL-2.0", "SPDX License Identifier")
flags.Int("year", 9001, "Year of copyright")
flags.String("copyrightHolder", "IBM Corp.", "Copyright Holder")
flags.StringArray("ignoredRepos", []string{"foo", "bar"}, "repos to ignore")
err := flags.Parse(tt.args)
assert.Nil(t, err, "If this broke, the test is wrong, not the function under test")
Expand Down Expand Up @@ -367,3 +372,45 @@ func Test_GetConfigPath(t *testing.T) {
abs, _ := filepath.Abs(cfgPath)
assert.Equal(t, abs, actualOutput.GetConfigPath(), "Loaded config should return abs file path")
}

func Test_FormatCopyrightYears(t *testing.T) {
currentYear := time.Now().Year()

tests := []struct {
description string
copyrightYear int
expectedOutput string
}{
{
description: "No copyright year set (0) should return current year only",
copyrightYear: 0,
expectedOutput: strconv.Itoa(currentYear),
},
{
description: "Copyright year equals current year should return single year",
copyrightYear: currentYear,
expectedOutput: strconv.Itoa(currentYear),
},
{
description: "Copyright year before current year should return year range",
copyrightYear: 2023,
expectedOutput: fmt.Sprintf("2023, %d", currentYear),
},
{
description: "Old copyright year should return year range",
copyrightYear: 2018,
expectedOutput: fmt.Sprintf("2018, %d", currentYear),
},
}

for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
c := MustNew()
c.Project.CopyrightYear = tt.copyrightYear

actualOutput := c.FormatCopyrightYears()

assert.Equal(t, tt.expectedOutput, actualOutput, tt.description)
})
}
}
Loading