//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() {}