-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathconfig.go
169 lines (146 loc) · 4.57 KB
/
config.go
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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
// SPDX-FileCopyrightText: 2019-2025 caixw
//
// SPDX-License-Identifier: MIT
//go:generate web locale -l=und -f=yaml ./
//go:generate web update-locale -src=./locales/und.yaml -dest=./locales/zh.yaml
// Package config 提供了对多种格式配置文件的支持
package config
import (
"errors"
"io/fs"
"os"
"path/filepath"
)
// Config 项目的配置文件管理
//
// 相对于 [Serializer],Config 提供了对目录的保护,只能存储指定目录下的内容。
type Config struct {
root *os.Root
s Serializer
}
// BuildDir 根据 dir 生成不同的 [Config]
//
// dir 为项目的配置文件目录,后续通过 [Config] 操作都被限制在此目录之下。可以带以下的特殊前缀:
// - ~ 表示系统提供的配置文件目录,比如 Linux 的 XDG_CONFIG、Windows 的 AppData 等;
// - @ 表示当前程序的主目录;
// - ^ 表示绝对路径;
// - # 表示工作路径,这是一个随着工作目录变化的值,使用时需要小心;
// - 其它则是直接采用 [Dir] 初始化。
//
// 这是对 [SystemDir]、[AppDir]、[Dir] 和 [WDDir] 的合并处理。
func BuildDir(s Serializer, dir string) (*Config, error) {
if len(dir) == 0 {
return Dir(s, dir), nil
}
switch dir[0] {
case '@':
return AppDir(s, dir[1:])
case '~':
return SystemDir(s, dir[1:])
case '^':
return Dir(s, dir[1:]), nil
case '#':
return WDDir(s, dir[1:])
default:
return Dir(s, dir), nil
}
}
// SystemDir 将系统提供的配置目录下的 dir 作为配置目录
//
// dir 相对的 [os.UserConfigDir] 目录名称;
func SystemDir(s Serializer, dir string) (*Config, error) {
return New(s, dir, os.UserConfigDir)
}
// AppDir 将应用程序下的 dir 作为配置文件的保存目录
//
// dir 相对 [os.Executable] 的目录名称;
func AppDir(s Serializer, dir string) (*Config, error) {
return New(s, dir, func() (string, error) {
ex, err := os.Executable()
return filepath.Dir(ex), err
})
}
// WDDir 将工作目录作为配置文件的保存目录
//
// dir 相对 [os.Getwd] 的目录名称;
func WDDir(s Serializer, dir string) (*Config, error) {
return New(s, dir, os.Getwd)
}
// Dir 以指定的目录作为配置文件的保存位置
func Dir(s Serializer, dir string) *Config {
c, _ := New(s, dir, nil)
return c
}
// New 声明 [Config] 对象
//
// dir 表示当前项目的配置文件存放的目录名称,如果 parent 不为空,为相对于 parent 返回值的路径;
// parent 表示获取系统中用于存放配置文件的路径,比如 Linux 中的 XDG_CONFIG 等目录。
// 用户可以根据自己的需求自行实现该方法,如果为 nil,表示直接将 dir 作为全路径进行处理。
func New(s Serializer, dir string, parent func() (string, error)) (*Config, error) {
if parent != nil {
p, err := parent()
if err != nil {
return nil, err
}
if p != "" {
dir = filepath.Join(p, dir)
}
}
if s == nil {
s = make(Serializer, 5)
}
if _, err := os.Stat(dir); errors.Is(err, fs.ErrNotExist) {
if err := os.MkdirAll(dir, fs.ModePerm); err != nil {
return nil, err
}
} else if err != nil {
return nil, err
}
root, err := os.OpenRoot(dir)
if err != nil {
return nil, err
}
return &Config{
root: root,
s: s,
}, nil
}
// Exists 是否存在指定的文件
func (f *Config) Exists(name string) bool {
_, err := f.Root().Stat(name)
return !errors.Is(err, fs.ErrNotExist)
}
// Root 配置文件的目录
func (f *Config) Root() *os.Root { return f.root }
// Load 加载指定名称的文件内容至 v
//
// name 为文件名,相对于 [Config.Root],根据文件扩展名决定采用什么编码方法;
// 如果 v 实现了 [Sanitizer],在加载之后会调用该接口对数据进行处理;
func (f *Config) Load(name string, v any) error {
if err := f.s.Unmarshal(f.Root().OpenFile, name, v); err != nil {
return err
}
if s, ok := v.(Sanitizer); ok {
if fe := s.SanitizeConfig(); fe != nil {
fe.Path = name
return fe
}
}
return nil
}
// Read 读取文件的原始内容
func (f *Config) Read(name string) ([]byte, error) { return fs.ReadFile(f.Root().FS(), name) }
// Save 将 v 解码并保存至 name 中
//
// 根据文件扩展名决定采用什么编码方法;
// mode 表示文件的权限,仅对新建文件时有效;
// 如果 v 实现了 [Sanitizer],在保存之前会调用该接口对数据进行处理;
func (f *Config) Save(name string, v any, mode fs.FileMode) error {
if s, ok := v.(Sanitizer); ok {
if fe := s.SanitizeConfig(); fe != nil {
fe.Path = name
return fe
}
}
return f.s.Marshal(f.Root().OpenFile, name, v, mode)
}