1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
|
//go:build workflows
package main
import (
"bytes"
"encoding/json"
"fmt"
"os"
"path/filepath"
"sync"
)
// KV represents a string to value pair.
type KV[T any] struct {
K string
V T
}
// A Map represents an ordered set of key-value pairs.
type Map[T any] []KV[T]
// MarshalJSON encodes m as a key-value pair in JSON.
func (m *Map[T]) MarshalJSON() ([]byte, error) {
if m == nil || len(*m) == 0 {
return []byte("{}"), nil
}
buf := bytes.NewBuffer(make([]byte, 0, len(*m)<<5))
buf.WriteByte('{')
last := len(*m) - 1
for i, pair := range *m {
p, err := json.Marshal(pair.K)
if err != nil {
return nil, fmt.Errorf("key %q: %w", pair.K, err)
}
buf.Write(p)
buf.WriteByte(':')
p, err = json.Marshal(pair.V)
if err != nil {
return nil, fmt.Errorf("value of key %q: %w", pair.K, err)
}
buf.Write(p)
if i < last {
buf.WriteByte(',')
}
}
buf.WriteByte('}')
return buf.Bytes(), nil
}
// A Step is a task ran as part of a [Job].
type Step struct {
// A name for the step.
Name string `json:"name,omitempty"`
// Selects an action to run as part of a step in the job.
Uses string `json:"uses,omitempty"`
// A unique identifier for the step.
ID string `json:"id,omitempty"`
// Prevent a step from running unless a condition is met.
If string `json:"if,omitempty"`
// Runs command-line programs that do not exceed 21,000 characters using the
// operating system's shell.
Run string `json:"run,omitempty"`
// Key-value pairs of input parameters defined by the action.
With Map[any] `json:"with,omitempty"`
}
// A Job is an entry in a [Workflow] document.
type Job struct {
// Set a name for the job.
Name string `json:"name,omitempty"`
// Identifies any jobs that must complete successfully before this job will
// run.
Needs []string `json:"needs,omitempty"`
// Define the type of machine to run the job on.
On string `json:"runs-on,omitempty"`
// A sequence of tasks.
Steps []Step `json:"steps,omitempty"`
}
// Workflow describes a workflow document.
type Workflow struct {
// The name of the workflow.
Name string `json:"name,omitempty"`
// The name for workflow runs generated from the workflow.
RunName string `json:"run-name,omitempty"`
// Define which events can cause the workflow to run.
On any `json:"on"`
// Named jobs part of the workflow run.
Jobs Map[Job] `json:"jobs,omitempty"`
}
// workflowsDir is the relative pathname of the workflows directory.
const workflowsDir = "../../.gitea/workflows"
// writeFile writes the action to the .gitea directory.
func (w *Workflow) writeFile(name string) error {
f, err := os.OpenFile(
filepath.Join(workflowsDir, name+".yml"),
os.O_CREATE|os.O_EXCL|os.O_WRONLY,
0444,
)
if err != nil {
return err
}
err = json.NewEncoder(f).Encode(w)
if closeErr := f.Close(); err == nil {
err = closeErr
}
return err
}
// removeOnce synchronises removal of stale workflow files.
var removeOnce sync.Once
// removeWorkflows removes existing workflow files once.
func removeWorkflows() {
removeOnce.Do(func() {
if err := os.RemoveAll(workflowsDir); err != nil {
fmt.Println(err)
os.Exit(1)
}
if err := os.Mkdir(workflowsDir, 0755); err != nil {
fmt.Println(err)
os.Exit(1)
}
})
}
// mustWriteFile is like writeFile, but terminates the program if a non-nil
// error is returned.
func (w *Workflow) mustWriteFile(name string) (_ struct{}) {
removeWorkflows()
if err := w.writeFile(name); err != nil {
fmt.Println(err)
os.Exit(1)
}
return
}
func main() {}
|