Skip to content

Commit 344d3b1

Browse files
Copilotpelikhan
andauthored
refactor parser and schedule helpers to reduce semantic drift
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
1 parent 76e94e3 commit 344d3b1

7 files changed

Lines changed: 111 additions & 59 deletions

pkg/parser/github.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"strings"
1111

1212
"github.com/github/gh-aw/pkg/constants"
13-
"github.com/github/gh-aw/pkg/envutil"
1413
"github.com/github/gh-aw/pkg/logger"
1514
"github.com/github/gh-aw/pkg/stringutil"
1615
)
@@ -63,10 +62,7 @@ func GetGitHubToken() (string, error) {
6362
githubLog.Print("Getting GitHub token")
6463

6564
// First try environment variable
66-
if token := envutil.GetStringFromEnv("GITHUB_TOKEN", "", githubLog); token != "" {
67-
return token, nil
68-
}
69-
if token := envutil.GetStringFromEnv("GH_TOKEN", "", githubLog); token != "" {
65+
if token := githubTokenFromEnv(githubLog); token != "" {
7066
return token, nil
7167
}
7268

pkg/parser/github_token_env.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package parser
2+
3+
import (
4+
"github.com/github/gh-aw/pkg/envutil"
5+
"github.com/github/gh-aw/pkg/logger"
6+
)
7+
8+
func githubTokenFromEnv(log *logger.Logger) string {
9+
if token := envutil.GetStringFromEnv("GITHUB_TOKEN", "", log); token != "" {
10+
return token
11+
}
12+
return envutil.GetStringFromEnv("GH_TOKEN", "", log)
13+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//go:build !integration
2+
3+
package parser
4+
5+
import (
6+
"testing"
7+
8+
"github.com/github/gh-aw/pkg/logger"
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
func TestGithubTokenFromEnv(t *testing.T) {
13+
t.Run("prefers GITHUB_TOKEN over GH_TOKEN", func(t *testing.T) {
14+
t.Setenv("GITHUB_TOKEN", "github-token")
15+
t.Setenv("GH_TOKEN", "gh-token")
16+
17+
assert.Equal(t, "github-token", githubTokenFromEnv(logger.New("parser:test")))
18+
})
19+
20+
t.Run("falls back to GH_TOKEN when GITHUB_TOKEN is empty", func(t *testing.T) {
21+
t.Setenv("GITHUB_TOKEN", "")
22+
t.Setenv("GH_TOKEN", "gh-token")
23+
24+
assert.Equal(t, "gh-token", githubTokenFromEnv(nil))
25+
})
26+
27+
t.Run("returns empty when neither token is set", func(t *testing.T) {
28+
t.Setenv("GITHUB_TOKEN", "")
29+
t.Setenv("GH_TOKEN", "")
30+
31+
assert.Empty(t, githubTokenFromEnv(nil))
32+
})
33+
}

pkg/parser/github_wasm.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,12 @@ package parser
44

55
import (
66
"errors"
7-
8-
"github.com/github/gh-aw/pkg/envutil"
97
)
108

119
func GetGitHubToken() (string, error) {
1210
// Wasm callers do not use the package logger, so pass nil to suppress debug
1311
// logging while still centralizing environment variable access.
14-
if token := envutil.GetStringFromEnv("GITHUB_TOKEN", "", nil); token != "" {
15-
return token, nil
16-
}
17-
if token := envutil.GetStringFromEnv("GH_TOKEN", "", nil); token != "" {
12+
if token := githubTokenFromEnv(nil); token != "" {
1813
return token, nil
1914
}
2015
return "", errors.New("GitHub token not available in Wasm (set GITHUB_TOKEN or GH_TOKEN environment variable)")

pkg/parser/schedule_cron_detection.go

Lines changed: 29 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,28 @@ var cronDetectionLog = logger.New("parser:schedule_cron_detection")
1616
// cronFieldPattern matches valid cron field syntax (pre-compiled for performance)
1717
var cronFieldPattern = regexp.MustCompile(`^[\d\*\-/,]+$`)
1818

19+
func cronFields(cron string) ([]string, bool) {
20+
fields := strings.Fields(cron)
21+
return fields, len(fields) == 5
22+
}
23+
24+
func isNumericCronField(field string) bool {
25+
if field == "" {
26+
return false
27+
}
28+
for _, ch := range field {
29+
if ch < '0' || ch > '9' {
30+
return false
31+
}
32+
}
33+
return true
34+
}
35+
1936
// IsDailyCron checks if a cron expression represents a daily schedule at a fixed time
2037
// (e.g., "0 0 * * *", "30 14 * * *", etc.)
2138
func IsDailyCron(cron string) bool {
22-
fields := strings.Fields(cron)
23-
if len(fields) != 5 {
39+
fields, ok := cronFields(cron)
40+
if !ok {
2441
return false
2542
}
2643
// Daily pattern: minute hour * * *
@@ -32,16 +49,8 @@ func IsDailyCron(cron string) bool {
3249
minute := fields[0]
3350
hour := fields[1]
3451

35-
// Minute and hour should be digits only (no *, /, -, ,)
36-
for _, ch := range minute {
37-
if ch < '0' || ch > '9' {
38-
return false
39-
}
40-
}
41-
for _, ch := range hour {
42-
if ch < '0' || ch > '9' {
43-
return false
44-
}
52+
if !isNumericCronField(minute) || !isNumericCronField(hour) {
53+
return false
4554
}
4655

4756
result := fields[2] == "*" && fields[3] == "*" && fields[4] == "*"
@@ -54,8 +63,8 @@ func IsDailyCron(cron string) bool {
5463
// IsHourlyCron checks if a cron expression represents an hourly interval with a fixed minute
5564
// (e.g., "0 */1 * * *", "30 */2 * * *", etc.)
5665
func IsHourlyCron(cron string) bool {
57-
fields := strings.Fields(cron)
58-
if len(fields) != 5 {
66+
fields, ok := cronFields(cron)
67+
if !ok {
5968
return false
6069
}
6170
// Hourly pattern: minute */N * * * or minute *N * * *
@@ -65,11 +74,8 @@ func IsHourlyCron(cron string) bool {
6574
minute := fields[0]
6675
hour := fields[1]
6776

68-
// Minute should be digits only (no *, /, -, ,)
69-
for _, ch := range minute {
70-
if ch < '0' || ch > '9' {
71-
return false
72-
}
77+
if !isNumericCronField(minute) {
78+
return false
7379
}
7480

7581
// Hour should be an interval pattern like */N
@@ -88,8 +94,8 @@ func IsHourlyCron(cron string) bool {
8894
// IsWeeklyCron checks if a cron expression represents a weekly schedule at a fixed time
8995
// (e.g., "0 0 * * 1", "30 14 * * 5", etc.)
9096
func IsWeeklyCron(cron string) bool {
91-
fields := strings.Fields(cron)
92-
if len(fields) != 5 {
97+
fields, ok := cronFields(cron)
98+
if !ok {
9399
return false
94100
}
95101
// Weekly pattern: minute hour * * DOW
@@ -101,16 +107,8 @@ func IsWeeklyCron(cron string) bool {
101107
minute := fields[0]
102108
hour := fields[1]
103109

104-
// Minute and hour should be digits only (no *, /, -, ,)
105-
for _, ch := range minute {
106-
if ch < '0' || ch > '9' {
107-
return false
108-
}
109-
}
110-
for _, ch := range hour {
111-
if ch < '0' || ch > '9' {
112-
return false
113-
}
110+
if !isNumericCronField(minute) || !isNumericCronField(hour) {
111+
return false
114112
}
115113

116114
// Check day-of-month and month are wildcards

pkg/parser/schedule_cron_detection_test.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ func TestIsCronExpression(t *testing.T) {
2828
{"invalid expression", "invalid cron expression", false},
2929
{"empty string", "", false},
3030
}
31-
3231
for _, tt := range tests {
3332
t.Run(tt.name, func(t *testing.T) {
3433
result := IsCronExpression(tt.input)
@@ -37,6 +36,28 @@ func TestIsCronExpression(t *testing.T) {
3736
}
3837
}
3938

39+
func TestIsNumericCronField(t *testing.T) {
40+
tests := []struct {
41+
name string
42+
input string
43+
expected bool
44+
}{
45+
{"single digit", "5", true},
46+
{"multiple digits", "15", true},
47+
{"empty", "", false},
48+
{"wildcard", "*", false},
49+
{"interval", "*/5", false},
50+
{"range", "1-5", false},
51+
{"list", "1,2", false},
52+
}
53+
54+
for _, tt := range tests {
55+
t.Run(tt.name, func(t *testing.T) {
56+
assert.Equal(t, tt.expected, isNumericCronField(tt.input))
57+
})
58+
}
59+
}
60+
4061
func TestIsDailyCron(t *testing.T) {
4162
tests := []struct {
4263
name string

pkg/workflow/schedule_preprocessing.go

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -472,12 +472,7 @@ func (c *Compiler) addDailyCronWarning(cronExpr string) {
472472
hour, minute,
473473
)
474474

475-
// This warning is added to the warning count
476-
// It will be collected and displayed by the compilation process
477-
c.IncrementWarningCount()
478-
479-
// Store the warning for later display
480-
c.addScheduleWarning(warningMsg)
475+
c.emitScheduleWarning(warningMsg)
481476
}
482477
}
483478

@@ -499,11 +494,7 @@ func (c *Compiler) addHourlyCronWarning(cronExpr string) {
499494
minute, interval,
500495
)
501496

502-
// This warning is added to the warning count
503-
c.IncrementWarningCount()
504-
505-
// Store the warning for later display
506-
c.addScheduleWarning(warningMsg)
497+
c.emitScheduleWarning(warningMsg)
507498
}
508499
}
509500

@@ -538,14 +529,19 @@ func (c *Compiler) addWeeklyCronWarning(cronExpr string) {
538529
weekdayName, hour, minute, strings.ToLower(weekdayName),
539530
)
540531

541-
// This warning is added to the warning count
542-
c.IncrementWarningCount()
543-
544-
// Store the warning for later display
545-
c.addScheduleWarning(warningMsg)
532+
c.emitScheduleWarning(warningMsg)
546533
}
547534
}
548535

536+
func (c *Compiler) emitScheduleWarning(warning string) {
537+
// This warning is added to the warning count.
538+
// It will be collected and displayed by the compilation process.
539+
c.IncrementWarningCount()
540+
541+
// Store the warning for later display.
542+
c.addScheduleWarning(warning)
543+
}
544+
549545
// addScheduleWarning adds a warning to the compiler's schedule warnings list
550546
func (c *Compiler) addScheduleWarning(warning string) {
551547
if c.scheduleWarnings == nil {

0 commit comments

Comments
 (0)