139 lines
3.4 KiB
Go
139 lines
3.4 KiB
Go
// Path: Backend/internal/bootstrap/project_disk_helpers.go
|
|
|
|
package bootstrap
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"unicode"
|
|
)
|
|
|
|
func samePath(left, right string) bool {
|
|
cleanLeft := filepath.Clean(left)
|
|
cleanRight := filepath.Clean(right)
|
|
if cleanLeft == cleanRight {
|
|
return true
|
|
}
|
|
leftInfo, leftErr := os.Stat(cleanLeft)
|
|
rightInfo, rightErr := os.Stat(cleanRight)
|
|
if leftErr == nil && rightErr == nil {
|
|
return os.SameFile(leftInfo, rightInfo)
|
|
}
|
|
return false
|
|
}
|
|
|
|
func normalizePOSIXSlug(value string) string {
|
|
trimmed := strings.TrimSpace(strings.ToLower(value))
|
|
if trimmed == "" {
|
|
return "untitled"
|
|
}
|
|
var builder strings.Builder
|
|
lastDash := false
|
|
for _, r := range trimmed {
|
|
switch {
|
|
case unicode.IsLetter(r) || unicode.IsDigit(r):
|
|
builder.WriteRune(r)
|
|
lastDash = false
|
|
case r == '-' || r == '_' || unicode.IsSpace(r):
|
|
if !lastDash && builder.Len() > 0 {
|
|
builder.WriteByte('-')
|
|
lastDash = true
|
|
}
|
|
}
|
|
}
|
|
slug := strings.Trim(builder.String(), "-")
|
|
if slug == "" {
|
|
return "untitled"
|
|
}
|
|
return slug
|
|
}
|
|
|
|
func fallbackFolderLabel(path string) string {
|
|
base := filepath.Base(filepath.FromSlash(path))
|
|
trimmed := strings.TrimPrefix(base, "folder-")
|
|
parts := strings.FieldsFunc(trimmed, func(r rune) bool { return r == '-' || r == '_' })
|
|
for index, part := range parts {
|
|
if part != "" {
|
|
parts[index] = strings.ToUpper(part[:1]) + part[1:]
|
|
}
|
|
}
|
|
label := strings.Join(parts, " ")
|
|
if label == "" {
|
|
return base
|
|
}
|
|
return label
|
|
}
|
|
|
|
func fallbackItemLabel(path string) string {
|
|
base := filepath.Base(filepath.FromSlash(path))
|
|
trimmed := strings.TrimPrefix(base, "item-")
|
|
parts := strings.FieldsFunc(trimmed, func(r rune) bool { return r == '-' || r == '_' })
|
|
for index, part := range parts {
|
|
if part != "" {
|
|
parts[index] = strings.ToUpper(part[:1]) + part[1:]
|
|
}
|
|
}
|
|
label := strings.Join(parts, " ")
|
|
if label == "" {
|
|
return base
|
|
}
|
|
return label
|
|
}
|
|
|
|
func normalizeProjectTreeItemType(itemType string) string {
|
|
switch strings.TrimSpace(strings.ToLower(itemType)) {
|
|
case "", "board", "core.board", "core.board.kanban", "kanban":
|
|
return "core.board.kanban"
|
|
case "core.doc", "doc", "document":
|
|
return "core.doc"
|
|
case "core.board.list", "list", "list-board":
|
|
return "core.board.list"
|
|
default:
|
|
return strings.TrimSpace(itemType)
|
|
}
|
|
}
|
|
|
|
func defaultProjectTreeItemSchema(itemType string) map[string]any {
|
|
return map[string]any{"type": "object", "itemType": normalizeProjectTreeItemType(itemType)}
|
|
}
|
|
|
|
func defaultProjectTreeItemData(itemType, name string) map[string]any {
|
|
return map[string]any{"title": strings.TrimSpace(name), "itemType": normalizeProjectTreeItemType(itemType)}
|
|
}
|
|
|
|
func slugDir(prefix, slug string) string {
|
|
trimmedSlug := strings.TrimSpace(slug)
|
|
if trimmedSlug == "" {
|
|
return prefix
|
|
}
|
|
return fmt.Sprintf("%s-%s", prefix, trimmedSlug)
|
|
}
|
|
|
|
func writeJSONFile(path string, payload any) error {
|
|
parentDir := filepath.Dir(path)
|
|
if err := os.MkdirAll(parentDir, 0o755); err != nil {
|
|
return err
|
|
}
|
|
data, err := json.MarshalIndent(payload, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
data = append(data, '\n')
|
|
return os.WriteFile(path, data, 0o644)
|
|
}
|
|
|
|
func readJSONFileMap(path string) map[string]any {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return map[string]any{}
|
|
}
|
|
var payload map[string]any
|
|
if err := json.Unmarshal(data, &payload); err != nil || payload == nil {
|
|
return map[string]any{}
|
|
}
|
|
return payload
|
|
}
|