@@ -14,10 +14,10 @@ type Project struct {
14
14
Name string `json:"name"`
15
15
Description string `json:"description"`
16
16
Contributors int `json:"contributors"`
17
- HomeUrl string `json:"home_url"`
18
- RepoUrl string `json:"repo_url,omitempty"`
17
+ HomeURL string `json:"home_url"`
18
+ RepoURL string `json:"repo_url,omitempty"`
19
19
LicenseType string `json:"license_type,omitempty"`
20
- LicenseUrl string `json:"license_url,omitempty"`
20
+ LicenseURL string `json:"license_url,omitempty"`
21
21
IsEvent bool `json:"is_event"`
22
22
IsTeam bool `json:"is_team"`
23
23
}
@@ -26,7 +26,7 @@ type Applicant struct {
26
26
Name string `json:"name"`
27
27
Email string `json:"email"`
28
28
Role string `json:"role"`
29
- Id int64 `json:"id"`
29
+ ID int64 `json:"id"`
30
30
}
31
31
32
32
type Application struct {
@@ -43,7 +43,7 @@ type Application struct {
43
43
CreatedAt time.Time `json:"created_at"`
44
44
}
45
45
46
- func (a * Application ) Parse (issue github.Issue ) {
46
+ func (a * Application ) Parse (issue * github.Issue ) {
47
47
a .validator = Validator {}
48
48
49
49
if strings .Contains (* issue .Title , "[project name]" ) {
@@ -63,32 +63,32 @@ func (a *Application) Parse(issue github.Issue) {
63
63
64
64
a .CreatedAt = issue .CreatedAt .Time
65
65
a .IssueNumber = * issue .Number
66
- a .Account = a .stringSection ("Account URL" , IsPresent , ParseAccountUrl )
67
- a .boolSection ("Non-commercial confirmation" , IsPresent , ParseCheckbox , IsChecked )
66
+ a .Account = a .stringSection ("Account URL" , true , ParseAccountURL )
67
+ a .boolSection ("Non-commercial confirmation" , true , ParseCheckbox , IsChecked )
68
68
69
- a .Project .IsTeam = a .boolSection ("Team application" , ParseCheckbox )
70
- a .Project .IsEvent = a .boolSection ("Event application" , ParseCheckbox )
69
+ a .Project .IsTeam = a .boolSection ("Team application" , false , ParseCheckbox )
70
+ a .Project .IsEvent = a .boolSection ("Event application" , false , ParseCheckbox )
71
71
72
72
isProject := ! a .Project .IsTeam && ! a .Project .IsEvent
73
73
74
- a .Project .Name = a .stringSection ("Project name" , IsPresent , ParsePlainString )
75
- a .Project .Description = a .stringSection ("Short description" , IsPresent , ParsePlainString )
76
- a .Project .Contributors = a .intSection ("Number of team members/core contributors" , IsPresent , ParsePlainString )
77
- a .Project .HomeUrl = a .stringSection ("Homepage URL" , IsPresent , IsUrl )
78
- a .Project .RepoUrl = a .stringSection ("Repository URL" , IsUrl )
79
- a .Project .LicenseType = a .stringSection ("License type" , When ( isProject , IsPresent ) , ParsePlainString )
80
- a .Project .LicenseUrl = a .stringSection ("License URL" , When ( isProject , IsPresent ), IsUrl )
81
- a .boolSection ("Age confirmation" , When ( isProject , IsPresent ) , ParseCheckbox , When (isProject , IsChecked ))
74
+ a .Project .Name = a .stringSection ("Project name" , true , ParsePlainString )
75
+ a .Project .Description = a .stringSection ("Short description" , true , ParsePlainString )
76
+ a .Project .Contributors = a .intSection ("Number of team members/core contributors" , true , ParsePlainString )
77
+ a .Project .HomeURL = a .stringSection ("Homepage URL" , true , IsURL )
78
+ a .Project .RepoURL = a .stringSection ("Repository URL" , false , IsURL )
79
+ a .Project .LicenseType = a .stringSection ("License type" , isProject , ParsePlainString )
80
+ a .Project .LicenseURL = a .stringSection ("License URL" , isProject , IsURL )
81
+ a .boolSection ("Age confirmation" , isProject , ParseCheckbox , When (isProject , IsChecked ))
82
82
83
- a .Applicant .Name = a .stringSection ("Name" , IsPresent , ParsePlainString )
84
- a .Applicant .Email = a .stringSection ("Email" , IsPresent , IsEmail )
85
- a .Applicant .Role = a .stringSection ("Project role" , IsPresent )
86
- a .Applicant .Id = * issue .User .ID
83
+ a .Applicant .Name = a .stringSection ("Name" , true , ParsePlainString )
84
+ a .Applicant .Email = a .stringSection ("Email" , true , IsEmail )
85
+ a .Applicant .Role = a .stringSection ("Project role" , true )
86
+ a .Applicant .ID = * issue .User .ID
87
87
88
- a .stringSection ("Profile or website" , IsUrl )
89
- a .stringSection ("Additional comments" )
88
+ a .stringSection ("Profile or website" , false , IsURL )
89
+ a .stringSection ("Additional comments" , false )
90
90
91
- a .CanContact = a .boolSection ("Can we contact you?" , ParseCheckbox )
91
+ a .CanContact = a .boolSection ("Can we contact you?" , false , ParseCheckbox )
92
92
93
93
if isTestingIssue () {
94
94
debugMessage ("Application data:" , a .GetData ())
@@ -112,44 +112,73 @@ func (a *Application) GetData() string {
112
112
return string (data )
113
113
}
114
114
115
+ // Take the Markdown-format body of an issue and break it down by section header
116
+ // and the content directly below it. We can reasonably expect the correct format
117
+ // here if someone files an issue using the application template, but it will also
118
+ // gracefully handle when this format is not present. Note that this will only
119
+ // create an entry when there is content to be added; in other words, a section
120
+ // header without any content will not be added.
115
121
func (a * Application ) extractSections (body string ) map [string ]string {
116
122
sections := make (map [string ]string )
117
123
118
124
lines := strings .Split (body , "\n " )
119
125
var currentHeader string
120
126
contentBuilder := strings.Builder {}
121
127
128
+ // For each line of the body content, it can either be a section's
129
+ // header or the content associated with that section's header.
122
130
for _ , line := range lines {
123
131
trimmedLine := strings .TrimSpace (line )
124
- if strings .HasPrefix (trimmedLine , "### " ) {
125
- if currentHeader != "" {
126
- sections [currentHeader ] = strings .TrimSpace (contentBuilder .String ())
127
- contentBuilder .Reset ()
132
+
133
+ // If we're in a section and the content doesn't start with
134
+ // a header marker, append it to our content builder
135
+ if ! strings .HasPrefix (trimmedLine , "### " ) {
136
+ if currentHeader == "" {
137
+ continue
128
138
}
129
- currentHeader = strings .TrimSpace (trimmedLine [4 :])
130
- } else if currentHeader != "" {
139
+
131
140
contentBuilder .WriteString (line + "\n " )
141
+ continue
142
+ }
143
+
144
+ // The content has a header marker, so create a new
145
+ // section entry and prepare the content builder
146
+ if currentHeader != "" && contentBuilder .Len () > 0 {
147
+ sections [currentHeader ] = strings .TrimSpace (contentBuilder .String ())
148
+ contentBuilder .Reset ()
132
149
}
150
+
151
+ currentHeader = strings .TrimSpace (trimmedLine [4 :])
133
152
}
134
153
135
- if currentHeader != "" {
154
+ // Once the loop has completed check if there's a
155
+ // trailing section needing to be closed
156
+ if currentHeader != "" && contentBuilder .Len () > 0 {
136
157
sections [currentHeader ] = strings .TrimSpace (contentBuilder .String ())
137
158
}
138
159
139
160
return sections
140
161
}
141
162
142
- func (a * Application ) stringSection (sectionName string , callbacks ... ValidatorCallback ) string {
163
+ func (a * Application ) stringSection (sectionName string , required bool , callbacks ... ValidatorCallback ) string {
143
164
value , exists := a .sections [sectionName ]
144
-
145
- if ! exists {
146
- a .validator .AddError (sectionName , value , "was not completed for application" )
165
+ _ , value , _ = ParseInput (value )
166
+
167
+ // If the section is required, apply the presence validator if the entry
168
+ // exists, early fail validation if it doesn't exist. If the section is
169
+ // not required and there is no content to work with, don't try to run
170
+ // additional validations.
171
+ if required {
172
+ if exists {
173
+ callbacks = append ([]ValidatorCallback {IsPresent }, callbacks ... )
174
+ } else {
175
+ a .validator .AddError (sectionName , value , "was not completed for application" )
176
+ return value
177
+ }
178
+ } else if ! exists || value == "" {
147
179
return value
148
180
}
149
181
150
- // everything gets passed through ParseInput first
151
- callbacks = append ([]ValidatorCallback {ParseInput }, callbacks ... )
152
-
153
182
for _ , callback := range callbacks {
154
183
pass , newValue , message := callback (value )
155
184
value = newValue
@@ -163,11 +192,11 @@ func (a *Application) stringSection(sectionName string, callbacks ...ValidatorCa
163
192
return value
164
193
}
165
194
166
- func (a * Application ) intSection (sectionName string , callbacks ... ValidatorCallback ) int {
167
- value := a .stringSection (sectionName , callbacks ... )
195
+ func (a * Application ) intSection (sectionName string , required bool , callbacks ... ValidatorCallback ) int {
196
+ value := a .stringSection (sectionName , required , callbacks ... )
168
197
169
- // don 't bother proceeding if there's already an error parsing the string
170
- if a .validator .HasError (sectionName ) {
198
+ // Don 't bother proceeding if there's already an error parsing the string
199
+ if a .validator .HasError (sectionName ) || value == "" {
171
200
return 0
172
201
}
173
202
@@ -180,11 +209,11 @@ func (a *Application) intSection(sectionName string, callbacks ...ValidatorCallb
180
209
return number
181
210
}
182
211
183
- func (a * Application ) boolSection (sectionName string , callbacks ... ValidatorCallback ) bool {
184
- value := a .stringSection (sectionName , callbacks ... )
212
+ func (a * Application ) boolSection (sectionName string , required bool , callbacks ... ValidatorCallback ) bool {
213
+ value := a .stringSection (sectionName , required , callbacks ... )
185
214
186
- // don 't bother proceeding if there's already an error parsing the string
187
- if a .validator .HasError (sectionName ) {
215
+ // Don 't bother proceeding if there's already an error parsing the string
216
+ if a .validator .HasError (sectionName ) || value == "" {
188
217
return false
189
218
}
190
219
0 commit comments