Skip to content

Commit b51b1f1

Browse files
authored
Move all templating-related helpers from run_steps.go into separate file (#421)
* Move templating helpers to own file * Move another helper over * Introduce tiny helper * Move code around * Use renderTemplate helper * Move tests too
1 parent e1d212f commit b51b1f1

File tree

3 files changed

+177
-165
lines changed

3 files changed

+177
-165
lines changed

internal/campaigns/run_steps.go

Lines changed: 5 additions & 165 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99
"os"
1010
"os/exec"
1111
"strings"
12-
"text/template"
1312
"time"
1413

1514
"github.com/hashicorp/go-multierror"
@@ -84,17 +83,14 @@ func runSteps(ctx context.Context, rf RepoFetcher, wc WorkspaceCreator, repo *gr
8483
}
8584
defer os.Remove(runScriptFile.Name())
8685

87-
// Parse step.Run as a template...
88-
tmpl, err := parseAsTemplate("step-run", step.Run, &stepContext)
89-
if err != nil {
86+
// Parse step.Run as a template and render it into a buffer and the
87+
// temp file we just created.
88+
var runScript bytes.Buffer
89+
out := io.MultiWriter(&runScript, runScriptFile)
90+
if err := renderTemplate("step-run", step.Run, out, &stepContext); err != nil {
9091
return nil, errors.Wrap(err, "parsing step run")
9192
}
9293

93-
// ... and render it into a buffer and the temp file we just created.
94-
var runScript bytes.Buffer
95-
if err := tmpl.Execute(io.MultiWriter(&runScript, runScriptFile), stepContext); err != nil {
96-
return nil, errors.Wrap(err, "executing template")
97-
}
9894
if err := runScriptFile.Close(); err != nil {
9995
return nil, errors.Wrap(err, "closing temporary file")
10096
}
@@ -333,159 +329,3 @@ func (e stepFailedErr) SingleLineError() string {
333329

334330
return strings.Split(out, "\n")[0]
335331
}
336-
337-
func parseAsTemplate(name, input string, stepCtx *StepContext) (*template.Template, error) {
338-
return template.New(name).Delims("${{", "}}").Funcs(stepCtx.ToFuncMap()).Parse(input)
339-
}
340-
341-
func renderMap(m map[string]string, stepCtx *StepContext) (map[string]string, error) {
342-
rendered := make(map[string]string, len(m))
343-
344-
for k, v := range rendered {
345-
var out bytes.Buffer
346-
347-
tmpl, err := parseAsTemplate(k, v, stepCtx)
348-
if err != nil {
349-
return rendered, err
350-
}
351-
352-
if err := tmpl.Execute(&out, stepCtx); err != nil {
353-
return rendered, err
354-
}
355-
356-
rendered[k] = out.String()
357-
}
358-
359-
return rendered, nil
360-
}
361-
362-
// StepContext represents the contextual information available when executing a
363-
// step that's defined in a campaign spec.
364-
type StepContext struct {
365-
PreviousStep StepResult
366-
Repository graphql.Repository
367-
}
368-
369-
// ToFuncMap returns a template.FuncMap to access fields on the StepContext in a
370-
// text/template.
371-
func (stepCtx *StepContext) ToFuncMap() template.FuncMap {
372-
return template.FuncMap{
373-
"join": func(list []string, sep string) string {
374-
return strings.Join(list, sep)
375-
},
376-
"split": func(s string, sep string) []string {
377-
return strings.Split(s, sep)
378-
},
379-
"previous_step": func() map[string]interface{} {
380-
result := map[string]interface{}{
381-
"modified_files": stepCtx.PreviousStep.ModifiedFiles(),
382-
"added_files": stepCtx.PreviousStep.AddedFiles(),
383-
"deleted_files": stepCtx.PreviousStep.DeletedFiles(),
384-
"renamed_files": stepCtx.PreviousStep.RenamedFiles(),
385-
}
386-
387-
if stepCtx.PreviousStep.Stdout != nil {
388-
result["stdout"] = stepCtx.PreviousStep.Stdout.String()
389-
} else {
390-
result["stdout"] = ""
391-
}
392-
393-
if stepCtx.PreviousStep.Stderr != nil {
394-
result["stderr"] = stepCtx.PreviousStep.Stderr.String()
395-
} else {
396-
result["stderr"] = ""
397-
}
398-
399-
return result
400-
},
401-
"repository": func() map[string]interface{} {
402-
return map[string]interface{}{
403-
"search_result_paths": stepCtx.Repository.SearchResultPaths(),
404-
"name": stepCtx.Repository.Name,
405-
}
406-
},
407-
}
408-
}
409-
410-
// StepResult represents the result of a previously executed step.
411-
type StepResult struct {
412-
// files are the changes made to files by the step.
413-
files *StepChanges
414-
415-
// Stdout is the output produced by the step on standard out.
416-
Stdout *bytes.Buffer
417-
// Stderr is the output produced by the step on standard error.
418-
Stderr *bytes.Buffer
419-
}
420-
421-
// StepChanges are the changes made to files by a previous step in a repository.
422-
type StepChanges struct {
423-
Modified []string
424-
Added []string
425-
Deleted []string
426-
Renamed []string
427-
}
428-
429-
// ModifiedFiles returns the files modified by a step.
430-
func (r StepResult) ModifiedFiles() []string {
431-
if r.files != nil {
432-
return r.files.Modified
433-
}
434-
return []string{}
435-
}
436-
437-
// AddedFiles returns the files added by a step.
438-
func (r StepResult) AddedFiles() []string {
439-
if r.files != nil {
440-
return r.files.Added
441-
}
442-
return []string{}
443-
}
444-
445-
// DeletedFiles returns the files deleted by a step.
446-
func (r StepResult) DeletedFiles() []string {
447-
if r.files != nil {
448-
return r.files.Deleted
449-
}
450-
return []string{}
451-
}
452-
453-
// RenamedFiles returns the new name of files that have been renamed by a step.
454-
func (r StepResult) RenamedFiles() []string {
455-
if r.files != nil {
456-
return r.files.Renamed
457-
}
458-
return []string{}
459-
}
460-
461-
func parseGitStatus(out []byte) (StepChanges, error) {
462-
result := StepChanges{}
463-
464-
stripped := strings.TrimSpace(string(out))
465-
if len(stripped) == 0 {
466-
return result, nil
467-
}
468-
469-
for _, line := range strings.Split(stripped, "\n") {
470-
if len(line) < 4 {
471-
return result, fmt.Errorf("git status line has unrecognized format: %q", line)
472-
}
473-
474-
file := line[3:]
475-
476-
switch line[0] {
477-
case 'M':
478-
result.Modified = append(result.Modified, file)
479-
case 'A':
480-
result.Added = append(result.Added, file)
481-
case 'D':
482-
result.Deleted = append(result.Deleted, file)
483-
case 'R':
484-
files := strings.Split(file, " -> ")
485-
newFile := files[len(files)-1]
486-
result.Renamed = append(result.Renamed, newFile)
487-
}
488-
}
489-
490-
return result, nil
491-
}

internal/campaigns/templating.go

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
package campaigns
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"io"
7+
"strings"
8+
"text/template"
9+
10+
"github.com/pkg/errors"
11+
"github.com/sourcegraph/src-cli/internal/campaigns/graphql"
12+
)
13+
14+
func renderTemplate(name, tmpl string, out io.Writer, stepCtx *StepContext) error {
15+
t, err := parseAsTemplate(name, tmpl, stepCtx)
16+
if err != nil {
17+
return errors.Wrap(err, "parsing step run")
18+
}
19+
20+
return t.Execute(out, stepCtx)
21+
}
22+
23+
func parseAsTemplate(name, input string, stepCtx *StepContext) (*template.Template, error) {
24+
return template.New(name).Delims("${{", "}}").Funcs(stepCtx.ToFuncMap()).Parse(input)
25+
}
26+
27+
func renderMap(m map[string]string, stepCtx *StepContext) (map[string]string, error) {
28+
rendered := make(map[string]string, len(m))
29+
30+
for k, v := range rendered {
31+
var out bytes.Buffer
32+
33+
if err := renderTemplate(k, v, &out, stepCtx); err != nil {
34+
return rendered, err
35+
}
36+
37+
rendered[k] = out.String()
38+
}
39+
40+
return rendered, nil
41+
}
42+
43+
// StepContext represents the contextual information available when executing a
44+
// step that's defined in a campaign spec.
45+
type StepContext struct {
46+
PreviousStep StepResult
47+
Repository graphql.Repository
48+
}
49+
50+
// ToFuncMap returns a template.FuncMap to access fields on the StepContext in a
51+
// text/template.
52+
func (stepCtx *StepContext) ToFuncMap() template.FuncMap {
53+
return template.FuncMap{
54+
"join": func(list []string, sep string) string {
55+
return strings.Join(list, sep)
56+
},
57+
"split": func(s string, sep string) []string {
58+
return strings.Split(s, sep)
59+
},
60+
"previous_step": func() map[string]interface{} {
61+
result := map[string]interface{}{
62+
"modified_files": stepCtx.PreviousStep.ModifiedFiles(),
63+
"added_files": stepCtx.PreviousStep.AddedFiles(),
64+
"deleted_files": stepCtx.PreviousStep.DeletedFiles(),
65+
"renamed_files": stepCtx.PreviousStep.RenamedFiles(),
66+
}
67+
68+
if stepCtx.PreviousStep.Stdout != nil {
69+
result["stdout"] = stepCtx.PreviousStep.Stdout.String()
70+
} else {
71+
result["stdout"] = ""
72+
}
73+
74+
if stepCtx.PreviousStep.Stderr != nil {
75+
result["stderr"] = stepCtx.PreviousStep.Stderr.String()
76+
} else {
77+
result["stderr"] = ""
78+
}
79+
80+
return result
81+
},
82+
"repository": func() map[string]interface{} {
83+
return map[string]interface{}{
84+
"search_result_paths": stepCtx.Repository.SearchResultPaths(),
85+
"name": stepCtx.Repository.Name,
86+
}
87+
},
88+
}
89+
}
90+
91+
// StepResult represents the result of a previously executed step.
92+
type StepResult struct {
93+
// files are the changes made to files by the step.
94+
files *StepChanges
95+
96+
// Stdout is the output produced by the step on standard out.
97+
Stdout *bytes.Buffer
98+
// Stderr is the output produced by the step on standard error.
99+
Stderr *bytes.Buffer
100+
}
101+
102+
// StepChanges are the changes made to files by a previous step in a repository.
103+
type StepChanges struct {
104+
Modified []string
105+
Added []string
106+
Deleted []string
107+
Renamed []string
108+
}
109+
110+
// ModifiedFiles returns the files modified by a step.
111+
func (r StepResult) ModifiedFiles() []string {
112+
if r.files != nil {
113+
return r.files.Modified
114+
}
115+
return []string{}
116+
}
117+
118+
// AddedFiles returns the files added by a step.
119+
func (r StepResult) AddedFiles() []string {
120+
if r.files != nil {
121+
return r.files.Added
122+
}
123+
return []string{}
124+
}
125+
126+
// DeletedFiles returns the files deleted by a step.
127+
func (r StepResult) DeletedFiles() []string {
128+
if r.files != nil {
129+
return r.files.Deleted
130+
}
131+
return []string{}
132+
}
133+
134+
// RenamedFiles returns the new name of files that have been renamed by a step.
135+
func (r StepResult) RenamedFiles() []string {
136+
if r.files != nil {
137+
return r.files.Renamed
138+
}
139+
return []string{}
140+
}
141+
142+
func parseGitStatus(out []byte) (StepChanges, error) {
143+
result := StepChanges{}
144+
145+
stripped := strings.TrimSpace(string(out))
146+
if len(stripped) == 0 {
147+
return result, nil
148+
}
149+
150+
for _, line := range strings.Split(stripped, "\n") {
151+
if len(line) < 4 {
152+
return result, fmt.Errorf("git status line has unrecognized format: %q", line)
153+
}
154+
155+
file := line[3:]
156+
157+
switch line[0] {
158+
case 'M':
159+
result.Modified = append(result.Modified, file)
160+
case 'A':
161+
result.Added = append(result.Added, file)
162+
case 'D':
163+
result.Deleted = append(result.Deleted, file)
164+
case 'R':
165+
files := strings.Split(file, " -> ")
166+
newFile := files[len(files)-1]
167+
result.Renamed = append(result.Renamed, newFile)
168+
}
169+
}
170+
171+
return result, nil
172+
}
File renamed without changes.

0 commit comments

Comments
 (0)