From 3846a8b973c0517d83a3dfaf8bbd97a299437b24 Mon Sep 17 00:00:00 2001 From: creatorHead Date: Tue, 16 Dec 2025 11:47:07 +0530 Subject: [PATCH] Update copyright format for IBM Corp with year ranges - Add FormatCopyrightYears() function to automatically calculate year ranges - Update default copyright holder from HashiCorp, Inc. to IBM Corp. - Enable year inclusion in copyright headers for source files - Update headers and license commands to use year formatting - Add comprehensive tests for year range calculation - Update README documentation for new defaults --- README.md | 6 +++- cmd/headers.go | 4 +-- cmd/license.go | 7 ++--- config/config.go | 24 ++++++++++++++- config/config_test.go | 69 ++++++++++++++++++++++++++++++++++++------- 5 files changed, 91 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index fd0566e..8a08b55 100644 --- a/README.md +++ b/README.md @@ -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: # copyright_year = 0 diff --git a/cmd/headers.go b/cmd/headers.go index 481f244..6902699 100644 --- a/cmd/headers.go +++ b/cmd/headers.go @@ -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, } @@ -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.\")") } diff --git a/cmd/license.go b/cmd/license.go index eff5dac..ebaad57 100644 --- a/cmd/license.go +++ b/cmd/license.go @@ -7,7 +7,6 @@ import ( "errors" "fmt" "path/filepath" - "strconv" "github.com/hashicorp/copywrite/github" "github.com/hashicorp/copywrite/licensecheck" @@ -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 { @@ -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.\")") } diff --git a/config/config.go b/config/config.go index 16786ff..320676d 100644 --- a/config/config.go +++ b/config/config.go @@ -7,6 +7,8 @@ import ( "fmt" "os" "path/filepath" + "strconv" + "time" "github.com/knadh/koanf" "github.com/knadh/koanf/parsers/hcl" @@ -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 { @@ -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) +} diff --git a/config/config_test.go b/config/config_test.go index 442ea0d..78b754b 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -4,9 +4,12 @@ package config import ( + "fmt" "path/filepath" + "strconv" "strings" "testing" + "time" "github.com/knadh/koanf" "github.com/spf13/pflag" @@ -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") }) } @@ -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", }, @@ -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 { @@ -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", }, @@ -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", }, @@ -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", }, @@ -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", }, @@ -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") @@ -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) + }) + } +}