turbo-editors/turbo-corepublic Fork 0
main
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.

🛟 Updated. 28d5985 · on main · k33g · 4h ago
projectfile.go · 83 lines · 2.7 KBGo Blame HistoryRaw
 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()
}