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