github.com/ungtb10d/cli/v2@v2.0.0-20221110210412-98537dd9d6a1/pkg/githubtemplate/github_template.go (about)

     1  package githubtemplate
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path"
     7  	"regexp"
     8  	"sort"
     9  	"strings"
    10  
    11  	"gopkg.in/yaml.v3"
    12  )
    13  
    14  // FindNonLegacy returns the list of template file paths from the template folder (according to the "upgraded multiple template builder")
    15  func FindNonLegacy(rootDir string, name string) []string {
    16  	results := []string{}
    17  
    18  	// https://help.github.com/en/github/building-a-strong-community/creating-a-pull-request-template-for-your-repository
    19  	candidateDirs := []string{
    20  		path.Join(rootDir, ".github"),
    21  		rootDir,
    22  		path.Join(rootDir, "docs"),
    23  	}
    24  
    25  mainLoop:
    26  	for _, dir := range candidateDirs {
    27  		files, err := os.ReadDir(dir)
    28  		if err != nil {
    29  			continue
    30  		}
    31  
    32  		// detect multiple templates in a subdirectory
    33  		for _, file := range files {
    34  			if strings.EqualFold(file.Name(), name) && file.IsDir() {
    35  				templates, err := os.ReadDir(path.Join(dir, file.Name()))
    36  				if err != nil {
    37  					break
    38  				}
    39  				for _, tf := range templates {
    40  					if strings.HasSuffix(tf.Name(), ".md") {
    41  						results = append(results, path.Join(dir, file.Name(), tf.Name()))
    42  					}
    43  				}
    44  				if len(results) > 0 {
    45  					break mainLoop
    46  				}
    47  				break
    48  			}
    49  		}
    50  	}
    51  	sort.Strings(results)
    52  	return results
    53  }
    54  
    55  // FindLegacy returns the file path of the default(legacy) template
    56  func FindLegacy(rootDir string, name string) string {
    57  	namePattern := regexp.MustCompile(fmt.Sprintf(`(?i)^%s(\.|$)`, strings.ReplaceAll(name, "_", "[_-]")))
    58  
    59  	// https://help.github.com/en/github/building-a-strong-community/creating-a-pull-request-template-for-your-repository
    60  	candidateDirs := []string{
    61  		path.Join(rootDir, ".github"),
    62  		rootDir,
    63  		path.Join(rootDir, "docs"),
    64  	}
    65  	for _, dir := range candidateDirs {
    66  		files, err := os.ReadDir(dir)
    67  		if err != nil {
    68  			continue
    69  		}
    70  
    71  		// detect a single template file
    72  		for _, file := range files {
    73  			if namePattern.MatchString(file.Name()) && !file.IsDir() {
    74  				return path.Join(dir, file.Name())
    75  			}
    76  		}
    77  	}
    78  	return ""
    79  }
    80  
    81  // ExtractName returns the name of the template from YAML front-matter
    82  func ExtractName(filePath string) string {
    83  	contents, err := os.ReadFile(filePath)
    84  	frontmatterBoundaries := detectFrontmatter(contents)
    85  	if err == nil && frontmatterBoundaries[0] == 0 {
    86  		templateData := struct {
    87  			Name string
    88  		}{}
    89  		if err := yaml.Unmarshal(contents[0:frontmatterBoundaries[1]], &templateData); err == nil && templateData.Name != "" {
    90  			return templateData.Name
    91  		}
    92  	}
    93  	return path.Base(filePath)
    94  }
    95  
    96  // ExtractContents returns the template contents without the YAML front-matter
    97  func ExtractContents(filePath string) []byte {
    98  	contents, err := os.ReadFile(filePath)
    99  	if err != nil {
   100  		return []byte{}
   101  	}
   102  	if frontmatterBoundaries := detectFrontmatter(contents); frontmatterBoundaries[0] == 0 {
   103  		return contents[frontmatterBoundaries[1]:]
   104  	}
   105  	return contents
   106  }
   107  
   108  var yamlPattern = regexp.MustCompile(`(?m)^---\r?\n(\s*\r?\n)?`)
   109  
   110  func detectFrontmatter(c []byte) []int {
   111  	if matches := yamlPattern.FindAllIndex(c, 2); len(matches) > 1 {
   112  		return []int{matches[0][0], matches[1][1]}
   113  	}
   114  	return []int{-1, -1}
   115  }