forgejo/modules/issue/template/unmarshal.go
Jason Song 84447df4d3
Support Issue forms and PR forms (#20987)
* feat: extend issue template for yaml

* feat: support yaml template

* feat: render form to markdown

* feat: support yaml template for pr

* chore: rename to Fields

* feat: template unmarshal

* feat: split template

* feat: render to markdown

* feat: use full name as template file name

* chore: remove useless file

* feat: use dropdown of fomantic ui

* feat: update input style

* docs: more comments

* fix: render text without render

* chore: fix lint error

* fix: support use description as about in markdown

* fix: add field class in form

* chore: generate swagger

* feat: validate template

* feat: support is_nummber and regex

* test: fix broken unit tests

* fix: ignore empty body of md template

* fix: make multiple easymde editors work in one page

* feat: better UI

* fix: js error in pr form

* chore: generate swagger

* feat: support regex validation

* chore: generate swagger

* fix: refresh each markdown editor

* chore: give up required validation

* fix: correct issue template candidates

* fix: correct checkboxes style

* chore: ignore .hugo_build.lock in docs

* docs: separate out a new doc for merge templates

* docs: introduce syntax of yaml template

* feat: show a alert for invalid templates

* test: add case for a valid template

* fix: correct attributes of required checkbox

* fix: add class not-under-easymde for dropzone

* fix: use more back-quotes

* chore: remove translation in zh-CN

* fix EasyMDE statusbar margin

* fix: remove repeated blocks

* fix: reuse regex for quotes

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2022-09-02 15:58:49 +08:00

126 lines
3.4 KiB
Go

// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package template
import (
"fmt"
"io"
"path/filepath"
"strconv"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"gopkg.in/yaml.v2"
)
// CouldBe indicates a file with the filename could be a template,
// it is a low cost check before further processing.
func CouldBe(filename string) bool {
it := &api.IssueTemplate{
FileName: filename,
}
return it.Type() != ""
}
// Unmarshal parses out a valid template from the content
func Unmarshal(filename string, content []byte) (*api.IssueTemplate, error) {
it, err := unmarshal(filename, content)
if err != nil {
return nil, err
}
if err := Validate(it); err != nil {
return nil, err
}
return it, nil
}
// UnmarshalFromEntry parses out a valid template from the blob in entry
func UnmarshalFromEntry(entry *git.TreeEntry, dir string) (*api.IssueTemplate, error) {
return unmarshalFromEntry(entry, filepath.Join(dir, entry.Name()))
}
// UnmarshalFromCommit parses out a valid template from the commit
func UnmarshalFromCommit(commit *git.Commit, filename string) (*api.IssueTemplate, error) {
entry, err := commit.GetTreeEntryByPath(filename)
if err != nil {
return nil, fmt.Errorf("get entry for %q: %w", filename, err)
}
return unmarshalFromEntry(entry, filename)
}
// UnmarshalFromRepo parses out a valid template from the head commit of the branch
func UnmarshalFromRepo(repo *git.Repository, branch, filename string) (*api.IssueTemplate, error) {
commit, err := repo.GetBranchCommit(branch)
if err != nil {
return nil, fmt.Errorf("get commit on branch %q: %w", branch, err)
}
return UnmarshalFromCommit(commit, filename)
}
func unmarshalFromEntry(entry *git.TreeEntry, filename string) (*api.IssueTemplate, error) {
if size := entry.Blob().Size(); size > setting.UI.MaxDisplayFileSize {
return nil, fmt.Errorf("too large: %v > MaxDisplayFileSize", size)
}
r, err := entry.Blob().DataAsync()
if err != nil {
return nil, fmt.Errorf("data async: %w", err)
}
defer r.Close()
content, err := io.ReadAll(r)
if err != nil {
return nil, fmt.Errorf("read all: %w", err)
}
return Unmarshal(filename, content)
}
func unmarshal(filename string, content []byte) (*api.IssueTemplate, error) {
it := &api.IssueTemplate{
FileName: filename,
}
// Compatible with treating description as about
compatibleTemplate := &struct {
About string `yaml:"description"`
}{}
if typ := it.Type(); typ == api.IssueTemplateTypeMarkdown {
templateBody, err := markdown.ExtractMetadata(string(content), it)
if err != nil {
return nil, err
}
it.Content = templateBody
if it.About == "" {
if _, err := markdown.ExtractMetadata(string(content), compatibleTemplate); err == nil && compatibleTemplate.About != "" {
it.About = compatibleTemplate.About
}
}
} else if typ == api.IssueTemplateTypeYaml {
if err := yaml.Unmarshal(content, it); err != nil {
return nil, fmt.Errorf("yaml unmarshal: %w", err)
}
if it.About == "" {
if err := yaml.Unmarshal(content, compatibleTemplate); err == nil && compatibleTemplate.About != "" {
it.About = compatibleTemplate.About
}
}
for i, v := range it.Fields {
if v.ID == "" {
v.ID = strconv.Itoa(i)
}
}
}
return it, nil
}