189 lines
4.3 KiB
Go
189 lines
4.3 KiB
Go
package gsp
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
type InitOptions struct {
|
|
Name string
|
|
Entry string
|
|
Force bool
|
|
GSPVersion string
|
|
ToolkitVersion string
|
|
DefaultOutputFolder string
|
|
}
|
|
|
|
type InitResult struct {
|
|
Root string `json:"root"`
|
|
Created []string `json:"created,omitempty"`
|
|
Updated []string `json:"updated,omitempty"`
|
|
}
|
|
|
|
func InitProject(root string, options InitOptions) (InitResult, error) {
|
|
absRoot, err := filepath.Abs(root)
|
|
if err != nil {
|
|
return InitResult{}, err
|
|
}
|
|
if err := os.MkdirAll(absRoot, 0755); err != nil {
|
|
return InitResult{}, err
|
|
}
|
|
|
|
name := strings.TrimSpace(options.Name)
|
|
if name == "" {
|
|
name = filepath.Base(absRoot)
|
|
}
|
|
entry := strings.TrimSpace(options.Entry)
|
|
if entry == "" {
|
|
entry = "project.entry"
|
|
}
|
|
gspVersion := strings.TrimSpace(options.GSPVersion)
|
|
if gspVersion == "" {
|
|
gspVersion = DefaultGSPVersion
|
|
}
|
|
toolkitVersion := strings.TrimSpace(options.ToolkitVersion)
|
|
if toolkitVersion == "" {
|
|
toolkitVersion = ToolkitVersion
|
|
}
|
|
output := strings.TrimSpace(options.DefaultOutputFolder)
|
|
if output == "" {
|
|
output = ".gsp"
|
|
}
|
|
|
|
result := InitResult{Root: filepath.ToSlash(absRoot)}
|
|
designDir := filepath.Join(absRoot, "design")
|
|
if err := os.MkdirAll(designDir, 0755); err != nil {
|
|
return InitResult{}, err
|
|
}
|
|
result.Created = append(result.Created, relPath(absRoot, designDir)+"/")
|
|
|
|
manifest := fmt.Sprintf(`gspVersion: %s
|
|
toolkitVersion: %s
|
|
project: %s
|
|
entry:
|
|
- %s
|
|
scan:
|
|
- design
|
|
customFieldPolicy: strict
|
|
fieldRegistry: gsp.fields
|
|
stageRules:
|
|
design:
|
|
minResolution: L0
|
|
integrate:
|
|
minResolution: L2
|
|
implement:
|
|
minResolution: L3
|
|
bind:
|
|
minResolution: L4
|
|
release:
|
|
minResolution: L5
|
|
types:
|
|
- concept
|
|
- style
|
|
- feedback
|
|
- interaction
|
|
- mechanic
|
|
- page
|
|
output: %s
|
|
`, gspVersion, toolkitVersion, yamlScalar(name), yamlScalar(entry), yamlScalar(output))
|
|
|
|
entryFile := filepath.Join(designDir, safeFileName(entry)+".gsp")
|
|
entryContent := fmt.Sprintf(`id: %s
|
|
title: Project Entry
|
|
type: concept
|
|
resolution: L0
|
|
context: Project entry GSP.
|
|
`, yamlScalar(entry))
|
|
fieldsContent := `gspFieldsVersion: 0.1
|
|
fields: {}
|
|
`
|
|
|
|
if err := writeInitFile(absRoot, filepath.Join(absRoot, "gsp.manifest"), []byte(manifest), options.Force, &result); err != nil {
|
|
return InitResult{}, err
|
|
}
|
|
if err := writeInitFile(absRoot, filepath.Join(absRoot, "gsp.fields"), []byte(fieldsContent), options.Force, &result); err != nil {
|
|
return InitResult{}, err
|
|
}
|
|
if err := writeInitFile(absRoot, entryFile, []byte(entryContent), options.Force, &result); err != nil {
|
|
return InitResult{}, err
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func writeInitFile(root, path string, data []byte, force bool, result *InitResult) error {
|
|
existed := false
|
|
if _, err := os.Stat(path); err == nil {
|
|
existed = true
|
|
if !force {
|
|
return fmt.Errorf("%s already exists; use --force to overwrite generated init files", relPath(root, path))
|
|
}
|
|
} else if err != nil && !os.IsNotExist(err) {
|
|
return err
|
|
}
|
|
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
|
return err
|
|
}
|
|
if err := os.WriteFile(path, data, 0644); err != nil {
|
|
return err
|
|
}
|
|
rel := relPath(root, path)
|
|
if existed {
|
|
result.Updated = append(result.Updated, rel)
|
|
return nil
|
|
}
|
|
result.Created = append(result.Created, rel)
|
|
return nil
|
|
}
|
|
|
|
func safeFileName(value string) string {
|
|
var builder strings.Builder
|
|
for _, r := range value {
|
|
switch {
|
|
case r >= 'a' && r <= 'z':
|
|
builder.WriteRune(r)
|
|
case r >= 'A' && r <= 'Z':
|
|
builder.WriteRune(r)
|
|
case r >= '0' && r <= '9':
|
|
builder.WriteRune(r)
|
|
case r == '.', r == '-', r == '_':
|
|
builder.WriteRune(r)
|
|
default:
|
|
builder.WriteRune('_')
|
|
}
|
|
}
|
|
name := strings.Trim(builder.String(), "._-")
|
|
if name == "" {
|
|
return "project.entry"
|
|
}
|
|
return name
|
|
}
|
|
|
|
func yamlScalar(value string) string {
|
|
value = strings.TrimSpace(value)
|
|
if value == "" {
|
|
return `""`
|
|
}
|
|
if isPlainYAMLScalar(value) {
|
|
return value
|
|
}
|
|
escaped := strings.ReplaceAll(value, `\`, `\\`)
|
|
escaped = strings.ReplaceAll(escaped, `"`, `\"`)
|
|
return `"` + escaped + `"`
|
|
}
|
|
|
|
func isPlainYAMLScalar(value string) bool {
|
|
for _, r := range value {
|
|
switch {
|
|
case r >= 'a' && r <= 'z':
|
|
case r >= 'A' && r <= 'Z':
|
|
case r >= '0' && r <= '9':
|
|
case r == '.', r == '-', r == '_':
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|