Skip to content

Commit 9c49743

Browse files
authored
Update copyright format for IBM Corp with year ranges (#179)
- 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
1 parent e840eb9 commit 9c49743

File tree

5 files changed

+91
-19
lines changed

5 files changed

+91
-19
lines changed

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,14 @@ project {
9494
license = "MPL-2.0"
9595
9696
# (OPTIONAL) Represents the copyright holder used in all statements
97-
# Default: HashiCorp, Inc.
97+
# Default: IBM Corp.
9898
# copyright_holder = ""
9999
100100
# (OPTIONAL) Represents the year that the project initially began
101+
# This is used as the starting year in copyright statements
102+
# If set and different from current year, headers will show: "copyright_year, current_year"
103+
# If set and same as current year, headers will show: "current_year"
104+
# If not set (0), it will be auto-detected from GitHub or use current year only
101105
# Default: <the year the repo was first created>
102106
# copyright_year = 0
103107

cmd/headers.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ config, see the "copywrite init" command.`,
9292

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

130130
// These flags will get mapped to keys in the the global Config
131131
headersCmd.Flags().StringP("spdx", "s", "", "SPDX-compliant license identifier (e.g., 'MPL-2.0')")
132-
headersCmd.Flags().StringP("copyright-holder", "c", "", "Copyright holder (default \"HashiCorp, Inc.\")")
132+
headersCmd.Flags().StringP("copyright-holder", "c", "", "Copyright holder (default \"IBM Corp.\")")
133133
}

cmd/license.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"errors"
88
"fmt"
99
"path/filepath"
10-
"strconv"
1110

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

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

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

7271
licenseFiles, err := licensecheck.FindLicenseFiles(dirPath)
7372
if err != nil {
@@ -174,5 +173,5 @@ func init() {
174173
// TODO: eventually, the copyrightYear should be dynamically inferred from the repo
175174
licenseCmd.Flags().IntP("year", "y", 0, "Year that the copyright statement should include")
176175
licenseCmd.Flags().StringP("spdx", "s", "", "SPDX License Identifier indicating what the LICENSE file should represent")
177-
licenseCmd.Flags().StringP("copyright-holder", "c", "", "Copyright holder (default \"HashiCorp, Inc.\")")
176+
licenseCmd.Flags().StringP("copyright-holder", "c", "", "Copyright holder (default \"IBM Corp.\")")
178177
}

config/config.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77
"fmt"
88
"os"
99
"path/filepath"
10+
"strconv"
11+
"time"
1012

1113
"github.com/knadh/koanf"
1214
"github.com/knadh/koanf/parsers/hcl"
@@ -88,7 +90,7 @@ func New() (*Config, error) {
8890
// Preload default config values
8991
defaults := map[string]interface{}{
9092
"schema_version": 1,
91-
"project.copyright_holder": "HashiCorp, Inc.",
93+
"project.copyright_holder": "IBM Corp.",
9294
}
9395
err := c.LoadConfMap(defaults)
9496
if err != nil {
@@ -237,3 +239,23 @@ func (c *Config) Sprint() string {
237239
func (c *Config) GetConfigPath() string {
238240
return c.absCfgPath
239241
}
242+
243+
// FormatCopyrightYears returns a formatted year string for copyright statements.
244+
// If copyrightYear is 0 or equals current year, returns current year only.
245+
// Otherwise returns "copyrightYear, currentYear" format (e.g., "2023, 2025").
246+
func (c *Config) FormatCopyrightYears() string {
247+
currentYear := time.Now().Year()
248+
249+
// If no copyright year is set, use current year only
250+
if c.Project.CopyrightYear == 0 {
251+
return strconv.Itoa(currentYear)
252+
}
253+
254+
// If copyright year equals current year, return single year
255+
if c.Project.CopyrightYear == currentYear {
256+
return strconv.Itoa(currentYear)
257+
}
258+
259+
// Return year range: "startYear, currentYear"
260+
return fmt.Sprintf("%d, %d", c.Project.CopyrightYear, currentYear)
261+
}

config/config_test.go

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@
44
package config
55

66
import (
7+
"fmt"
78
"path/filepath"
9+
"strconv"
810
"strings"
911
"testing"
12+
"time"
1013

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

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

@@ -48,7 +51,7 @@ func Test_LoadConfMap(t *testing.T) {
4851
globalKoanf: koanf.New(delim),
4952
SchemaVersion: 12,
5053
Project: Project{
51-
CopyrightHolder: "HashiCorp, Inc.",
54+
CopyrightHolder: "IBM Corp.",
5255
CopyrightYear: 9001,
5356
License: "MPL-2.0",
5457
},
@@ -73,10 +76,11 @@ func Test_LoadConfMap(t *testing.T) {
7376
func Test_LoadCommandFlags(t *testing.T) {
7477
// Map command flags to config keys
7578
mapping := map[string]string{
76-
`schemaVersion`: `schema_version`,
77-
`spdx`: `project.license`,
78-
`year`: `project.copyright_year`,
79-
`ignoredRepos`: `dispatch.ignored_repos`,
79+
`schemaVersion`: `schema_version`,
80+
`spdx`: `project.license`,
81+
`year`: `project.copyright_year`,
82+
`copyrightHolder`: `project.copyright_holder`,
83+
`ignoredRepos`: `dispatch.ignored_repos`,
8084
}
8185

8286
tests := []struct {
@@ -92,7 +96,7 @@ func Test_LoadCommandFlags(t *testing.T) {
9296
expectedOutput: &Config{
9397
SchemaVersion: 1,
9498
Project: Project{
95-
CopyrightHolder: "HashiCorp, Inc.",
99+
CopyrightHolder: "IBM Corp.",
96100
CopyrightYear: 9001,
97101
License: "MPL-2.0",
98102
},
@@ -108,7 +112,7 @@ func Test_LoadCommandFlags(t *testing.T) {
108112
expectedOutput: &Config{
109113
SchemaVersion: 12,
110114
Project: Project{
111-
CopyrightHolder: "HashiCorp, Inc.",
115+
CopyrightHolder: "IBM Corp.",
112116
CopyrightYear: 9001,
113117
License: "MPL-2.0",
114118
},
@@ -124,7 +128,7 @@ func Test_LoadCommandFlags(t *testing.T) {
124128
expectedOutput: &Config{
125129
SchemaVersion: 33,
126130
Project: Project{
127-
CopyrightHolder: "HashiCorp, Inc.",
131+
CopyrightHolder: "IBM Corp.",
128132
CopyrightYear: 9001,
129133
License: "MPL-2.0",
130134
},
@@ -140,7 +144,7 @@ func Test_LoadCommandFlags(t *testing.T) {
140144
expectedOutput: &Config{
141145
SchemaVersion: 33,
142146
Project: Project{
143-
CopyrightHolder: "HashiCorp, Inc.",
147+
CopyrightHolder: "IBM Corp.",
144148
CopyrightYear: 9001,
145149
License: "MPL-2.0",
146150
},
@@ -157,6 +161,7 @@ func Test_LoadCommandFlags(t *testing.T) {
157161
flags.Int("schemaVersion", 12, "Config Schema Version")
158162
flags.String("spdx", "MPL-2.0", "SPDX License Identifier")
159163
flags.Int("year", 9001, "Year of copyright")
164+
flags.String("copyrightHolder", "IBM Corp.", "Copyright Holder")
160165
flags.StringArray("ignoredRepos", []string{"foo", "bar"}, "repos to ignore")
161166
err := flags.Parse(tt.args)
162167
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) {
367372
abs, _ := filepath.Abs(cfgPath)
368373
assert.Equal(t, abs, actualOutput.GetConfigPath(), "Loaded config should return abs file path")
369374
}
375+
376+
func Test_FormatCopyrightYears(t *testing.T) {
377+
currentYear := time.Now().Year()
378+
379+
tests := []struct {
380+
description string
381+
copyrightYear int
382+
expectedOutput string
383+
}{
384+
{
385+
description: "No copyright year set (0) should return current year only",
386+
copyrightYear: 0,
387+
expectedOutput: strconv.Itoa(currentYear),
388+
},
389+
{
390+
description: "Copyright year equals current year should return single year",
391+
copyrightYear: currentYear,
392+
expectedOutput: strconv.Itoa(currentYear),
393+
},
394+
{
395+
description: "Copyright year before current year should return year range",
396+
copyrightYear: 2023,
397+
expectedOutput: fmt.Sprintf("2023, %d", currentYear),
398+
},
399+
{
400+
description: "Old copyright year should return year range",
401+
copyrightYear: 2018,
402+
expectedOutput: fmt.Sprintf("2018, %d", currentYear),
403+
},
404+
}
405+
406+
for _, tt := range tests {
407+
t.Run(tt.description, func(t *testing.T) {
408+
c := MustNew()
409+
c.Project.CopyrightYear = tt.copyrightYear
410+
411+
actualOutput := c.FormatCopyrightYears()
412+
413+
assert.Equal(t, tt.expectedOutput, actualOutput, tt.description)
414+
})
415+
}
416+
}

0 commit comments

Comments
 (0)