From 2773179dd571de41e9d619dae110d5fe61d7918e Mon Sep 17 00:00:00 2001 From: flu0r1ne Date: Sun, 25 Jun 2023 02:47:02 -0500 Subject: initial commit --- main.go | 379 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 379 insertions(+) create mode 100644 main.go (limited to 'main.go') diff --git a/main.go b/main.go new file mode 100644 index 0000000..3dfe15c --- /dev/null +++ b/main.go @@ -0,0 +1,379 @@ +package main + +import ( + "errors" + "fmt" + "log" + "os" + "path" + "path/filepath" + "sort" + "strings" + "time" +) + +/********************************************* + * Workspace Root + *********************************************/ + +type workspaceRoot struct { + path string +}; + +func systemWorkspaceRoot() workspaceRoot { + const WORKSPACE_DIR = "ws" + + home, err := os.UserHomeDir() + + if err != nil { + log.Fatalf("The user home directory is not defined: %v", err) + } + + workspaceRootPath := filepath.Join(home, WORKSPACE_DIR); + + _, err = os.Stat(workspaceRootPath) + + if os.IsNotExist(err) { + if err := os.Mkdir(workspaceRootPath, 0755); err != nil { + log.Fatalf("Failed to create directory: %s", workspaceRootPath); + } + } + + return workspaceRoot { workspaceRootPath } +} + +type workspaceDirs []*workspaceDir + +func (wsRoot *workspaceRoot) readWorkspaces() workspaceDirs { + dirs, err := os.ReadDir(wsRoot.path) + + if err != nil { + log.Fatalf("Failed to read workspaces in %s: %v", wsRoot.path, err); + } + + workspaces := make([]*workspaceDir, 0) + + for _, dir := range dirs { + if !dir.Type().IsDir() { + continue + } + + wsDir, err := newWorkspaceFromName(wsRoot, dir.Name()) + + if err != nil { + log.Printf("Error reading directory in workspace %s: %s: %v", wsRoot.path, dir.Name(), err); + continue + } + + workspaces = append(workspaces, wsDir); + } + + return workspaces +} + +func (dirs workspaceDirs) findDirByName(name string) *workspaceDir { + birthTime, nonce, err := parseWorkspaceName(name) + + if err != nil { + log.Fatalf("Cannot find directory %s, error parsing name", name); + } + + var dir *workspaceDir = nil + + for _, dir = range dirs { + if dir.birthTime.Equal(*birthTime) && dir.nonce == *nonce { + break + } + } + + return dir +} + +/********************************************* + * Workspace Directory Object + *********************************************/ + +type workspaceDir struct { + birthTime time.Time + nonce string + accessTime time.Time + parent *workspaceRoot +} + +var MisformattedWorkspaceError = errors.New("Misformatted workspace name error") + +func formatWorkspaceName(birthTime time.Time, nonce string) string { + dateTime := birthTime.UTC().Format(time.DateTime) + dateTime = strings.ReplaceAll(dateTime, " ", "_") + return dateTime + "." + nonce +} + +func parseWorkspaceName(name string) (birthTime *time.Time, nonce *string, err error) { + parts := strings.Split(name, ".") + + if len(parts) != 2 { + return nil, nil, MisformattedWorkspaceError + } + + dateTime := strings.ReplaceAll(parts[0], "_", " ") + + timePart, err := time.ParseInLocation(time.DateTime, dateTime, time.UTC) + noncePart := parts[1] + + if err != nil { + return nil, nil, MisformattedWorkspaceError + } + + return &timePart, &noncePart, nil +} + +func newWorkspaceFromName(parent *workspaceRoot, name string) (dir *workspaceDir, err error) { + dir = nil + + birthTime, nonce, err := parseWorkspaceName(name) + if err != nil { + return + } + + st, err := os.Stat(path.Join(parent.path, name)) + if err != nil { + return + } + + dir = &workspaceDir { + *birthTime, + *nonce, + st.ModTime(), + parent, + } + + return +} + +func createWorkspaceDir(ws *workspaceRoot) workspaceDir { + birthTime := time.Now() + namePattern := formatWorkspaceName(birthTime, "*") + + dir, err := os.MkdirTemp(ws.path, namePattern) + if err != nil { + log.Fatalf("Failed to create new workspace directory: %v", err); + } + + dir = path.Base(dir) + + _, nonce, err := parseWorkspaceName(dir) + + if err != nil { + log.Fatalf("Failed to parse the workspace name: %v\n", err); + } + + return workspaceDir { + birthTime, + *nonce, + birthTime, + ws, + }; +} + +func (dir *workspaceDir) name() string { + return formatWorkspaceName(dir.birthTime, dir.nonce) +} + +func (dir *workspaceDir) path() string { + return path.Join(dir.parent.path, dir.name()) +} + +func (dir *workspaceDir) timeSinceLastAccess() time.Duration { + return time.Since(dir.accessTime) +} + +/********************************************* + * Commands Helpers + *********************************************/ + +const ( + secondsInSec = 1 + minsInSec = 60 * secondsInSec + hourInSec = 60 * minsInSec + dayInSec = 24 * hourInSec +) + +func formatTimestamp(duration time.Duration) string { + secs := int64(duration.Seconds()) + days := secs / dayInSec + secs %= dayInSec + hours := secs / hourInSec + secs %= hourInSec + mins := secs / minsInSec + secs %= minsInSec + + return fmt.Sprintf("%d days %d hours %d min %d secs", days, hours, mins, secs) +} + +func readRecencySortedWorkspaces() (*workspaceRoot, workspaceDirs) { + wsRoot := systemWorkspaceRoot() + workspaces := wsRoot.readWorkspaces() + + sort.Slice(workspaces, func(i, j int) bool { + return workspaces[i].accessTime.Compare(workspaces[j].accessTime) < 0 + }) + + return &wsRoot, workspaces +} + +func currentWorkspace(wsRoot *workspaceRoot, workspaces workspaceDirs) int { + + cwd, err := os.Getwd() + + if err != nil { + log.Fatalf("Failed to obtain the working directory: %s\n", err); + } + + if !strings.HasPrefix(cwd, wsRoot.path) { + log.Fatalf("The current working directory is not in a workspace\n") + } + + dirName := path.Base(cwd) + + cur_idx := 0 + for ; cur_idx < len(workspaces); cur_idx++ { + dir := workspaces[cur_idx] + if dir.name() == dirName { + break + } + } + + if cur_idx == len(workspaces) { + log.Fatalf("Failed to find %s\n", dirName) + } + + return cur_idx +} + +/********************************************* + * Commands + *********************************************/ + +func createNewWorkspace() { + ws := systemWorkspaceRoot() + workspace := createWorkspaceDir(&ws) + path := workspace.path() + + if err := os.Chdir(path); err != nil { + log.Fatalf("Failed to change into directory: %v\n", err) + } + + fmt.Println(path) +} + +func listWorkspaces() { + _, workspaces := readRecencySortedWorkspaces() + + for i, ws := range workspaces { + delta := ws.timeSinceLastAccess() + + fmt.Println(i + 1, ws.name(), formatTimestamp(delta)) + } +} + +func printRecentWorkspace() { + _, workspaces := readRecencySortedWorkspaces() + + if len(workspaces) == 0 { + log.Fatalln("There are currently no workspaces. Create a workspace with `ws new`.") + } + + fmt.Println(workspaces[len(workspaces) - 1].path()) +} + +type printWorkspaceSubcmd int + +const ( + PrevWs printWorkspaceSubcmd = iota + NextWs + CurrWs +) + +func printWorkspace(subcmd printWorkspaceSubcmd) { + wsRoot, workspaces := readRecencySortedWorkspaces() + + idx := currentWorkspace(wsRoot, workspaces) + + switch subcmd { + case PrevWs: + if idx <= 0 { + log.Fatalln("The first workspace has been reached") + } + + idx-- + case NextWs: + if idx + 1 >= len(workspaces) { + log.Fatalln("The current workspace is the newest workspace") + } + + idx++ + } + + fmt.Println(workspaces[idx].path()) +} + +func printWorkspaceRoot() { + wsRoot := systemWorkspaceRoot() + fmt.Println(wsRoot.path) +} + +func fatalPrintUsage() { +fmt.Printf(` + ws internal + + ws_internal create_new_workspace + ws_internal list_workspaces + ws_internal print_recent_workspace + ws_internal print_current_workspace + ws_internal print_next_workspace + ws_internal print_prev_workspace + ws_internal print_workspace_root + ws_internal activate +`) + os.Exit(1) +} + +func main() { + + if len(os.Args) < 1 { + os.Exit(127) + } + + prog := os.Args[0] + + log.SetPrefix(prog + ": ") + log.SetFlags(0) + + if len(os.Args) < 2 { + fatalPrintUsage() + } + + cmd := os.Args[1] + + switch cmd { + case "create_new_workspace": + createNewWorkspace() + case "list_workspaces": + listWorkspaces() + case "print_recent_workspace": + printRecentWorkspace() + case "print_current_workspace": + printWorkspace(CurrWs) + case "print_next_workspace": + printWorkspace(NextWs) + case "print_prev_workspace": + printWorkspace(PrevWs) + case "print_workspace_root": + printWorkspaceRoot() + case "activate": + fmt.Print(shellWrapper) + default: + fatalPrintUsage() + } + +} -- cgit v1.2.3