turbo-editors/turbo-corepublic Fork 0
v1.0.2
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-core.git
git clone ssh://git@rickub.com/turbo-editors/turbo-core.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

projectfile.go · 83 lines · 2.7 KBGo Blame HistoryRaw
🛟 Updated. 28d5985 k33g 18h ago1// Package projectfile writes the TOML files a project keeps in the editor's own
2// directory — .turbo-go, .turbo-rust — namely its settings, its snippets and
3// its tools.
4//
5// It exists because three packages were writing one identically: the same
6// temporary file, the same rename, the same mode. A fourth would have been the
7// point at which nobody remembered which copy was the correct one.
8//
9// if err := projectfile.Write(path, []byte(contents)); err != nil {
10// return err
11// }
12package projectfile
13
14import (
15 "fmt"
16 "os"
17 "path/filepath"
18)
19
20// fileMode is what a project's configuration file is created with: readable by
21// everyone, writable by its owner. These files are meant to be committed and
22// read by a team.
23const fileMode = 0o644
24
25// dirMode is what the editor's own directory is created with.
26const dirMode = 0o755
27
28// Write creates or replaces a project file, making its directory if need be.
29//
30// The write goes to a temporary file in the same directory which is then
31// renamed over the target, so an interrupted write leaves the previous file
32// intact rather than half of the new one. The temporary must share the
33// directory because a rename is only atomic within one filesystem.
34//
35// This is deliberately **not** what internal/buffer does for a source file.
36// That one preserves the mode of the file it is replacing, because it is saving
37// over something the user already had; this one is creating a file the editor
38// is introducing, and a fixed mode is the right answer for it.
39//
40// err := projectfile.Write(".turbo-go/tools.toml", []byte(template))
41func Write(path string, data []byte) error {
42 if err := write(path, data); err != nil {
43 return fmt.Errorf("writing %s: %w", path, err)
44 }
45 return nil
46}
47
48// write does the work, leaving the error to be named by Write.
49func write(path string, data []byte) error {
50 directory := filepath.Dir(path)
51 if err := os.MkdirAll(directory, dirMode); err != nil {
52 return err
53 }
54
55 temporary, err := os.CreateTemp(directory, ".projectfile-*.toml")
56 if err != nil {
57 return err
58 }
59 name := temporary.Name()
60 defer os.Remove(name) // a no-op once the rename below has succeeded
61
62 if err := writeAndClose(temporary, data); err != nil {
63 return err
64 }
65 // CreateTemp makes the file readable by its owner only, and the rename
66 // would carry that through to a file a team is meant to share.
67 if err := os.Chmod(name, fileMode); err != nil {
68 return err
69 }
70 return os.Rename(name, path)
71}
72
73// writeAndClose writes a file and closes it, closing it either way.
74//
75// The close is what reports a write that failed on its way out of the buffer,
76// so its error matters as much as the write's.
77func writeAndClose(f *os.File, data []byte) error {
78 if _, err := f.Write(data); err != nil {
79 f.Close()
80 return err
81 }
82 return f.Close()
83}