Files

189 lines
4.3 KiB
Go
Raw Permalink Normal View History

2026-05-06 19:40:55 +08:00
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
2026-05-07 11:04:11 +08:00
customFieldPolicy: strict
fieldRegistry: gsp.fields
2026-05-06 19:40:55 +08:00
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
2026-05-06 20:00:50 +08:00
title: Project Entry
2026-05-06 19:40:55 +08:00
type: concept
resolution: L0
context: Project entry GSP.
`, yamlScalar(entry))
2026-05-07 11:04:11 +08:00
fieldsContent := `gspFieldsVersion: 0.1
fields: {}
`
2026-05-06 19:40:55 +08:00
if err := writeInitFile(absRoot, filepath.Join(absRoot, "gsp.manifest"), []byte(manifest), options.Force, &result); err != nil {
return InitResult{}, err
}
2026-05-07 11:04:11 +08:00
if err := writeInitFile(absRoot, filepath.Join(absRoot, "gsp.fields"), []byte(fieldsContent), options.Force, &result); err != nil {
return InitResult{}, err
}
2026-05-06 19:40:55 +08:00
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
}