Files
ctask/internal/session/sessionname.go
T

69 lines
1.8 KiB
Go

package session
import (
"crypto/sha256"
"encoding/hex"
"path/filepath"
"runtime"
"strings"
)
// SessionName returns a stable tmux session name for the given workspace.
// Format: ctask-<category>-<slug>-<hash6>.
//
// category and slug are sanitized to [A-Za-z0-9_-]; characters outside that
// set become '_'; runs of '_' collapse; both are lowercased. slug is
// truncated to 30 characters maximum (after sanitization). hash6 is the
// first six lowercase-hex characters of sha256(canonical absolute path).
//
// On Windows the path is lowercased before hashing to match
// config.searchRootKey conventions for case-insensitive filesystems.
func SessionName(category, slug, absWsPath string) string {
cat := sanitizeNameComponent(category)
sl := sanitizeNameComponent(slug)
if len(sl) > 30 {
sl = sl[:30]
sl = strings.TrimRight(sl, "_")
}
clean := filepath.Clean(absWsPath)
if runtime.GOOS == "windows" {
clean = strings.ToLower(clean)
}
sum := sha256.Sum256([]byte(clean))
hash := hex.EncodeToString(sum[:])[:6]
return "ctask-" + cat + "-" + sl + "-" + hash
}
// sanitizeNameComponent replaces every char outside [A-Za-z0-9_-] with '_',
// collapses runs of '_', trims leading/trailing '_' and '-', and lowercases.
func sanitizeNameComponent(s string) string {
if s == "" {
return "_"
}
var b strings.Builder
b.Grow(len(s))
for _, r := range s {
switch {
case r >= 'a' && r <= 'z':
b.WriteRune(r)
case r >= 'A' && r <= 'Z':
b.WriteRune(r + ('a' - 'A'))
case r >= '0' && r <= '9':
b.WriteRune(r)
case r == '_' || r == '-':
b.WriteRune(r)
default:
b.WriteByte('_')
}
}
out := b.String()
for strings.Contains(out, "__") {
out = strings.ReplaceAll(out, "__", "_")
}
out = strings.Trim(out, "_-")
if out == "" {
return "_"
}
return out
}