1
+ module Preferences
2
+ import ... Pkg, .. TOML
3
+ import .. API: get_uuid
4
+ import .. Types: parse_toml
5
+ import Base: UUID
6
+
7
+ export load_preferences, @load_preferences ,
8
+ save_preferences!, @save_preferences! ,
9
+ modify_preferences!, @modify_preferences! ,
10
+ clear_preferences!, @clear_preferences!
11
+
12
+
13
+ """
14
+ depot_preferences_paths(uuid::UUID)
15
+
16
+ Return the possible paths of all preferences file for the given package `UUID` saved in
17
+ depot-wide `prefs` locations.
18
+ """
19
+ function depot_preferences_paths (uuid:: UUID )
20
+ depots = reverse (Pkg. depots ())
21
+ return [joinpath (depot, " prefs" , string (uuid, " .toml" )) for depot in depots]
22
+ end
23
+
24
+ """
25
+ get_uuid_throw(m::Module)
26
+
27
+ Convert a `Module` to a `UUID`, throwing an `ArgumentError` if the given module does not
28
+ correspond to a loaded package. This is expected for modules such as `Base`, `Main`,
29
+ anonymous modules, etc...
30
+ """
31
+ function get_uuid_throw (m:: Module )
32
+ uuid = get_uuid (m)
33
+ if uuid === nothing
34
+ throw (ArgumentError (" Module does not correspond to a loaded package!" ))
35
+ end
36
+ return uuid
37
+ end
38
+
39
+ """
40
+ recursive_merge(base::Dict, overrides::Dict...)
41
+
42
+ Helper function to merge preference dicts recursively, honoring overrides in nested
43
+ dictionaries properly.
44
+ """
45
+ function recursive_merge (base:: Dict , overrides:: Dict... )
46
+ new_base = Base. _typeddict (base, overrides... )
47
+ for override in overrides
48
+ for (k, v) in override
49
+ if haskey (new_base, k) && isa (new_base[k], Dict) && isa (override[k], Dict)
50
+ new_base[k] = recursive_merge (new_base[k], override[k])
51
+ else
52
+ new_base[k] = override[k]
53
+ end
54
+ end
55
+ end
56
+ return new_base
57
+ end
58
+
59
+ """
60
+ load_preferences(uuid::UUID)
61
+ load_preferences(m::Module)
62
+
63
+ Load the preferences for the given package, returning them as a `Dict`. Most users
64
+ should use the `@load_preferences()` macro which auto-determines the calling `Module`.
65
+ """
66
+ function load_preferences (uuid:: UUID )
67
+ # First, load from depots, merging as we go:
68
+ prefs = Dict {String,Any} ()
69
+ for path in depot_preferences_paths (uuid)
70
+ if isfile (path)
71
+ prefs = recursive_merge (prefs, parse_toml (path))
72
+ end
73
+ end
74
+
75
+ # Finally, load from the currently-active project:
76
+ proj_path = Base. active_project ()
77
+ if isfile (proj_path)
78
+ project = parse_toml (proj_path)
79
+ if haskey (project, " preferences" ) && isa (project[" preferences" ], Dict)
80
+ proj_prefs = get (project[" preferences" ], string (uuid), Dict ())
81
+ prefs = recursive_merge (prefs, proj_prefs)
82
+ end
83
+ end
84
+ return prefs
85
+ end
86
+ load_preferences (m:: Module ) = load_preferences (get_uuid_throw (m))
87
+
88
+ """
89
+ save_preferences!(uuid::UUID, prefs::Dict; depot::Union{String,Nothing} = nothing)
90
+ save_preferences!(m::Module, prefs::Dict; depot::Union{String,Nothing} = nothing)
91
+
92
+ Save the preferences for the given package. Most users should use the
93
+ `@save_preferences!()` macro which auto-determines the calling `Module`. See also the
94
+ `modify_preferences!()` function (and the associated `@modifiy_preferences!()` macro) for
95
+ easy load/modify/save workflows.
96
+
97
+ The `depot` keyword argument allows saving of depot-wide preferences, as opposed to the
98
+ default of project-specific preferences. Simply set the `depot` keyword argument to the
99
+ path of a depot (use `Pkg.depots1()` for the default depot) and the preferences will be
100
+ saved to that location.
101
+ """
102
+ function save_preferences! (uuid:: UUID , prefs:: Dict ;
103
+ depot:: Union{AbstractString,Nothing} = nothing )
104
+ if depot === nothing
105
+ # Save to project
106
+ proj_path = Base. active_project ()
107
+ project = Dict {String,Any} ()
108
+ if isfile (proj_path)
109
+ project = parse_toml (proj_path)
110
+ end
111
+ if ! haskey (project, " preferences" )
112
+ project[" preferences" ] = Dict {String,Any} ()
113
+ end
114
+ if ! isa (project[" preferences" ], Dict)
115
+ error (" $(proj_path) has conflicting `preferences` entry type: Not a Dict!" )
116
+ end
117
+ project[" preferences" ][string (uuid)] = prefs
118
+ mkpath (dirname (proj_path))
119
+ open (proj_path, " w" ) do io
120
+ TOML. print (io, project, sorted= true )
121
+ end
122
+ else
123
+ path = joinpath (depot, " prefs" , string (uuid, " .toml" ))
124
+ mkpath (dirname (path))
125
+ open (path, " w" ) do io
126
+ TOML. print (io, prefs, sorted= true )
127
+ end
128
+ end
129
+ return nothing
130
+ end
131
+ function save_preferences! (m:: Module , prefs:: Dict ;
132
+ depot:: Union{AbstractString,Nothing} = nothing )
133
+ return save_preferences! (get_uuid_throw (m), prefs; depot= depot)
134
+ end
135
+
136
+ """
137
+ modify_preferences!(f::Function, uuid::UUID)
138
+ modify_preferences!(f::Function, m::Module)
139
+
140
+ Supports `do`-block modification of preferences. Loads the preferences, passes them to a
141
+ user function, then writes the modified `Dict` back to the preferences file. Example:
142
+
143
+ ```julia
144
+ modify_preferences!(@__MODULE__) do prefs
145
+ prefs["key"] = "value"
146
+ end
147
+ ```
148
+
149
+ This function returns the full preferences object. Most users should use the
150
+ `@modify_preferences!()` macro which auto-determines the calling `Module`.
151
+
152
+ Note that this method does not support modifying depot-wide preferences; modifications
153
+ always are saved to the active project.
154
+ """
155
+ function modify_preferences! (f:: Function , uuid:: UUID )
156
+ prefs = load_preferences (uuid)
157
+ f (prefs)
158
+ save_preferences! (uuid, prefs)
159
+ return prefs
160
+ end
161
+ modify_preferences! (f:: Function , m:: Module ) = modify_preferences! (f, get_uuid_throw (m))
162
+
163
+ """
164
+ clear_preferences!(uuid::UUID)
165
+ clear_preferences!(m::Module)
166
+
167
+ Convenience method to remove all preferences for the given package. Most users should
168
+ use the `@clear_preferences!()` macro, which auto-determines the calling `Module`. This
169
+ method clears not only project-specific preferences, but also depot-wide preferences, if
170
+ the current user has the permissions to do so.
171
+ """
172
+ function clear_preferences! (uuid:: UUID )
173
+ for path in depot_preferences_paths (uuid)
174
+ try
175
+ rm (path; force= true )
176
+ catch
177
+ @warn (" Unable to remove preference path $(path) " )
178
+ end
179
+ end
180
+
181
+ # Clear the project preferences key, if it exists
182
+ proj_path = Base. active_project ()
183
+ if isfile (proj_path)
184
+ project = parse_toml (proj_path)
185
+ if haskey (project, " preferences" ) && isa (project[" preferences" ], Dict)
186
+ delete! (project[" preferences" ], string (uuid))
187
+ open (proj_path, " w" ) do io
188
+ TOML. print (io, project, sorted= true )
189
+ end
190
+ end
191
+ end
192
+ end
193
+
194
+ """
195
+ @load_preferences()
196
+
197
+ Convenience macro to call `load_preferences()` for the current package.
198
+ """
199
+ macro load_preferences ()
200
+ return quote
201
+ load_preferences ($ (esc (get_uuid_throw (__module__))))
202
+ end
203
+ end
204
+
205
+ """
206
+ @save_preferences!(prefs)
207
+
208
+ Convenience macro to call `save_preferences!()` for the current package. Note that
209
+ saving to a depot path is not supported in this macro, use `save_preferences!()` if you
210
+ wish to do that.
211
+ """
212
+ macro save_preferences! (prefs)
213
+ return quote
214
+ save_preferences! ($ (esc (get_uuid_throw (__module__))), $ (esc (prefs)))
215
+ end
216
+ end
217
+
218
+ """
219
+ @modify_preferences!(func)
220
+
221
+ Convenience macro to call `modify_preferences!()` for the current package.
222
+ """
223
+ macro modify_preferences! (func)
224
+ return quote
225
+ modify_preferences! ($ (esc (func)), $ (esc (get_uuid_throw (__module__))))
226
+ end
227
+ end
228
+
229
+ """
230
+ @clear_preferences!()
231
+
232
+ Convenience macro to call `clear_preferences!()` for the current package.
233
+ """
234
+ macro clear_preferences! ()
235
+ return quote
236
+ preferences! ($ (esc (get_uuid_throw (__module__))))
237
+ end
238
+ end
239
+ end # module Preferences
0 commit comments