##// END OF EJS Templates
rust-config: fix incorrect coercion of null values to false...
Raphaël Gomès -
r51876:8343947a default
parent child Browse files
Show More
@@ -1,725 +1,725 b''
1 //! Code for parsing default Mercurial config items.
1 //! Code for parsing default Mercurial config items.
2 use itertools::Itertools;
2 use itertools::Itertools;
3 use serde::Deserialize;
3 use serde::Deserialize;
4
4
5 use crate::{errors::HgError, exit_codes, FastHashMap};
5 use crate::{errors::HgError, exit_codes, FastHashMap};
6
6
7 /// Corresponds to the structure of `mercurial/configitems.toml`.
7 /// Corresponds to the structure of `mercurial/configitems.toml`.
8 #[derive(Debug, Deserialize)]
8 #[derive(Debug, Deserialize)]
9 pub struct ConfigItems {
9 pub struct ConfigItems {
10 items: Vec<DefaultConfigItem>,
10 items: Vec<DefaultConfigItem>,
11 templates: FastHashMap<String, Vec<TemplateItem>>,
11 templates: FastHashMap<String, Vec<TemplateItem>>,
12 #[serde(rename = "template-applications")]
12 #[serde(rename = "template-applications")]
13 template_applications: Vec<TemplateApplication>,
13 template_applications: Vec<TemplateApplication>,
14 }
14 }
15
15
16 /// Corresponds to a config item declaration in `mercurial/configitems.toml`.
16 /// Corresponds to a config item declaration in `mercurial/configitems.toml`.
17 #[derive(Clone, Debug, PartialEq, Deserialize)]
17 #[derive(Clone, Debug, PartialEq, Deserialize)]
18 #[serde(try_from = "RawDefaultConfigItem")]
18 #[serde(try_from = "RawDefaultConfigItem")]
19 pub struct DefaultConfigItem {
19 pub struct DefaultConfigItem {
20 /// Section of the config the item is in (e.g. `[merge-tools]`)
20 /// Section of the config the item is in (e.g. `[merge-tools]`)
21 section: String,
21 section: String,
22 /// Name of the item (e.g. `meld.gui`)
22 /// Name of the item (e.g. `meld.gui`)
23 name: String,
23 name: String,
24 /// Default value (can be dynamic, see [`DefaultConfigItemType`])
24 /// Default value (can be dynamic, see [`DefaultConfigItemType`])
25 default: Option<DefaultConfigItemType>,
25 default: Option<DefaultConfigItemType>,
26 /// If the config option is generic (e.g. `merge-tools.*`), defines
26 /// If the config option is generic (e.g. `merge-tools.*`), defines
27 /// the priority of this item relative to other generic items.
27 /// the priority of this item relative to other generic items.
28 /// If we're looking for <pattern>, then all generic items within the same
28 /// If we're looking for <pattern>, then all generic items within the same
29 /// section will be sorted by order of priority, and the first regex match
29 /// section will be sorted by order of priority, and the first regex match
30 /// against `name` is returned.
30 /// against `name` is returned.
31 #[serde(default)]
31 #[serde(default)]
32 priority: Option<isize>,
32 priority: Option<isize>,
33 /// Aliases, if any. Each alias is a tuple of `(section, name)` for each
33 /// Aliases, if any. Each alias is a tuple of `(section, name)` for each
34 /// option that is aliased to this one.
34 /// option that is aliased to this one.
35 #[serde(default)]
35 #[serde(default)]
36 alias: Vec<(String, String)>,
36 alias: Vec<(String, String)>,
37 /// Whether the config item is marked as experimental
37 /// Whether the config item is marked as experimental
38 #[serde(default)]
38 #[serde(default)]
39 experimental: bool,
39 experimental: bool,
40 /// The (possibly empty) docstring for the item
40 /// The (possibly empty) docstring for the item
41 #[serde(default)]
41 #[serde(default)]
42 documentation: String,
42 documentation: String,
43 /// Whether the item is part of an in-core extension. This allows us to
43 /// Whether the item is part of an in-core extension. This allows us to
44 /// hide them if the extension is not enabled, to preserve legacy
44 /// hide them if the extension is not enabled, to preserve legacy
45 /// behavior.
45 /// behavior.
46 #[serde(default)]
46 #[serde(default)]
47 in_core_extension: Option<String>,
47 in_core_extension: Option<String>,
48 }
48 }
49
49
50 /// Corresponds to the raw (i.e. on disk) structure of config items. Used as
50 /// Corresponds to the raw (i.e. on disk) structure of config items. Used as
51 /// an intermediate step in deserialization.
51 /// an intermediate step in deserialization.
52 #[derive(Clone, Debug, Deserialize)]
52 #[derive(Clone, Debug, Deserialize)]
53 struct RawDefaultConfigItem {
53 struct RawDefaultConfigItem {
54 section: String,
54 section: String,
55 name: String,
55 name: String,
56 default: Option<toml::Value>,
56 default: Option<toml::Value>,
57 #[serde(rename = "default-type")]
57 #[serde(rename = "default-type")]
58 default_type: Option<String>,
58 default_type: Option<String>,
59 #[serde(default)]
59 #[serde(default)]
60 priority: isize,
60 priority: isize,
61 #[serde(default)]
61 #[serde(default)]
62 generic: bool,
62 generic: bool,
63 #[serde(default)]
63 #[serde(default)]
64 alias: Vec<(String, String)>,
64 alias: Vec<(String, String)>,
65 #[serde(default)]
65 #[serde(default)]
66 experimental: bool,
66 experimental: bool,
67 #[serde(default)]
67 #[serde(default)]
68 documentation: String,
68 documentation: String,
69 #[serde(default)]
69 #[serde(default)]
70 in_core_extension: Option<String>,
70 in_core_extension: Option<String>,
71 }
71 }
72
72
73 impl TryFrom<RawDefaultConfigItem> for DefaultConfigItem {
73 impl TryFrom<RawDefaultConfigItem> for DefaultConfigItem {
74 type Error = HgError;
74 type Error = HgError;
75
75
76 fn try_from(value: RawDefaultConfigItem) -> Result<Self, Self::Error> {
76 fn try_from(value: RawDefaultConfigItem) -> Result<Self, Self::Error> {
77 Ok(Self {
77 Ok(Self {
78 section: value.section,
78 section: value.section,
79 name: value.name,
79 name: value.name,
80 default: raw_default_to_concrete(
80 default: raw_default_to_concrete(
81 value.default_type,
81 value.default_type,
82 value.default,
82 value.default,
83 )?,
83 )?,
84 priority: if value.generic {
84 priority: if value.generic {
85 Some(value.priority)
85 Some(value.priority)
86 } else {
86 } else {
87 None
87 None
88 },
88 },
89 alias: value.alias,
89 alias: value.alias,
90 experimental: value.experimental,
90 experimental: value.experimental,
91 documentation: value.documentation,
91 documentation: value.documentation,
92 in_core_extension: value.in_core_extension,
92 in_core_extension: value.in_core_extension,
93 })
93 })
94 }
94 }
95 }
95 }
96
96
97 impl DefaultConfigItem {
97 impl DefaultConfigItem {
98 fn is_generic(&self) -> bool {
98 fn is_generic(&self) -> bool {
99 self.priority.is_some()
99 self.priority.is_some()
100 }
100 }
101
101
102 pub fn in_core_extension(&self) -> Option<&str> {
102 pub fn in_core_extension(&self) -> Option<&str> {
103 self.in_core_extension.as_deref()
103 self.in_core_extension.as_deref()
104 }
104 }
105
105
106 pub fn section(&self) -> &str {
106 pub fn section(&self) -> &str {
107 self.section.as_ref()
107 self.section.as_ref()
108 }
108 }
109 }
109 }
110
110
111 impl<'a> TryFrom<&'a DefaultConfigItem> for Option<&'a str> {
111 impl<'a> TryFrom<&'a DefaultConfigItem> for Option<&'a str> {
112 type Error = HgError;
112 type Error = HgError;
113
113
114 fn try_from(
114 fn try_from(
115 value: &'a DefaultConfigItem,
115 value: &'a DefaultConfigItem,
116 ) -> Result<Option<&'a str>, Self::Error> {
116 ) -> Result<Option<&'a str>, Self::Error> {
117 match &value.default {
117 match &value.default {
118 Some(default) => {
118 Some(default) => {
119 let err = HgError::abort(
119 let err = HgError::abort(
120 format!(
120 format!(
121 "programming error: wrong query on config item '{}.{}'",
121 "programming error: wrong query on config item '{}.{}'",
122 value.section,
122 value.section,
123 value.name
123 value.name
124 ),
124 ),
125 exit_codes::ABORT,
125 exit_codes::ABORT,
126 Some(format!(
126 Some(format!(
127 "asked for '&str', type of default is '{}'",
127 "asked for '&str', type of default is '{}'",
128 default.type_str()
128 default.type_str()
129 )),
129 )),
130 );
130 );
131 match default {
131 match default {
132 DefaultConfigItemType::Primitive(toml::Value::String(
132 DefaultConfigItemType::Primitive(toml::Value::String(
133 s,
133 s,
134 )) => Ok(Some(s)),
134 )) => Ok(Some(s)),
135 _ => Err(err),
135 _ => Err(err),
136 }
136 }
137 }
137 }
138 None => Ok(None),
138 None => Ok(None),
139 }
139 }
140 }
140 }
141 }
141 }
142
142
143 impl<'a> TryFrom<&'a DefaultConfigItem> for Option<&'a [u8]> {
143 impl<'a> TryFrom<&'a DefaultConfigItem> for Option<&'a [u8]> {
144 type Error = HgError;
144 type Error = HgError;
145
145
146 fn try_from(
146 fn try_from(
147 value: &'a DefaultConfigItem,
147 value: &'a DefaultConfigItem,
148 ) -> Result<Option<&'a [u8]>, Self::Error> {
148 ) -> Result<Option<&'a [u8]>, Self::Error> {
149 match &value.default {
149 match &value.default {
150 Some(default) => {
150 Some(default) => {
151 let err = HgError::abort(
151 let err = HgError::abort(
152 format!(
152 format!(
153 "programming error: wrong query on config item '{}.{}'",
153 "programming error: wrong query on config item '{}.{}'",
154 value.section,
154 value.section,
155 value.name
155 value.name
156 ),
156 ),
157 exit_codes::ABORT,
157 exit_codes::ABORT,
158 Some(format!(
158 Some(format!(
159 "asked for bytes, type of default is '{}', \
159 "asked for bytes, type of default is '{}', \
160 which cannot be interpreted as bytes",
160 which cannot be interpreted as bytes",
161 default.type_str()
161 default.type_str()
162 )),
162 )),
163 );
163 );
164 match default {
164 match default {
165 DefaultConfigItemType::Primitive(p) => {
165 DefaultConfigItemType::Primitive(p) => {
166 Ok(p.as_str().map(str::as_bytes))
166 Ok(p.as_str().map(str::as_bytes))
167 }
167 }
168 _ => Err(err),
168 _ => Err(err),
169 }
169 }
170 }
170 }
171 None => Ok(None),
171 None => Ok(None),
172 }
172 }
173 }
173 }
174 }
174 }
175
175
176 impl TryFrom<&DefaultConfigItem> for Option<bool> {
176 impl TryFrom<&DefaultConfigItem> for Option<bool> {
177 type Error = HgError;
177 type Error = HgError;
178
178
179 fn try_from(value: &DefaultConfigItem) -> Result<Self, Self::Error> {
179 fn try_from(value: &DefaultConfigItem) -> Result<Self, Self::Error> {
180 match &value.default {
180 match &value.default {
181 Some(default) => {
181 Some(default) => {
182 let err = HgError::abort(
182 let err = HgError::abort(
183 format!(
183 format!(
184 "programming error: wrong query on config item '{}.{}'",
184 "programming error: wrong query on config item '{}.{}'",
185 value.section,
185 value.section,
186 value.name
186 value.name
187 ),
187 ),
188 exit_codes::ABORT,
188 exit_codes::ABORT,
189 Some(format!(
189 Some(format!(
190 "asked for 'bool', type of default is '{}'",
190 "asked for 'bool', type of default is '{}'",
191 default.type_str()
191 default.type_str()
192 )),
192 )),
193 );
193 );
194 match default {
194 match default {
195 DefaultConfigItemType::Primitive(
195 DefaultConfigItemType::Primitive(
196 toml::Value::Boolean(b),
196 toml::Value::Boolean(b),
197 ) => Ok(Some(*b)),
197 ) => Ok(Some(*b)),
198 _ => Err(err),
198 _ => Err(err),
199 }
199 }
200 }
200 }
201 None => Ok(Some(false)),
201 None => Ok(None),
202 }
202 }
203 }
203 }
204 }
204 }
205
205
206 impl TryFrom<&DefaultConfigItem> for Option<u32> {
206 impl TryFrom<&DefaultConfigItem> for Option<u32> {
207 type Error = HgError;
207 type Error = HgError;
208
208
209 fn try_from(value: &DefaultConfigItem) -> Result<Self, Self::Error> {
209 fn try_from(value: &DefaultConfigItem) -> Result<Self, Self::Error> {
210 match &value.default {
210 match &value.default {
211 Some(default) => {
211 Some(default) => {
212 let err = HgError::abort(
212 let err = HgError::abort(
213 format!(
213 format!(
214 "programming error: wrong query on config item '{}.{}'",
214 "programming error: wrong query on config item '{}.{}'",
215 value.section,
215 value.section,
216 value.name
216 value.name
217 ),
217 ),
218 exit_codes::ABORT,
218 exit_codes::ABORT,
219 Some(format!(
219 Some(format!(
220 "asked for 'u32', type of default is '{}'",
220 "asked for 'u32', type of default is '{}'",
221 default.type_str()
221 default.type_str()
222 )),
222 )),
223 );
223 );
224 match default {
224 match default {
225 DefaultConfigItemType::Primitive(
225 DefaultConfigItemType::Primitive(
226 toml::Value::Integer(b),
226 toml::Value::Integer(b),
227 ) => {
227 ) => {
228 Ok(Some((*b).try_into().expect("TOML integer to u32")))
228 Ok(Some((*b).try_into().expect("TOML integer to u32")))
229 }
229 }
230 _ => Err(err),
230 _ => Err(err),
231 }
231 }
232 }
232 }
233 None => Ok(None),
233 None => Ok(None),
234 }
234 }
235 }
235 }
236 }
236 }
237
237
238 impl TryFrom<&DefaultConfigItem> for Option<u64> {
238 impl TryFrom<&DefaultConfigItem> for Option<u64> {
239 type Error = HgError;
239 type Error = HgError;
240
240
241 fn try_from(value: &DefaultConfigItem) -> Result<Self, Self::Error> {
241 fn try_from(value: &DefaultConfigItem) -> Result<Self, Self::Error> {
242 match &value.default {
242 match &value.default {
243 Some(default) => {
243 Some(default) => {
244 let err = HgError::abort(
244 let err = HgError::abort(
245 format!(
245 format!(
246 "programming error: wrong query on config item '{}.{}'",
246 "programming error: wrong query on config item '{}.{}'",
247 value.section,
247 value.section,
248 value.name
248 value.name
249 ),
249 ),
250 exit_codes::ABORT,
250 exit_codes::ABORT,
251 Some(format!(
251 Some(format!(
252 "asked for 'u64', type of default is '{}'",
252 "asked for 'u64', type of default is '{}'",
253 default.type_str()
253 default.type_str()
254 )),
254 )),
255 );
255 );
256 match default {
256 match default {
257 DefaultConfigItemType::Primitive(
257 DefaultConfigItemType::Primitive(
258 toml::Value::Integer(b),
258 toml::Value::Integer(b),
259 ) => {
259 ) => {
260 Ok(Some((*b).try_into().expect("TOML integer to u64")))
260 Ok(Some((*b).try_into().expect("TOML integer to u64")))
261 }
261 }
262 _ => Err(err),
262 _ => Err(err),
263 }
263 }
264 }
264 }
265 None => Ok(None),
265 None => Ok(None),
266 }
266 }
267 }
267 }
268 }
268 }
269
269
270 /// Allows abstracting over more complex default values than just primitives.
270 /// Allows abstracting over more complex default values than just primitives.
271 /// The former `configitems.py` contained some dynamic code that is encoded
271 /// The former `configitems.py` contained some dynamic code that is encoded
272 /// in this enum.
272 /// in this enum.
273 #[derive(Debug, PartialEq, Clone, Deserialize)]
273 #[derive(Debug, PartialEq, Clone, Deserialize)]
274 pub enum DefaultConfigItemType {
274 pub enum DefaultConfigItemType {
275 /// Some primitive type (string, integer, boolean)
275 /// Some primitive type (string, integer, boolean)
276 Primitive(toml::Value),
276 Primitive(toml::Value),
277 /// A dynamic value that will be given by the code at runtime
277 /// A dynamic value that will be given by the code at runtime
278 Dynamic,
278 Dynamic,
279 /// An lazily-returned array (possibly only relevant in the Python impl)
279 /// An lazily-returned array (possibly only relevant in the Python impl)
280 /// Example: `lambda: [b"zstd", b"zlib"]`
280 /// Example: `lambda: [b"zstd", b"zlib"]`
281 Lambda(Vec<String>),
281 Lambda(Vec<String>),
282 /// For now, a special case for `web.encoding` that points to the
282 /// For now, a special case for `web.encoding` that points to the
283 /// `encoding.encoding` module in the Python impl so that local encoding
283 /// `encoding.encoding` module in the Python impl so that local encoding
284 /// is correctly resolved at runtime
284 /// is correctly resolved at runtime
285 LazyModule(String),
285 LazyModule(String),
286 ListType,
286 ListType,
287 }
287 }
288
288
289 impl DefaultConfigItemType {
289 impl DefaultConfigItemType {
290 pub fn type_str(&self) -> &str {
290 pub fn type_str(&self) -> &str {
291 match self {
291 match self {
292 DefaultConfigItemType::Primitive(primitive) => {
292 DefaultConfigItemType::Primitive(primitive) => {
293 primitive.type_str()
293 primitive.type_str()
294 }
294 }
295 DefaultConfigItemType::Dynamic => "dynamic",
295 DefaultConfigItemType::Dynamic => "dynamic",
296 DefaultConfigItemType::Lambda(_) => "lambda",
296 DefaultConfigItemType::Lambda(_) => "lambda",
297 DefaultConfigItemType::LazyModule(_) => "lazy_module",
297 DefaultConfigItemType::LazyModule(_) => "lazy_module",
298 DefaultConfigItemType::ListType => "list_type",
298 DefaultConfigItemType::ListType => "list_type",
299 }
299 }
300 }
300 }
301 }
301 }
302
302
303 /// Most of the fields are shared with [`DefaultConfigItem`].
303 /// Most of the fields are shared with [`DefaultConfigItem`].
304 #[derive(Debug, Clone, Deserialize)]
304 #[derive(Debug, Clone, Deserialize)]
305 #[serde(try_from = "RawTemplateItem")]
305 #[serde(try_from = "RawTemplateItem")]
306 struct TemplateItem {
306 struct TemplateItem {
307 suffix: String,
307 suffix: String,
308 default: Option<DefaultConfigItemType>,
308 default: Option<DefaultConfigItemType>,
309 priority: Option<isize>,
309 priority: Option<isize>,
310 #[serde(default)]
310 #[serde(default)]
311 alias: Vec<(String, String)>,
311 alias: Vec<(String, String)>,
312 #[serde(default)]
312 #[serde(default)]
313 experimental: bool,
313 experimental: bool,
314 #[serde(default)]
314 #[serde(default)]
315 documentation: String,
315 documentation: String,
316 }
316 }
317
317
318 /// Corresponds to the raw (i.e. on disk) representation of a template item.
318 /// Corresponds to the raw (i.e. on disk) representation of a template item.
319 /// Used as an intermediate step in deserialization.
319 /// Used as an intermediate step in deserialization.
320 #[derive(Clone, Debug, Deserialize)]
320 #[derive(Clone, Debug, Deserialize)]
321 struct RawTemplateItem {
321 struct RawTemplateItem {
322 suffix: String,
322 suffix: String,
323 default: Option<toml::Value>,
323 default: Option<toml::Value>,
324 #[serde(rename = "default-type")]
324 #[serde(rename = "default-type")]
325 default_type: Option<String>,
325 default_type: Option<String>,
326 #[serde(default)]
326 #[serde(default)]
327 priority: isize,
327 priority: isize,
328 #[serde(default)]
328 #[serde(default)]
329 generic: bool,
329 generic: bool,
330 #[serde(default)]
330 #[serde(default)]
331 alias: Vec<(String, String)>,
331 alias: Vec<(String, String)>,
332 #[serde(default)]
332 #[serde(default)]
333 experimental: bool,
333 experimental: bool,
334 #[serde(default)]
334 #[serde(default)]
335 documentation: String,
335 documentation: String,
336 }
336 }
337
337
338 impl TemplateItem {
338 impl TemplateItem {
339 fn into_default_item(
339 fn into_default_item(
340 self,
340 self,
341 application: TemplateApplication,
341 application: TemplateApplication,
342 ) -> DefaultConfigItem {
342 ) -> DefaultConfigItem {
343 DefaultConfigItem {
343 DefaultConfigItem {
344 section: application.section,
344 section: application.section,
345 name: application
345 name: application
346 .prefix
346 .prefix
347 .map(|prefix| format!("{}.{}", prefix, self.suffix))
347 .map(|prefix| format!("{}.{}", prefix, self.suffix))
348 .unwrap_or(self.suffix),
348 .unwrap_or(self.suffix),
349 default: self.default,
349 default: self.default,
350 priority: self.priority,
350 priority: self.priority,
351 alias: self.alias,
351 alias: self.alias,
352 experimental: self.experimental,
352 experimental: self.experimental,
353 documentation: self.documentation,
353 documentation: self.documentation,
354 in_core_extension: None,
354 in_core_extension: None,
355 }
355 }
356 }
356 }
357 }
357 }
358
358
359 impl TryFrom<RawTemplateItem> for TemplateItem {
359 impl TryFrom<RawTemplateItem> for TemplateItem {
360 type Error = HgError;
360 type Error = HgError;
361
361
362 fn try_from(value: RawTemplateItem) -> Result<Self, Self::Error> {
362 fn try_from(value: RawTemplateItem) -> Result<Self, Self::Error> {
363 Ok(Self {
363 Ok(Self {
364 suffix: value.suffix,
364 suffix: value.suffix,
365 default: raw_default_to_concrete(
365 default: raw_default_to_concrete(
366 value.default_type,
366 value.default_type,
367 value.default,
367 value.default,
368 )?,
368 )?,
369 priority: if value.generic {
369 priority: if value.generic {
370 Some(value.priority)
370 Some(value.priority)
371 } else {
371 } else {
372 None
372 None
373 },
373 },
374 alias: value.alias,
374 alias: value.alias,
375 experimental: value.experimental,
375 experimental: value.experimental,
376 documentation: value.documentation,
376 documentation: value.documentation,
377 })
377 })
378 }
378 }
379 }
379 }
380
380
381 /// Transforms the on-disk string-based representation of complex default types
381 /// Transforms the on-disk string-based representation of complex default types
382 /// to the concrete [`DefaultconfigItemType`].
382 /// to the concrete [`DefaultconfigItemType`].
383 fn raw_default_to_concrete(
383 fn raw_default_to_concrete(
384 default_type: Option<String>,
384 default_type: Option<String>,
385 default: Option<toml::Value>,
385 default: Option<toml::Value>,
386 ) -> Result<Option<DefaultConfigItemType>, HgError> {
386 ) -> Result<Option<DefaultConfigItemType>, HgError> {
387 Ok(match default_type.as_deref() {
387 Ok(match default_type.as_deref() {
388 None => default.as_ref().map(|default| {
388 None => default.as_ref().map(|default| {
389 DefaultConfigItemType::Primitive(default.to_owned())
389 DefaultConfigItemType::Primitive(default.to_owned())
390 }),
390 }),
391 Some("dynamic") => Some(DefaultConfigItemType::Dynamic),
391 Some("dynamic") => Some(DefaultConfigItemType::Dynamic),
392 Some("list_type") => Some(DefaultConfigItemType::ListType),
392 Some("list_type") => Some(DefaultConfigItemType::ListType),
393 Some("lambda") => match &default {
393 Some("lambda") => match &default {
394 Some(default) => Some(DefaultConfigItemType::Lambda(
394 Some(default) => Some(DefaultConfigItemType::Lambda(
395 default.to_owned().try_into().map_err(|e| {
395 default.to_owned().try_into().map_err(|e| {
396 HgError::abort(
396 HgError::abort(
397 e.to_string(),
397 e.to_string(),
398 exit_codes::ABORT,
398 exit_codes::ABORT,
399 Some("Check 'mercurial/configitems.toml'".into()),
399 Some("Check 'mercurial/configitems.toml'".into()),
400 )
400 )
401 })?,
401 })?,
402 )),
402 )),
403 None => {
403 None => {
404 return Err(HgError::abort(
404 return Err(HgError::abort(
405 "lambda defined with no return value".to_string(),
405 "lambda defined with no return value".to_string(),
406 exit_codes::ABORT,
406 exit_codes::ABORT,
407 Some("Check 'mercurial/configitems.toml'".into()),
407 Some("Check 'mercurial/configitems.toml'".into()),
408 ))
408 ))
409 }
409 }
410 },
410 },
411 Some("lazy_module") => match &default {
411 Some("lazy_module") => match &default {
412 Some(default) => {
412 Some(default) => {
413 Some(DefaultConfigItemType::LazyModule(match default {
413 Some(DefaultConfigItemType::LazyModule(match default {
414 toml::Value::String(module) => module.to_owned(),
414 toml::Value::String(module) => module.to_owned(),
415 _ => {
415 _ => {
416 return Err(HgError::abort(
416 return Err(HgError::abort(
417 "lazy_module module name should be a string"
417 "lazy_module module name should be a string"
418 .to_string(),
418 .to_string(),
419 exit_codes::ABORT,
419 exit_codes::ABORT,
420 Some("Check 'mercurial/configitems.toml'".into()),
420 Some("Check 'mercurial/configitems.toml'".into()),
421 ))
421 ))
422 }
422 }
423 }))
423 }))
424 }
424 }
425 None => {
425 None => {
426 return Err(HgError::abort(
426 return Err(HgError::abort(
427 "lazy_module should have a default value".to_string(),
427 "lazy_module should have a default value".to_string(),
428 exit_codes::ABORT,
428 exit_codes::ABORT,
429 Some("Check 'mercurial/configitems.toml'".into()),
429 Some("Check 'mercurial/configitems.toml'".into()),
430 ))
430 ))
431 }
431 }
432 },
432 },
433 Some(invalid) => {
433 Some(invalid) => {
434 return Err(HgError::abort(
434 return Err(HgError::abort(
435 format!("invalid default_type '{}'", invalid),
435 format!("invalid default_type '{}'", invalid),
436 exit_codes::ABORT,
436 exit_codes::ABORT,
437 Some("Check 'mercurial/configitems.toml'".into()),
437 Some("Check 'mercurial/configitems.toml'".into()),
438 ))
438 ))
439 }
439 }
440 })
440 })
441 }
441 }
442
442
443 #[derive(Debug, Clone, Deserialize)]
443 #[derive(Debug, Clone, Deserialize)]
444 struct TemplateApplication {
444 struct TemplateApplication {
445 template: String,
445 template: String,
446 section: String,
446 section: String,
447 #[serde(default)]
447 #[serde(default)]
448 prefix: Option<String>,
448 prefix: Option<String>,
449 }
449 }
450
450
451 /// Represents the (dynamic) set of default core Mercurial config items from
451 /// Represents the (dynamic) set of default core Mercurial config items from
452 /// `mercurial/configitems.toml`.
452 /// `mercurial/configitems.toml`.
453 #[derive(Clone, Debug, Default)]
453 #[derive(Clone, Debug, Default)]
454 pub struct DefaultConfig {
454 pub struct DefaultConfig {
455 /// Mapping of section -> (mapping of name -> item)
455 /// Mapping of section -> (mapping of name -> item)
456 items: FastHashMap<String, FastHashMap<String, DefaultConfigItem>>,
456 items: FastHashMap<String, FastHashMap<String, DefaultConfigItem>>,
457 }
457 }
458
458
459 impl DefaultConfig {
459 impl DefaultConfig {
460 pub fn empty() -> DefaultConfig {
460 pub fn empty() -> DefaultConfig {
461 Self {
461 Self {
462 items: Default::default(),
462 items: Default::default(),
463 }
463 }
464 }
464 }
465
465
466 /// Returns `Self`, given the contents of `mercurial/configitems.toml`
466 /// Returns `Self`, given the contents of `mercurial/configitems.toml`
467 #[logging_timer::time("trace")]
467 #[logging_timer::time("trace")]
468 pub fn from_contents(contents: &str) -> Result<Self, HgError> {
468 pub fn from_contents(contents: &str) -> Result<Self, HgError> {
469 let mut from_file: ConfigItems =
469 let mut from_file: ConfigItems =
470 toml::from_str(contents).map_err(|e| {
470 toml::from_str(contents).map_err(|e| {
471 HgError::abort(
471 HgError::abort(
472 e.to_string(),
472 e.to_string(),
473 exit_codes::ABORT,
473 exit_codes::ABORT,
474 Some("Check 'mercurial/configitems.toml'".into()),
474 Some("Check 'mercurial/configitems.toml'".into()),
475 )
475 )
476 })?;
476 })?;
477
477
478 let mut flat_items = from_file.items;
478 let mut flat_items = from_file.items;
479
479
480 for application in from_file.template_applications.drain(..) {
480 for application in from_file.template_applications.drain(..) {
481 match from_file.templates.get(&application.template) {
481 match from_file.templates.get(&application.template) {
482 None => return Err(
482 None => return Err(
483 HgError::abort(
483 HgError::abort(
484 format!(
484 format!(
485 "template application refers to undefined template '{}'",
485 "template application refers to undefined template '{}'",
486 application.template
486 application.template
487 ),
487 ),
488 exit_codes::ABORT,
488 exit_codes::ABORT,
489 Some("Check 'mercurial/configitems.toml'".into())
489 Some("Check 'mercurial/configitems.toml'".into())
490 )
490 )
491 ),
491 ),
492 Some(template_items) => {
492 Some(template_items) => {
493 for template_item in template_items {
493 for template_item in template_items {
494 flat_items.push(
494 flat_items.push(
495 template_item
495 template_item
496 .clone()
496 .clone()
497 .into_default_item(application.clone()),
497 .into_default_item(application.clone()),
498 )
498 )
499 }
499 }
500 }
500 }
501 };
501 };
502 }
502 }
503
503
504 let items = flat_items.into_iter().fold(
504 let items = flat_items.into_iter().fold(
505 FastHashMap::default(),
505 FastHashMap::default(),
506 |mut acc, item| {
506 |mut acc, item| {
507 acc.entry(item.section.to_owned())
507 acc.entry(item.section.to_owned())
508 .or_insert_with(|| {
508 .or_insert_with(|| {
509 let mut section = FastHashMap::default();
509 let mut section = FastHashMap::default();
510 section.insert(item.name.to_owned(), item.to_owned());
510 section.insert(item.name.to_owned(), item.to_owned());
511 section
511 section
512 })
512 })
513 .insert(item.name.to_owned(), item);
513 .insert(item.name.to_owned(), item);
514 acc
514 acc
515 },
515 },
516 );
516 );
517
517
518 Ok(Self { items })
518 Ok(Self { items })
519 }
519 }
520
520
521 /// Return the default config item that matches `section` and `item`.
521 /// Return the default config item that matches `section` and `item`.
522 pub fn get(
522 pub fn get(
523 &self,
523 &self,
524 section: &[u8],
524 section: &[u8],
525 item: &[u8],
525 item: &[u8],
526 ) -> Option<&DefaultConfigItem> {
526 ) -> Option<&DefaultConfigItem> {
527 // Core items must be valid UTF-8
527 // Core items must be valid UTF-8
528 let section = String::from_utf8_lossy(section);
528 let section = String::from_utf8_lossy(section);
529 let section_map = self.items.get(section.as_ref())?;
529 let section_map = self.items.get(section.as_ref())?;
530 let item_name_lossy = String::from_utf8_lossy(item);
530 let item_name_lossy = String::from_utf8_lossy(item);
531 match section_map.get(item_name_lossy.as_ref()) {
531 match section_map.get(item_name_lossy.as_ref()) {
532 Some(item) => Some(item),
532 Some(item) => Some(item),
533 None => {
533 None => {
534 for generic_item in section_map
534 for generic_item in section_map
535 .values()
535 .values()
536 .filter(|item| item.is_generic())
536 .filter(|item| item.is_generic())
537 .sorted_by_key(|item| match item.priority {
537 .sorted_by_key(|item| match item.priority {
538 Some(priority) => (priority, &item.name),
538 Some(priority) => (priority, &item.name),
539 _ => unreachable!(),
539 _ => unreachable!(),
540 })
540 })
541 {
541 {
542 if regex::bytes::Regex::new(&generic_item.name)
542 if regex::bytes::Regex::new(&generic_item.name)
543 .expect("invalid regex in configitems")
543 .expect("invalid regex in configitems")
544 .is_match(item)
544 .is_match(item)
545 {
545 {
546 return Some(generic_item);
546 return Some(generic_item);
547 }
547 }
548 }
548 }
549 None
549 None
550 }
550 }
551 }
551 }
552 }
552 }
553 }
553 }
554
554
555 #[cfg(test)]
555 #[cfg(test)]
556 mod tests {
556 mod tests {
557 use crate::config::config_items::{
557 use crate::config::config_items::{
558 DefaultConfigItem, DefaultConfigItemType,
558 DefaultConfigItem, DefaultConfigItemType,
559 };
559 };
560
560
561 use super::DefaultConfig;
561 use super::DefaultConfig;
562
562
563 #[test]
563 #[test]
564 fn test_config_read() {
564 fn test_config_read() {
565 let contents = r#"
565 let contents = r#"
566 [[items]]
566 [[items]]
567 section = "alias"
567 section = "alias"
568 name = "abcd.*"
568 name = "abcd.*"
569 default = 3
569 default = 3
570 generic = true
570 generic = true
571 priority = -1
571 priority = -1
572
572
573 [[items]]
573 [[items]]
574 section = "alias"
574 section = "alias"
575 name = ".*"
575 name = ".*"
576 default-type = "dynamic"
576 default-type = "dynamic"
577 generic = true
577 generic = true
578
578
579 [[items]]
579 [[items]]
580 section = "cmdserver"
580 section = "cmdserver"
581 name = "track-log"
581 name = "track-log"
582 default-type = "lambda"
582 default-type = "lambda"
583 default = [ "chgserver", "cmdserver", "repocache",]
583 default = [ "chgserver", "cmdserver", "repocache",]
584
584
585 [[items]]
585 [[items]]
586 section = "chgserver"
586 section = "chgserver"
587 name = "idletimeout"
587 name = "idletimeout"
588 default = 3600
588 default = 3600
589
589
590 [[items]]
590 [[items]]
591 section = "cmdserver"
591 section = "cmdserver"
592 name = "message-encodings"
592 name = "message-encodings"
593 default-type = "list_type"
593 default-type = "list_type"
594
594
595 [[items]]
595 [[items]]
596 section = "web"
596 section = "web"
597 name = "encoding"
597 name = "encoding"
598 default-type = "lazy_module"
598 default-type = "lazy_module"
599 default = "encoding.encoding"
599 default = "encoding.encoding"
600
600
601 [[items]]
601 [[items]]
602 section = "command-templates"
602 section = "command-templates"
603 name = "graphnode"
603 name = "graphnode"
604 alias = [["ui", "graphnodetemplate"]]
604 alias = [["ui", "graphnodetemplate"]]
605 documentation = """This is a docstring.
605 documentation = """This is a docstring.
606 This is another line \
606 This is another line \
607 but this is not."""
607 but this is not."""
608
608
609 [[items]]
609 [[items]]
610 section = "censor"
610 section = "censor"
611 name = "policy"
611 name = "policy"
612 default = "abort"
612 default = "abort"
613 experimental = true
613 experimental = true
614
614
615 [[template-applications]]
615 [[template-applications]]
616 template = "diff-options"
616 template = "diff-options"
617 section = "commands"
617 section = "commands"
618 prefix = "revert.interactive"
618 prefix = "revert.interactive"
619
619
620 [[template-applications]]
620 [[template-applications]]
621 template = "diff-options"
621 template = "diff-options"
622 section = "diff"
622 section = "diff"
623
623
624 [templates]
624 [templates]
625 [[templates.diff-options]]
625 [[templates.diff-options]]
626 suffix = "nodates"
626 suffix = "nodates"
627 default = false
627 default = false
628
628
629 [[templates.diff-options]]
629 [[templates.diff-options]]
630 suffix = "showfunc"
630 suffix = "showfunc"
631 default = false
631 default = false
632
632
633 [[templates.diff-options]]
633 [[templates.diff-options]]
634 suffix = "unified"
634 suffix = "unified"
635 "#;
635 "#;
636 let res = DefaultConfig::from_contents(contents);
636 let res = DefaultConfig::from_contents(contents);
637 let config = match res {
637 let config = match res {
638 Ok(config) => config,
638 Ok(config) => config,
639 Err(e) => panic!("{}", e),
639 Err(e) => panic!("{}", e),
640 };
640 };
641 let expected = DefaultConfigItem {
641 let expected = DefaultConfigItem {
642 section: "censor".into(),
642 section: "censor".into(),
643 name: "policy".into(),
643 name: "policy".into(),
644 default: Some(DefaultConfigItemType::Primitive("abort".into())),
644 default: Some(DefaultConfigItemType::Primitive("abort".into())),
645 priority: None,
645 priority: None,
646 alias: vec![],
646 alias: vec![],
647 experimental: true,
647 experimental: true,
648 documentation: "".into(),
648 documentation: "".into(),
649 in_core_extension: None,
649 in_core_extension: None,
650 };
650 };
651 assert_eq!(config.get(b"censor", b"policy"), Some(&expected));
651 assert_eq!(config.get(b"censor", b"policy"), Some(&expected));
652
652
653 // Test generic priority. The `.*` pattern is wider than `abcd.*`, but
653 // Test generic priority. The `.*` pattern is wider than `abcd.*`, but
654 // `abcd.*` has priority, so it should match first.
654 // `abcd.*` has priority, so it should match first.
655 let expected = DefaultConfigItem {
655 let expected = DefaultConfigItem {
656 section: "alias".into(),
656 section: "alias".into(),
657 name: "abcd.*".into(),
657 name: "abcd.*".into(),
658 default: Some(DefaultConfigItemType::Primitive(3.into())),
658 default: Some(DefaultConfigItemType::Primitive(3.into())),
659 priority: Some(-1),
659 priority: Some(-1),
660 alias: vec![],
660 alias: vec![],
661 experimental: false,
661 experimental: false,
662 documentation: "".into(),
662 documentation: "".into(),
663 in_core_extension: None,
663 in_core_extension: None,
664 };
664 };
665 assert_eq!(config.get(b"alias", b"abcdsomething"), Some(&expected));
665 assert_eq!(config.get(b"alias", b"abcdsomething"), Some(&expected));
666
666
667 //... but if it doesn't, we should fallback to `.*`
667 //... but if it doesn't, we should fallback to `.*`
668 let expected = DefaultConfigItem {
668 let expected = DefaultConfigItem {
669 section: "alias".into(),
669 section: "alias".into(),
670 name: ".*".into(),
670 name: ".*".into(),
671 default: Some(DefaultConfigItemType::Dynamic),
671 default: Some(DefaultConfigItemType::Dynamic),
672 priority: Some(0),
672 priority: Some(0),
673 alias: vec![],
673 alias: vec![],
674 experimental: false,
674 experimental: false,
675 documentation: "".into(),
675 documentation: "".into(),
676 in_core_extension: None,
676 in_core_extension: None,
677 };
677 };
678 assert_eq!(config.get(b"alias", b"something"), Some(&expected));
678 assert_eq!(config.get(b"alias", b"something"), Some(&expected));
679
679
680 let expected = DefaultConfigItem {
680 let expected = DefaultConfigItem {
681 section: "chgserver".into(),
681 section: "chgserver".into(),
682 name: "idletimeout".into(),
682 name: "idletimeout".into(),
683 default: Some(DefaultConfigItemType::Primitive(3600.into())),
683 default: Some(DefaultConfigItemType::Primitive(3600.into())),
684 priority: None,
684 priority: None,
685 alias: vec![],
685 alias: vec![],
686 experimental: false,
686 experimental: false,
687 documentation: "".into(),
687 documentation: "".into(),
688 in_core_extension: None,
688 in_core_extension: None,
689 };
689 };
690 assert_eq!(config.get(b"chgserver", b"idletimeout"), Some(&expected));
690 assert_eq!(config.get(b"chgserver", b"idletimeout"), Some(&expected));
691
691
692 let expected = DefaultConfigItem {
692 let expected = DefaultConfigItem {
693 section: "cmdserver".into(),
693 section: "cmdserver".into(),
694 name: "track-log".into(),
694 name: "track-log".into(),
695 default: Some(DefaultConfigItemType::Lambda(vec![
695 default: Some(DefaultConfigItemType::Lambda(vec![
696 "chgserver".into(),
696 "chgserver".into(),
697 "cmdserver".into(),
697 "cmdserver".into(),
698 "repocache".into(),
698 "repocache".into(),
699 ])),
699 ])),
700 priority: None,
700 priority: None,
701 alias: vec![],
701 alias: vec![],
702 experimental: false,
702 experimental: false,
703 documentation: "".into(),
703 documentation: "".into(),
704 in_core_extension: None,
704 in_core_extension: None,
705 };
705 };
706 assert_eq!(config.get(b"cmdserver", b"track-log"), Some(&expected));
706 assert_eq!(config.get(b"cmdserver", b"track-log"), Some(&expected));
707
707
708 let expected = DefaultConfigItem {
708 let expected = DefaultConfigItem {
709 section: "command-templates".into(),
709 section: "command-templates".into(),
710 name: "graphnode".into(),
710 name: "graphnode".into(),
711 default: None,
711 default: None,
712 priority: None,
712 priority: None,
713 alias: vec![("ui".into(), "graphnodetemplate".into())],
713 alias: vec![("ui".into(), "graphnodetemplate".into())],
714 experimental: false,
714 experimental: false,
715 documentation:
715 documentation:
716 "This is a docstring.\nThis is another line but this is not."
716 "This is a docstring.\nThis is another line but this is not."
717 .into(),
717 .into(),
718 in_core_extension: None,
718 in_core_extension: None,
719 };
719 };
720 assert_eq!(
720 assert_eq!(
721 config.get(b"command-templates", b"graphnode"),
721 config.get(b"command-templates", b"graphnode"),
722 Some(&expected)
722 Some(&expected)
723 );
723 );
724 }
724 }
725 }
725 }
@@ -1,811 +1,810 b''
1 // config.rs
1 // config.rs
2 //
2 //
3 // Copyright 2020
3 // Copyright 2020
4 // Valentin Gatien-Baron,
4 // Valentin Gatien-Baron,
5 // Raphaël Gomès <rgomes@octobus.net>
5 // Raphaël Gomès <rgomes@octobus.net>
6 //
6 //
7 // This software may be used and distributed according to the terms of the
7 // This software may be used and distributed according to the terms of the
8 // GNU General Public License version 2 or any later version.
8 // GNU General Public License version 2 or any later version.
9
9
10 //! Mercurial config parsing and interfaces.
10 //! Mercurial config parsing and interfaces.
11
11
12 pub mod config_items;
12 pub mod config_items;
13 mod layer;
13 mod layer;
14 mod plain_info;
14 mod plain_info;
15 mod values;
15 mod values;
16 pub use layer::{ConfigError, ConfigOrigin, ConfigParseError};
16 pub use layer::{ConfigError, ConfigOrigin, ConfigParseError};
17 use lazy_static::lazy_static;
17 use lazy_static::lazy_static;
18 pub use plain_info::PlainInfo;
18 pub use plain_info::PlainInfo;
19
19
20 use self::config_items::DefaultConfig;
20 use self::config_items::DefaultConfig;
21 use self::config_items::DefaultConfigItem;
21 use self::config_items::DefaultConfigItem;
22 use self::layer::ConfigLayer;
22 use self::layer::ConfigLayer;
23 use self::layer::ConfigValue;
23 use self::layer::ConfigValue;
24 use crate::errors::HgError;
24 use crate::errors::HgError;
25 use crate::errors::{HgResultExt, IoResultExt};
25 use crate::errors::{HgResultExt, IoResultExt};
26 use crate::utils::files::get_bytes_from_os_str;
26 use crate::utils::files::get_bytes_from_os_str;
27 use format_bytes::{write_bytes, DisplayBytes};
27 use format_bytes::{write_bytes, DisplayBytes};
28 use std::collections::HashSet;
28 use std::collections::HashSet;
29 use std::env;
29 use std::env;
30 use std::fmt;
30 use std::fmt;
31 use std::path::{Path, PathBuf};
31 use std::path::{Path, PathBuf};
32 use std::str;
32 use std::str;
33
33
34 lazy_static! {
34 lazy_static! {
35 static ref DEFAULT_CONFIG: Result<DefaultConfig, HgError> = {
35 static ref DEFAULT_CONFIG: Result<DefaultConfig, HgError> = {
36 DefaultConfig::from_contents(include_str!(
36 DefaultConfig::from_contents(include_str!(
37 "../../../../mercurial/configitems.toml"
37 "../../../../mercurial/configitems.toml"
38 ))
38 ))
39 };
39 };
40 }
40 }
41
41
42 /// Holds the config values for the current repository
42 /// Holds the config values for the current repository
43 /// TODO update this docstring once we support more sources
43 /// TODO update this docstring once we support more sources
44 #[derive(Clone)]
44 #[derive(Clone)]
45 pub struct Config {
45 pub struct Config {
46 layers: Vec<layer::ConfigLayer>,
46 layers: Vec<layer::ConfigLayer>,
47 plain: PlainInfo,
47 plain: PlainInfo,
48 }
48 }
49
49
50 impl DisplayBytes for Config {
50 impl DisplayBytes for Config {
51 fn display_bytes(
51 fn display_bytes(
52 &self,
52 &self,
53 out: &mut dyn std::io::Write,
53 out: &mut dyn std::io::Write,
54 ) -> std::io::Result<()> {
54 ) -> std::io::Result<()> {
55 for (index, layer) in self.layers.iter().rev().enumerate() {
55 for (index, layer) in self.layers.iter().rev().enumerate() {
56 write_bytes!(
56 write_bytes!(
57 out,
57 out,
58 b"==== Layer {} (trusted: {}) ====\n{}",
58 b"==== Layer {} (trusted: {}) ====\n{}",
59 index,
59 index,
60 if layer.trusted {
60 if layer.trusted {
61 &b"yes"[..]
61 &b"yes"[..]
62 } else {
62 } else {
63 &b"no"[..]
63 &b"no"[..]
64 },
64 },
65 layer
65 layer
66 )?;
66 )?;
67 }
67 }
68 Ok(())
68 Ok(())
69 }
69 }
70 }
70 }
71
71
72 pub enum ConfigSource {
72 pub enum ConfigSource {
73 /// Absolute path to a config file
73 /// Absolute path to a config file
74 AbsPath(PathBuf),
74 AbsPath(PathBuf),
75 /// Already parsed (from the CLI, env, Python resources, etc.)
75 /// Already parsed (from the CLI, env, Python resources, etc.)
76 Parsed(layer::ConfigLayer),
76 Parsed(layer::ConfigLayer),
77 }
77 }
78
78
79 #[derive(Debug)]
79 #[derive(Debug)]
80 pub struct ConfigValueParseErrorDetails {
80 pub struct ConfigValueParseErrorDetails {
81 pub origin: ConfigOrigin,
81 pub origin: ConfigOrigin,
82 pub line: Option<usize>,
82 pub line: Option<usize>,
83 pub section: Vec<u8>,
83 pub section: Vec<u8>,
84 pub item: Vec<u8>,
84 pub item: Vec<u8>,
85 pub value: Vec<u8>,
85 pub value: Vec<u8>,
86 pub expected_type: &'static str,
86 pub expected_type: &'static str,
87 }
87 }
88
88
89 // boxed to avoid very large Result types
89 // boxed to avoid very large Result types
90 pub type ConfigValueParseError = Box<ConfigValueParseErrorDetails>;
90 pub type ConfigValueParseError = Box<ConfigValueParseErrorDetails>;
91
91
92 impl fmt::Display for ConfigValueParseError {
92 impl fmt::Display for ConfigValueParseError {
93 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
93 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
94 // TODO: add origin and line number information, here and in
94 // TODO: add origin and line number information, here and in
95 // corresponding python code
95 // corresponding python code
96 write!(
96 write!(
97 f,
97 f,
98 "config error: {}.{} is not a {} ('{}')",
98 "config error: {}.{} is not a {} ('{}')",
99 String::from_utf8_lossy(&self.section),
99 String::from_utf8_lossy(&self.section),
100 String::from_utf8_lossy(&self.item),
100 String::from_utf8_lossy(&self.item),
101 self.expected_type,
101 self.expected_type,
102 String::from_utf8_lossy(&self.value)
102 String::from_utf8_lossy(&self.value)
103 )
103 )
104 }
104 }
105 }
105 }
106
106
107 /// Returns true if the config item is disabled by PLAIN or PLAINEXCEPT
107 /// Returns true if the config item is disabled by PLAIN or PLAINEXCEPT
108 fn should_ignore(plain: &PlainInfo, section: &[u8], item: &[u8]) -> bool {
108 fn should_ignore(plain: &PlainInfo, section: &[u8], item: &[u8]) -> bool {
109 // duplication with [_applyconfig] in [ui.py],
109 // duplication with [_applyconfig] in [ui.py],
110 if !plain.is_plain() {
110 if !plain.is_plain() {
111 return false;
111 return false;
112 }
112 }
113 if section == b"alias" {
113 if section == b"alias" {
114 return plain.plainalias();
114 return plain.plainalias();
115 }
115 }
116 if section == b"revsetalias" {
116 if section == b"revsetalias" {
117 return plain.plainrevsetalias();
117 return plain.plainrevsetalias();
118 }
118 }
119 if section == b"templatealias" {
119 if section == b"templatealias" {
120 return plain.plaintemplatealias();
120 return plain.plaintemplatealias();
121 }
121 }
122 if section == b"ui" {
122 if section == b"ui" {
123 let to_delete: &[&[u8]] = &[
123 let to_delete: &[&[u8]] = &[
124 b"debug",
124 b"debug",
125 b"fallbackencoding",
125 b"fallbackencoding",
126 b"quiet",
126 b"quiet",
127 b"slash",
127 b"slash",
128 b"logtemplate",
128 b"logtemplate",
129 b"message-output",
129 b"message-output",
130 b"statuscopies",
130 b"statuscopies",
131 b"style",
131 b"style",
132 b"traceback",
132 b"traceback",
133 b"verbose",
133 b"verbose",
134 ];
134 ];
135 return to_delete.contains(&item);
135 return to_delete.contains(&item);
136 }
136 }
137 let sections_to_delete: &[&[u8]] =
137 let sections_to_delete: &[&[u8]] =
138 &[b"defaults", b"commands", b"command-templates"];
138 &[b"defaults", b"commands", b"command-templates"];
139 sections_to_delete.contains(&section)
139 sections_to_delete.contains(&section)
140 }
140 }
141
141
142 impl Config {
142 impl Config {
143 /// The configuration to use when printing configuration-loading errors
143 /// The configuration to use when printing configuration-loading errors
144 pub fn empty() -> Self {
144 pub fn empty() -> Self {
145 Self {
145 Self {
146 layers: Vec::new(),
146 layers: Vec::new(),
147 plain: PlainInfo::empty(),
147 plain: PlainInfo::empty(),
148 }
148 }
149 }
149 }
150
150
151 /// Load system and user configuration from various files.
151 /// Load system and user configuration from various files.
152 ///
152 ///
153 /// This is also affected by some environment variables.
153 /// This is also affected by some environment variables.
154 pub fn load_non_repo() -> Result<Self, ConfigError> {
154 pub fn load_non_repo() -> Result<Self, ConfigError> {
155 let mut config = Self::empty();
155 let mut config = Self::empty();
156 let opt_rc_path = env::var_os("HGRCPATH");
156 let opt_rc_path = env::var_os("HGRCPATH");
157 // HGRCPATH replaces system config
157 // HGRCPATH replaces system config
158 if opt_rc_path.is_none() {
158 if opt_rc_path.is_none() {
159 config.add_system_config()?
159 config.add_system_config()?
160 }
160 }
161
161
162 config.add_for_environment_variable("EDITOR", b"ui", b"editor");
162 config.add_for_environment_variable("EDITOR", b"ui", b"editor");
163 config.add_for_environment_variable("VISUAL", b"ui", b"editor");
163 config.add_for_environment_variable("VISUAL", b"ui", b"editor");
164 config.add_for_environment_variable("PAGER", b"pager", b"pager");
164 config.add_for_environment_variable("PAGER", b"pager", b"pager");
165
165
166 // These are set by `run-tests.py --rhg` to enable fallback for the
166 // These are set by `run-tests.py --rhg` to enable fallback for the
167 // entire test suite. Alternatives would be setting configuration
167 // entire test suite. Alternatives would be setting configuration
168 // through `$HGRCPATH` but some tests override that, or changing the
168 // through `$HGRCPATH` but some tests override that, or changing the
169 // `hg` shell alias to include `--config` but that disrupts tests that
169 // `hg` shell alias to include `--config` but that disrupts tests that
170 // print command lines and check expected output.
170 // print command lines and check expected output.
171 config.add_for_environment_variable(
171 config.add_for_environment_variable(
172 "RHG_ON_UNSUPPORTED",
172 "RHG_ON_UNSUPPORTED",
173 b"rhg",
173 b"rhg",
174 b"on-unsupported",
174 b"on-unsupported",
175 );
175 );
176 config.add_for_environment_variable(
176 config.add_for_environment_variable(
177 "RHG_FALLBACK_EXECUTABLE",
177 "RHG_FALLBACK_EXECUTABLE",
178 b"rhg",
178 b"rhg",
179 b"fallback-executable",
179 b"fallback-executable",
180 );
180 );
181
181
182 // HGRCPATH replaces user config
182 // HGRCPATH replaces user config
183 if opt_rc_path.is_none() {
183 if opt_rc_path.is_none() {
184 config.add_user_config()?
184 config.add_user_config()?
185 }
185 }
186 if let Some(rc_path) = &opt_rc_path {
186 if let Some(rc_path) = &opt_rc_path {
187 for path in env::split_paths(rc_path) {
187 for path in env::split_paths(rc_path) {
188 if !path.as_os_str().is_empty() {
188 if !path.as_os_str().is_empty() {
189 if path.is_dir() {
189 if path.is_dir() {
190 config.add_trusted_dir(&path)?
190 config.add_trusted_dir(&path)?
191 } else {
191 } else {
192 config.add_trusted_file(&path)?
192 config.add_trusted_file(&path)?
193 }
193 }
194 }
194 }
195 }
195 }
196 }
196 }
197 Ok(config)
197 Ok(config)
198 }
198 }
199
199
200 pub fn load_cli_args(
200 pub fn load_cli_args(
201 &mut self,
201 &mut self,
202 cli_config_args: impl IntoIterator<Item = impl AsRef<[u8]>>,
202 cli_config_args: impl IntoIterator<Item = impl AsRef<[u8]>>,
203 color_arg: Option<Vec<u8>>,
203 color_arg: Option<Vec<u8>>,
204 ) -> Result<(), ConfigError> {
204 ) -> Result<(), ConfigError> {
205 if let Some(layer) = ConfigLayer::parse_cli_args(cli_config_args)? {
205 if let Some(layer) = ConfigLayer::parse_cli_args(cli_config_args)? {
206 self.layers.push(layer)
206 self.layers.push(layer)
207 }
207 }
208 if let Some(arg) = color_arg {
208 if let Some(arg) = color_arg {
209 let mut layer = ConfigLayer::new(ConfigOrigin::CommandLineColor);
209 let mut layer = ConfigLayer::new(ConfigOrigin::CommandLineColor);
210 layer.add(b"ui"[..].into(), b"color"[..].into(), arg, None);
210 layer.add(b"ui"[..].into(), b"color"[..].into(), arg, None);
211 self.layers.push(layer)
211 self.layers.push(layer)
212 }
212 }
213 Ok(())
213 Ok(())
214 }
214 }
215
215
216 fn add_trusted_dir(&mut self, path: &Path) -> Result<(), ConfigError> {
216 fn add_trusted_dir(&mut self, path: &Path) -> Result<(), ConfigError> {
217 if let Some(entries) = std::fs::read_dir(path)
217 if let Some(entries) = std::fs::read_dir(path)
218 .when_reading_file(path)
218 .when_reading_file(path)
219 .io_not_found_as_none()?
219 .io_not_found_as_none()?
220 {
220 {
221 let mut file_paths = entries
221 let mut file_paths = entries
222 .map(|result| {
222 .map(|result| {
223 result.when_reading_file(path).map(|entry| entry.path())
223 result.when_reading_file(path).map(|entry| entry.path())
224 })
224 })
225 .collect::<Result<Vec<_>, _>>()?;
225 .collect::<Result<Vec<_>, _>>()?;
226 file_paths.sort();
226 file_paths.sort();
227 for file_path in &file_paths {
227 for file_path in &file_paths {
228 if file_path.extension() == Some(std::ffi::OsStr::new("rc")) {
228 if file_path.extension() == Some(std::ffi::OsStr::new("rc")) {
229 self.add_trusted_file(file_path)?
229 self.add_trusted_file(file_path)?
230 }
230 }
231 }
231 }
232 }
232 }
233 Ok(())
233 Ok(())
234 }
234 }
235
235
236 fn add_trusted_file(&mut self, path: &Path) -> Result<(), ConfigError> {
236 fn add_trusted_file(&mut self, path: &Path) -> Result<(), ConfigError> {
237 if let Some(data) = std::fs::read(path)
237 if let Some(data) = std::fs::read(path)
238 .when_reading_file(path)
238 .when_reading_file(path)
239 .io_not_found_as_none()?
239 .io_not_found_as_none()?
240 {
240 {
241 self.layers.extend(ConfigLayer::parse(path, &data)?)
241 self.layers.extend(ConfigLayer::parse(path, &data)?)
242 }
242 }
243 Ok(())
243 Ok(())
244 }
244 }
245
245
246 fn add_for_environment_variable(
246 fn add_for_environment_variable(
247 &mut self,
247 &mut self,
248 var: &str,
248 var: &str,
249 section: &[u8],
249 section: &[u8],
250 key: &[u8],
250 key: &[u8],
251 ) {
251 ) {
252 if let Some(value) = env::var_os(var) {
252 if let Some(value) = env::var_os(var) {
253 let origin = layer::ConfigOrigin::Environment(var.into());
253 let origin = layer::ConfigOrigin::Environment(var.into());
254 let mut layer = ConfigLayer::new(origin);
254 let mut layer = ConfigLayer::new(origin);
255 layer.add(
255 layer.add(
256 section.to_owned(),
256 section.to_owned(),
257 key.to_owned(),
257 key.to_owned(),
258 get_bytes_from_os_str(value),
258 get_bytes_from_os_str(value),
259 None,
259 None,
260 );
260 );
261 self.layers.push(layer)
261 self.layers.push(layer)
262 }
262 }
263 }
263 }
264
264
265 #[cfg(unix)] // TODO: other platforms
265 #[cfg(unix)] // TODO: other platforms
266 fn add_system_config(&mut self) -> Result<(), ConfigError> {
266 fn add_system_config(&mut self) -> Result<(), ConfigError> {
267 let mut add_for_prefix = |prefix: &Path| -> Result<(), ConfigError> {
267 let mut add_for_prefix = |prefix: &Path| -> Result<(), ConfigError> {
268 let etc = prefix.join("etc").join("mercurial");
268 let etc = prefix.join("etc").join("mercurial");
269 self.add_trusted_file(&etc.join("hgrc"))?;
269 self.add_trusted_file(&etc.join("hgrc"))?;
270 self.add_trusted_dir(&etc.join("hgrc.d"))
270 self.add_trusted_dir(&etc.join("hgrc.d"))
271 };
271 };
272 let root = Path::new("/");
272 let root = Path::new("/");
273 // TODO: use `std::env::args_os().next().unwrap()` a.k.a. argv[0]
273 // TODO: use `std::env::args_os().next().unwrap()` a.k.a. argv[0]
274 // instead? TODO: can this be a relative path?
274 // instead? TODO: can this be a relative path?
275 let hg = crate::utils::current_exe()?;
275 let hg = crate::utils::current_exe()?;
276 // TODO: this order (per-installation then per-system) matches
276 // TODO: this order (per-installation then per-system) matches
277 // `systemrcpath()` in `mercurial/scmposix.py`, but
277 // `systemrcpath()` in `mercurial/scmposix.py`, but
278 // `mercurial/helptext/config.txt` suggests it should be reversed
278 // `mercurial/helptext/config.txt` suggests it should be reversed
279 if let Some(installation_prefix) = hg.parent().and_then(Path::parent) {
279 if let Some(installation_prefix) = hg.parent().and_then(Path::parent) {
280 if installation_prefix != root {
280 if installation_prefix != root {
281 add_for_prefix(installation_prefix)?
281 add_for_prefix(installation_prefix)?
282 }
282 }
283 }
283 }
284 add_for_prefix(root)?;
284 add_for_prefix(root)?;
285 Ok(())
285 Ok(())
286 }
286 }
287
287
288 #[cfg(unix)] // TODO: other plateforms
288 #[cfg(unix)] // TODO: other plateforms
289 fn add_user_config(&mut self) -> Result<(), ConfigError> {
289 fn add_user_config(&mut self) -> Result<(), ConfigError> {
290 let opt_home = home::home_dir();
290 let opt_home = home::home_dir();
291 if let Some(home) = &opt_home {
291 if let Some(home) = &opt_home {
292 self.add_trusted_file(&home.join(".hgrc"))?
292 self.add_trusted_file(&home.join(".hgrc"))?
293 }
293 }
294 let darwin = cfg!(any(target_os = "macos", target_os = "ios"));
294 let darwin = cfg!(any(target_os = "macos", target_os = "ios"));
295 if !darwin {
295 if !darwin {
296 if let Some(config_home) = env::var_os("XDG_CONFIG_HOME")
296 if let Some(config_home) = env::var_os("XDG_CONFIG_HOME")
297 .map(PathBuf::from)
297 .map(PathBuf::from)
298 .or_else(|| opt_home.map(|home| home.join(".config")))
298 .or_else(|| opt_home.map(|home| home.join(".config")))
299 {
299 {
300 self.add_trusted_file(&config_home.join("hg").join("hgrc"))?
300 self.add_trusted_file(&config_home.join("hg").join("hgrc"))?
301 }
301 }
302 }
302 }
303 Ok(())
303 Ok(())
304 }
304 }
305
305
306 /// Loads in order, which means that the precedence is the same
306 /// Loads in order, which means that the precedence is the same
307 /// as the order of `sources`.
307 /// as the order of `sources`.
308 pub fn load_from_explicit_sources(
308 pub fn load_from_explicit_sources(
309 sources: Vec<ConfigSource>,
309 sources: Vec<ConfigSource>,
310 ) -> Result<Self, ConfigError> {
310 ) -> Result<Self, ConfigError> {
311 let mut layers = vec![];
311 let mut layers = vec![];
312
312
313 for source in sources.into_iter() {
313 for source in sources.into_iter() {
314 match source {
314 match source {
315 ConfigSource::Parsed(c) => layers.push(c),
315 ConfigSource::Parsed(c) => layers.push(c),
316 ConfigSource::AbsPath(c) => {
316 ConfigSource::AbsPath(c) => {
317 // TODO check if it should be trusted
317 // TODO check if it should be trusted
318 // mercurial/ui.py:427
318 // mercurial/ui.py:427
319 let data = match std::fs::read(&c) {
319 let data = match std::fs::read(&c) {
320 Err(_) => continue, // same as the python code
320 Err(_) => continue, // same as the python code
321 Ok(data) => data,
321 Ok(data) => data,
322 };
322 };
323 layers.extend(ConfigLayer::parse(&c, &data)?)
323 layers.extend(ConfigLayer::parse(&c, &data)?)
324 }
324 }
325 }
325 }
326 }
326 }
327
327
328 Ok(Config {
328 Ok(Config {
329 layers,
329 layers,
330 plain: PlainInfo::empty(),
330 plain: PlainInfo::empty(),
331 })
331 })
332 }
332 }
333
333
334 /// Loads the per-repository config into a new `Config` which is combined
334 /// Loads the per-repository config into a new `Config` which is combined
335 /// with `self`.
335 /// with `self`.
336 pub(crate) fn combine_with_repo(
336 pub(crate) fn combine_with_repo(
337 &self,
337 &self,
338 repo_config_files: &[PathBuf],
338 repo_config_files: &[PathBuf],
339 ) -> Result<Self, ConfigError> {
339 ) -> Result<Self, ConfigError> {
340 let (cli_layers, other_layers) = self
340 let (cli_layers, other_layers) = self
341 .layers
341 .layers
342 .iter()
342 .iter()
343 .cloned()
343 .cloned()
344 .partition(ConfigLayer::is_from_command_line);
344 .partition(ConfigLayer::is_from_command_line);
345
345
346 let mut repo_config = Self {
346 let mut repo_config = Self {
347 layers: other_layers,
347 layers: other_layers,
348 plain: PlainInfo::empty(),
348 plain: PlainInfo::empty(),
349 };
349 };
350 for path in repo_config_files {
350 for path in repo_config_files {
351 // TODO: check if this file should be trusted:
351 // TODO: check if this file should be trusted:
352 // `mercurial/ui.py:427`
352 // `mercurial/ui.py:427`
353 repo_config.add_trusted_file(path)?;
353 repo_config.add_trusted_file(path)?;
354 }
354 }
355 repo_config.layers.extend(cli_layers);
355 repo_config.layers.extend(cli_layers);
356 Ok(repo_config)
356 Ok(repo_config)
357 }
357 }
358
358
359 pub fn apply_plain(&mut self, plain: PlainInfo) {
359 pub fn apply_plain(&mut self, plain: PlainInfo) {
360 self.plain = plain;
360 self.plain = plain;
361 }
361 }
362
362
363 /// Returns the default value for the given config item, if any.
363 /// Returns the default value for the given config item, if any.
364 pub fn get_default(
364 pub fn get_default(
365 &self,
365 &self,
366 section: &[u8],
366 section: &[u8],
367 item: &[u8],
367 item: &[u8],
368 ) -> Result<Option<&DefaultConfigItem>, HgError> {
368 ) -> Result<Option<&DefaultConfigItem>, HgError> {
369 let default_config = DEFAULT_CONFIG.as_ref().map_err(|e| {
369 let default_config = DEFAULT_CONFIG.as_ref().map_err(|e| {
370 HgError::abort(
370 HgError::abort(
371 e.to_string(),
371 e.to_string(),
372 crate::exit_codes::ABORT,
372 crate::exit_codes::ABORT,
373 Some("`mercurial/configitems.toml` is not valid".into()),
373 Some("`mercurial/configitems.toml` is not valid".into()),
374 )
374 )
375 })?;
375 })?;
376 let default_opt = default_config.get(section, item);
376 let default_opt = default_config.get(section, item);
377 Ok(default_opt.filter(|default| {
377 Ok(default_opt.filter(|default| {
378 default
378 default
379 .in_core_extension()
379 .in_core_extension()
380 .map(|extension| {
380 .map(|extension| {
381 // Only return the default for an in-core extension item
381 // Only return the default for an in-core extension item
382 // if said extension is enabled
382 // if said extension is enabled
383 self.is_extension_enabled(extension.as_bytes())
383 self.is_extension_enabled(extension.as_bytes())
384 })
384 })
385 .unwrap_or(true)
385 .unwrap_or(true)
386 }))
386 }))
387 }
387 }
388
388
389 /// Return the config item that corresponds to a section + item, a function
389 /// Return the config item that corresponds to a section + item, a function
390 /// to parse from the raw bytes to the expected type (which is passed as
390 /// to parse from the raw bytes to the expected type (which is passed as
391 /// a string only to make debugging easier).
391 /// a string only to make debugging easier).
392 /// Used by higher-level methods like `get_bool`.
392 /// Used by higher-level methods like `get_bool`.
393 ///
393 ///
394 /// `fallback_to_default` controls whether the default value (if any) is
394 /// `fallback_to_default` controls whether the default value (if any) is
395 /// returned if nothing is found.
395 /// returned if nothing is found.
396 fn get_parse<'config, T: 'config>(
396 fn get_parse<'config, T: 'config>(
397 &'config self,
397 &'config self,
398 section: &[u8],
398 section: &[u8],
399 item: &[u8],
399 item: &[u8],
400 expected_type: &'static str,
400 expected_type: &'static str,
401 parse: impl Fn(&'config [u8]) -> Option<T>,
401 parse: impl Fn(&'config [u8]) -> Option<T>,
402 fallback_to_default: bool,
402 fallback_to_default: bool,
403 ) -> Result<Option<T>, HgError>
403 ) -> Result<Option<T>, HgError>
404 where
404 where
405 Option<T>: TryFrom<&'config DefaultConfigItem, Error = HgError>,
405 Option<T>: TryFrom<&'config DefaultConfigItem, Error = HgError>,
406 {
406 {
407 match self.get_inner(section, item) {
407 match self.get_inner(section, item) {
408 Some((layer, v)) => match parse(&v.bytes) {
408 Some((layer, v)) => match parse(&v.bytes) {
409 Some(b) => Ok(Some(b)),
409 Some(b) => Ok(Some(b)),
410 None => Err(Box::new(ConfigValueParseErrorDetails {
410 None => Err(Box::new(ConfigValueParseErrorDetails {
411 origin: layer.origin.to_owned(),
411 origin: layer.origin.to_owned(),
412 line: v.line,
412 line: v.line,
413 value: v.bytes.to_owned(),
413 value: v.bytes.to_owned(),
414 section: section.to_owned(),
414 section: section.to_owned(),
415 item: item.to_owned(),
415 item: item.to_owned(),
416 expected_type,
416 expected_type,
417 })
417 })
418 .into()),
418 .into()),
419 },
419 },
420 None => {
420 None => {
421 if !fallback_to_default {
421 if !fallback_to_default {
422 return Ok(None);
422 return Ok(None);
423 }
423 }
424 match self.get_default(section, item)? {
424 match self.get_default(section, item)? {
425 Some(default) => {
425 Some(default) => {
426 // Defaults are TOML values, so they're not in the same
426 // Defaults are TOML values, so they're not in the same
427 // shape as in the config files.
427 // shape as in the config files.
428 // First try to convert directly to the expected type
428 // First try to convert directly to the expected type
429 let as_t = default.try_into();
429 let as_t = default.try_into();
430 match as_t {
430 match as_t {
431 Ok(t) => Ok(t),
431 Ok(t) => Ok(t),
432 Err(e) => {
432 Err(e) => {
433 // If it fails, it means that...
433 // If it fails, it means that...
434 let as_bytes: Result<Option<&[u8]>, _> =
434 let as_bytes: Result<Option<&[u8]>, _> =
435 default.try_into();
435 default.try_into();
436 match as_bytes {
436 match as_bytes {
437 Ok(bytes_opt) => {
437 Ok(bytes_opt) => {
438 if let Some(bytes) = bytes_opt {
438 if let Some(bytes) = bytes_opt {
439 // ...we should be able to parse it
439 // ...we should be able to parse it
440 return Ok(parse(bytes));
440 return Ok(parse(bytes));
441 }
441 }
442 Err(e)
442 Err(e)
443 }
443 }
444 Err(_) => Err(e),
444 Err(_) => Err(e),
445 }
445 }
446 }
446 }
447 }
447 }
448 }
448 }
449 None => {
449 None => {
450 self.print_devel_warning(section, item)?;
450 self.print_devel_warning(section, item)?;
451 Ok(None)
451 Ok(None)
452 }
452 }
453 }
453 }
454 }
454 }
455 }
455 }
456 }
456 }
457
457
458 fn print_devel_warning(
458 fn print_devel_warning(
459 &self,
459 &self,
460 section: &[u8],
460 section: &[u8],
461 item: &[u8],
461 item: &[u8],
462 ) -> Result<(), HgError> {
462 ) -> Result<(), HgError> {
463 let warn_all = self.get_bool(b"devel", b"all-warnings")?;
463 let warn_all = self.get_bool(b"devel", b"all-warnings")?;
464 let warn_specific = self.get_bool(b"devel", b"warn-config-unknown")?;
464 let warn_specific = self.get_bool(b"devel", b"warn-config-unknown")?;
465 if !warn_all || !warn_specific {
465 if !warn_all || !warn_specific {
466 // We technically shouldn't print anything here since it's not
466 // We technically shouldn't print anything here since it's not
467 // the concern of `hg-core`.
467 // the concern of `hg-core`.
468 //
468 //
469 // We're printing directly to stderr since development warnings
469 // We're printing directly to stderr since development warnings
470 // are not on by default and surfacing this to consumer crates
470 // are not on by default and surfacing this to consumer crates
471 // (like `rhg`) would be more difficult, probably requiring
471 // (like `rhg`) would be more difficult, probably requiring
472 // something à la `log` crate.
472 // something à la `log` crate.
473 //
473 //
474 // TODO maybe figure out a way of exposing a "warnings" channel
474 // TODO maybe figure out a way of exposing a "warnings" channel
475 // that consumer crates can hook into. It would be useful for
475 // that consumer crates can hook into. It would be useful for
476 // all other warnings that `hg-core` could expose.
476 // all other warnings that `hg-core` could expose.
477 eprintln!(
477 eprintln!(
478 "devel-warn: accessing unregistered config item: '{}.{}'",
478 "devel-warn: accessing unregistered config item: '{}.{}'",
479 String::from_utf8_lossy(section),
479 String::from_utf8_lossy(section),
480 String::from_utf8_lossy(item),
480 String::from_utf8_lossy(item),
481 );
481 );
482 }
482 }
483 Ok(())
483 Ok(())
484 }
484 }
485
485
486 /// Returns an `Err` if the first value found is not a valid UTF-8 string.
486 /// Returns an `Err` if the first value found is not a valid UTF-8 string.
487 /// Otherwise, returns an `Ok(value)` if found, or `None`.
487 /// Otherwise, returns an `Ok(value)` if found, or `None`.
488 pub fn get_str(
488 pub fn get_str(
489 &self,
489 &self,
490 section: &[u8],
490 section: &[u8],
491 item: &[u8],
491 item: &[u8],
492 ) -> Result<Option<&str>, HgError> {
492 ) -> Result<Option<&str>, HgError> {
493 self.get_parse(
493 self.get_parse(
494 section,
494 section,
495 item,
495 item,
496 "ASCII or UTF-8 string",
496 "ASCII or UTF-8 string",
497 |value| str::from_utf8(value).ok(),
497 |value| str::from_utf8(value).ok(),
498 true,
498 true,
499 )
499 )
500 }
500 }
501
501
502 /// Same as `get_str`, but doesn't fall back to the default `configitem`
502 /// Same as `get_str`, but doesn't fall back to the default `configitem`
503 /// if not defined in the user config.
503 /// if not defined in the user config.
504 pub fn get_str_no_default(
504 pub fn get_str_no_default(
505 &self,
505 &self,
506 section: &[u8],
506 section: &[u8],
507 item: &[u8],
507 item: &[u8],
508 ) -> Result<Option<&str>, HgError> {
508 ) -> Result<Option<&str>, HgError> {
509 self.get_parse(
509 self.get_parse(
510 section,
510 section,
511 item,
511 item,
512 "ASCII or UTF-8 string",
512 "ASCII or UTF-8 string",
513 |value| str::from_utf8(value).ok(),
513 |value| str::from_utf8(value).ok(),
514 false,
514 false,
515 )
515 )
516 }
516 }
517
517
518 /// Returns an `Err` if the first value found is not a valid unsigned
518 /// Returns an `Err` if the first value found is not a valid unsigned
519 /// integer. Otherwise, returns an `Ok(value)` if found, or `None`.
519 /// integer. Otherwise, returns an `Ok(value)` if found, or `None`.
520 pub fn get_u32(
520 pub fn get_u32(
521 &self,
521 &self,
522 section: &[u8],
522 section: &[u8],
523 item: &[u8],
523 item: &[u8],
524 ) -> Result<Option<u32>, HgError> {
524 ) -> Result<Option<u32>, HgError> {
525 self.get_parse(
525 self.get_parse(
526 section,
526 section,
527 item,
527 item,
528 "valid integer",
528 "valid integer",
529 |value| str::from_utf8(value).ok()?.parse().ok(),
529 |value| str::from_utf8(value).ok()?.parse().ok(),
530 true,
530 true,
531 )
531 )
532 }
532 }
533
533
534 /// Returns an `Err` if the first value found is not a valid file size
534 /// Returns an `Err` if the first value found is not a valid file size
535 /// value such as `30` (default unit is bytes), `7 MB`, or `42.5 kb`.
535 /// value such as `30` (default unit is bytes), `7 MB`, or `42.5 kb`.
536 /// Otherwise, returns an `Ok(value_in_bytes)` if found, or `None`.
536 /// Otherwise, returns an `Ok(value_in_bytes)` if found, or `None`.
537 pub fn get_byte_size(
537 pub fn get_byte_size(
538 &self,
538 &self,
539 section: &[u8],
539 section: &[u8],
540 item: &[u8],
540 item: &[u8],
541 ) -> Result<Option<u64>, HgError> {
541 ) -> Result<Option<u64>, HgError> {
542 self.get_parse(
542 self.get_parse(
543 section,
543 section,
544 item,
544 item,
545 "byte quantity",
545 "byte quantity",
546 values::parse_byte_size,
546 values::parse_byte_size,
547 true,
547 true,
548 )
548 )
549 }
549 }
550
550
551 /// Returns an `Err` if the first value found is not a valid boolean.
551 /// Returns an `Err` if the first value found is not a valid boolean.
552 /// Otherwise, returns an `Ok(option)`, where `option` is the boolean if
552 /// Otherwise, returns an `Ok(option)`, where `option` is the boolean if
553 /// found, or `None`.
553 /// found, or `None`.
554 pub fn get_option(
554 pub fn get_option(
555 &self,
555 &self,
556 section: &[u8],
556 section: &[u8],
557 item: &[u8],
557 item: &[u8],
558 ) -> Result<Option<bool>, HgError> {
558 ) -> Result<Option<bool>, HgError> {
559 self.get_parse(section, item, "boolean", values::parse_bool, true)
559 self.get_parse(section, item, "boolean", values::parse_bool, true)
560 }
560 }
561
561
562 /// Same as `get_option`, but doesn't fall back to the default `configitem`
562 /// Same as `get_option`, but doesn't fall back to the default `configitem`
563 /// if not defined in the user config.
563 /// if not defined in the user config.
564 pub fn get_option_no_default(
564 pub fn get_option_no_default(
565 &self,
565 &self,
566 section: &[u8],
566 section: &[u8],
567 item: &[u8],
567 item: &[u8],
568 ) -> Result<Option<bool>, HgError> {
568 ) -> Result<Option<bool>, HgError> {
569 self.get_parse(section, item, "boolean", values::parse_bool, false)
569 self.get_parse(section, item, "boolean", values::parse_bool, false)
570 }
570 }
571
571
572 /// Returns the corresponding boolean in the config. Returns `Ok(false)`
572 /// Returns the corresponding boolean in the config. Returns `Ok(false)`
573 /// if the value is not found, an `Err` if it's not a valid boolean.
573 /// if the value is not found, an `Err` if it's not a valid boolean.
574 pub fn get_bool(
574 pub fn get_bool(
575 &self,
575 &self,
576 section: &[u8],
576 section: &[u8],
577 item: &[u8],
577 item: &[u8],
578 ) -> Result<bool, HgError> {
578 ) -> Result<bool, HgError> {
579 Ok(self.get_option(section, item)?.unwrap_or(false))
579 Ok(self.get_option(section, item)?.unwrap_or(false))
580 }
580 }
581
581
582 /// Same as `get_bool`, but doesn't fall back to the default `configitem`
582 /// Same as `get_bool`, but doesn't fall back to the default `configitem`
583 /// if not defined in the user config.
583 /// if not defined in the user config.
584 pub fn get_bool_no_default(
584 pub fn get_bool_no_default(
585 &self,
585 &self,
586 section: &[u8],
586 section: &[u8],
587 item: &[u8],
587 item: &[u8],
588 ) -> Result<bool, HgError> {
588 ) -> Result<bool, HgError> {
589 Ok(self.get_option_no_default(section, item)?.unwrap_or(false))
589 Ok(self.get_option_no_default(section, item)?.unwrap_or(false))
590 }
590 }
591
591
592 /// Returns `true` if the extension is enabled, `false` otherwise
592 /// Returns `true` if the extension is enabled, `false` otherwise
593 pub fn is_extension_enabled(&self, extension: &[u8]) -> bool {
593 pub fn is_extension_enabled(&self, extension: &[u8]) -> bool {
594 let value = self.get(b"extensions", extension);
594 let value = self.get(b"extensions", extension);
595 match value {
595 match value {
596 Some(c) => !c.starts_with(b"!"),
596 Some(c) => !c.starts_with(b"!"),
597 None => false,
597 None => false,
598 }
598 }
599 }
599 }
600
600
601 /// If there is an `item` value in `section`, parse and return a list of
601 /// If there is an `item` value in `section`, parse and return a list of
602 /// byte strings.
602 /// byte strings.
603 pub fn get_list(
603 pub fn get_list(
604 &self,
604 &self,
605 section: &[u8],
605 section: &[u8],
606 item: &[u8],
606 item: &[u8],
607 ) -> Option<Vec<Vec<u8>>> {
607 ) -> Option<Vec<Vec<u8>>> {
608 self.get(section, item).map(values::parse_list)
608 self.get(section, item).map(values::parse_list)
609 }
609 }
610
610
611 /// Returns the raw value bytes of the first one found, or `None`.
611 /// Returns the raw value bytes of the first one found, or `None`.
612 pub fn get(&self, section: &[u8], item: &[u8]) -> Option<&[u8]> {
612 pub fn get(&self, section: &[u8], item: &[u8]) -> Option<&[u8]> {
613 self.get_inner(section, item)
613 self.get_inner(section, item)
614 .map(|(_, value)| value.bytes.as_ref())
614 .map(|(_, value)| value.bytes.as_ref())
615 }
615 }
616
616
617 /// Returns the raw value bytes of the first one found, or `None`.
617 /// Returns the raw value bytes of the first one found, or `None`.
618 pub fn get_with_origin(
618 pub fn get_with_origin(
619 &self,
619 &self,
620 section: &[u8],
620 section: &[u8],
621 item: &[u8],
621 item: &[u8],
622 ) -> Option<(&[u8], &ConfigOrigin)> {
622 ) -> Option<(&[u8], &ConfigOrigin)> {
623 self.get_inner(section, item)
623 self.get_inner(section, item)
624 .map(|(layer, value)| (value.bytes.as_ref(), &layer.origin))
624 .map(|(layer, value)| (value.bytes.as_ref(), &layer.origin))
625 }
625 }
626
626
627 /// Returns the layer and the value of the first one found, or `None`.
627 /// Returns the layer and the value of the first one found, or `None`.
628 fn get_inner(
628 fn get_inner(
629 &self,
629 &self,
630 section: &[u8],
630 section: &[u8],
631 item: &[u8],
631 item: &[u8],
632 ) -> Option<(&ConfigLayer, &ConfigValue)> {
632 ) -> Option<(&ConfigLayer, &ConfigValue)> {
633 // Filter out the config items that are hidden by [PLAIN].
633 // Filter out the config items that are hidden by [PLAIN].
634 // This differs from python hg where we delete them from the config.
634 // This differs from python hg where we delete them from the config.
635 let should_ignore = should_ignore(&self.plain, section, item);
635 let should_ignore = should_ignore(&self.plain, section, item);
636 for layer in self.layers.iter().rev() {
636 for layer in self.layers.iter().rev() {
637 if !layer.trusted {
637 if !layer.trusted {
638 continue;
638 continue;
639 }
639 }
640 //The [PLAIN] config should not affect the defaults.
640 //The [PLAIN] config should not affect the defaults.
641 //
641 //
642 // However, PLAIN should also affect the "tweaked" defaults (unless
642 // However, PLAIN should also affect the "tweaked" defaults (unless
643 // "tweakdefault" is part of "HGPLAINEXCEPT").
643 // "tweakdefault" is part of "HGPLAINEXCEPT").
644 //
644 //
645 // In practice the tweak-default layer is only added when it is
645 // In practice the tweak-default layer is only added when it is
646 // relevant, so we can safely always take it into
646 // relevant, so we can safely always take it into
647 // account here.
647 // account here.
648 if should_ignore && !(layer.origin == ConfigOrigin::Tweakdefaults)
648 if should_ignore && !(layer.origin == ConfigOrigin::Tweakdefaults)
649 {
649 {
650 continue;
650 continue;
651 }
651 }
652 if let Some(v) = layer.get(section, item) {
652 if let Some(v) = layer.get(section, item) {
653 return Some((layer, v));
653 return Some((layer, v));
654 }
654 }
655 }
655 }
656 None
656 None
657 }
657 }
658
658
659 /// Return all keys defined for the given section
659 /// Return all keys defined for the given section
660 pub fn get_section_keys(&self, section: &[u8]) -> HashSet<&[u8]> {
660 pub fn get_section_keys(&self, section: &[u8]) -> HashSet<&[u8]> {
661 self.layers
661 self.layers
662 .iter()
662 .iter()
663 .flat_map(|layer| layer.iter_keys(section))
663 .flat_map(|layer| layer.iter_keys(section))
664 .collect()
664 .collect()
665 }
665 }
666
666
667 /// Returns whether any key is defined in the given section
667 /// Returns whether any key is defined in the given section
668 pub fn has_non_empty_section(&self, section: &[u8]) -> bool {
668 pub fn has_non_empty_section(&self, section: &[u8]) -> bool {
669 self.layers
669 self.layers
670 .iter()
670 .iter()
671 .any(|layer| layer.has_non_empty_section(section))
671 .any(|layer| layer.has_non_empty_section(section))
672 }
672 }
673
673
674 /// Yields (key, value) pairs for everything in the given section
674 /// Yields (key, value) pairs for everything in the given section
675 pub fn iter_section<'a>(
675 pub fn iter_section<'a>(
676 &'a self,
676 &'a self,
677 section: &'a [u8],
677 section: &'a [u8],
678 ) -> impl Iterator<Item = (&[u8], &[u8])> + 'a {
678 ) -> impl Iterator<Item = (&[u8], &[u8])> + 'a {
679 // Deduplicate keys redefined in multiple layers
679 // Deduplicate keys redefined in multiple layers
680 let mut keys_already_seen = HashSet::new();
680 let mut keys_already_seen = HashSet::new();
681 let mut key_is_new =
681 let mut key_is_new =
682 move |&(key, _value): &(&'a [u8], &'a [u8])| -> bool {
682 move |&(key, _value): &(&'a [u8], &'a [u8])| -> bool {
683 keys_already_seen.insert(key)
683 keys_already_seen.insert(key)
684 };
684 };
685 // This is similar to `flat_map` + `filter_map`, except with a single
685 // This is similar to `flat_map` + `filter_map`, except with a single
686 // closure that owns `key_is_new` (and therefore the
686 // closure that owns `key_is_new` (and therefore the
687 // `keys_already_seen` set):
687 // `keys_already_seen` set):
688 let mut layer_iters = self
688 let mut layer_iters = self
689 .layers
689 .layers
690 .iter()
690 .iter()
691 .rev()
691 .rev()
692 .map(move |layer| layer.iter_section(section))
692 .map(move |layer| layer.iter_section(section))
693 .peekable();
693 .peekable();
694 std::iter::from_fn(move || loop {
694 std::iter::from_fn(move || loop {
695 if let Some(pair) = layer_iters.peek_mut()?.find(&mut key_is_new) {
695 if let Some(pair) = layer_iters.peek_mut()?.find(&mut key_is_new) {
696 return Some(pair);
696 return Some(pair);
697 } else {
697 } else {
698 layer_iters.next();
698 layer_iters.next();
699 }
699 }
700 })
700 })
701 }
701 }
702
702
703 /// Get raw values bytes from all layers (even untrusted ones) in order
703 /// Get raw values bytes from all layers (even untrusted ones) in order
704 /// of precedence.
704 /// of precedence.
705 #[cfg(test)]
705 #[cfg(test)]
706 fn get_all(&self, section: &[u8], item: &[u8]) -> Vec<&[u8]> {
706 fn get_all(&self, section: &[u8], item: &[u8]) -> Vec<&[u8]> {
707 let mut res = vec![];
707 let mut res = vec![];
708 for layer in self.layers.iter().rev() {
708 for layer in self.layers.iter().rev() {
709 if let Some(v) = layer.get(section, item) {
709 if let Some(v) = layer.get(section, item) {
710 res.push(v.bytes.as_ref());
710 res.push(v.bytes.as_ref());
711 }
711 }
712 }
712 }
713 res
713 res
714 }
714 }
715
715
716 // a config layer that's introduced by ui.tweakdefaults
716 // a config layer that's introduced by ui.tweakdefaults
717 fn tweakdefaults_layer() -> ConfigLayer {
717 fn tweakdefaults_layer() -> ConfigLayer {
718 let mut layer = ConfigLayer::new(ConfigOrigin::Tweakdefaults);
718 let mut layer = ConfigLayer::new(ConfigOrigin::Tweakdefaults);
719
719
720 let mut add = |section: &[u8], item: &[u8], value: &[u8]| {
720 let mut add = |section: &[u8], item: &[u8], value: &[u8]| {
721 layer.add(
721 layer.add(
722 section[..].into(),
722 section[..].into(),
723 item[..].into(),
723 item[..].into(),
724 value[..].into(),
724 value[..].into(),
725 None,
725 None,
726 );
726 );
727 };
727 };
728 // duplication of [tweakrc] from [ui.py]
728 // duplication of [tweakrc] from [ui.py]
729 add(b"ui", b"rollback", b"False");
729 add(b"ui", b"rollback", b"False");
730 add(b"ui", b"statuscopies", b"yes");
730 add(b"ui", b"statuscopies", b"yes");
731 add(b"ui", b"interface", b"curses");
731 add(b"ui", b"interface", b"curses");
732 add(b"ui", b"relative-paths", b"yes");
732 add(b"ui", b"relative-paths", b"yes");
733 add(b"commands", b"grep.all-files", b"True");
733 add(b"commands", b"grep.all-files", b"True");
734 add(b"commands", b"update.check", b"noconflict");
734 add(b"commands", b"update.check", b"noconflict");
735 add(b"commands", b"status.verbose", b"True");
735 add(b"commands", b"status.verbose", b"True");
736 add(b"commands", b"resolve.explicit-re-merge", b"True");
736 add(b"commands", b"resolve.explicit-re-merge", b"True");
737 add(b"git", b"git", b"1");
737 add(b"git", b"git", b"1");
738 add(b"git", b"showfunc", b"1");
738 add(b"git", b"showfunc", b"1");
739 add(b"git", b"word-diff", b"1");
739 add(b"git", b"word-diff", b"1");
740 layer
740 layer
741 }
741 }
742
742
743 // introduce the tweaked defaults as implied by ui.tweakdefaults
743 // introduce the tweaked defaults as implied by ui.tweakdefaults
744 pub fn tweakdefaults(&mut self) {
744 pub fn tweakdefaults(&mut self) {
745 self.layers.insert(0, Config::tweakdefaults_layer());
745 self.layers.insert(0, Config::tweakdefaults_layer());
746 }
746 }
747 }
747 }
748
748
749 #[cfg(test)]
749 #[cfg(test)]
750 mod tests {
750 mod tests {
751 use super::*;
751 use super::*;
752 use pretty_assertions::assert_eq;
752 use pretty_assertions::assert_eq;
753 use std::fs::File;
753 use std::fs::File;
754 use std::io::Write;
754 use std::io::Write;
755
755
756 #[test]
756 #[test]
757 fn test_include_layer_ordering() {
757 fn test_include_layer_ordering() {
758 let tmpdir = tempfile::tempdir().unwrap();
758 let tmpdir = tempfile::tempdir().unwrap();
759 let tmpdir_path = tmpdir.path();
759 let tmpdir_path = tmpdir.path();
760 let mut included_file =
760 let mut included_file =
761 File::create(&tmpdir_path.join("included.rc")).unwrap();
761 File::create(&tmpdir_path.join("included.rc")).unwrap();
762
762
763 included_file.write_all(b"[section]\nitem=value1").unwrap();
763 included_file.write_all(b"[section]\nitem=value1").unwrap();
764 let base_config_path = tmpdir_path.join("base.rc");
764 let base_config_path = tmpdir_path.join("base.rc");
765 let mut config_file = File::create(&base_config_path).unwrap();
765 let mut config_file = File::create(&base_config_path).unwrap();
766 let data =
766 let data =
767 b"[section]\nitem=value0\n%include included.rc\nitem=value2\n\
767 b"[section]\nitem=value0\n%include included.rc\nitem=value2\n\
768 [section2]\ncount = 4\nsize = 1.5 KB\nnot-count = 1.5\nnot-size = 1 ub";
768 [section2]\ncount = 4\nsize = 1.5 KB\nnot-count = 1.5\nnot-size = 1 ub";
769 config_file.write_all(data).unwrap();
769 config_file.write_all(data).unwrap();
770
770
771 let sources = vec![ConfigSource::AbsPath(base_config_path)];
771 let sources = vec![ConfigSource::AbsPath(base_config_path)];
772 let config = Config::load_from_explicit_sources(sources)
772 let config = Config::load_from_explicit_sources(sources)
773 .expect("expected valid config");
773 .expect("expected valid config");
774
774
775 let (_, value) = config.get_inner(b"section", b"item").unwrap();
775 let (_, value) = config.get_inner(b"section", b"item").unwrap();
776 assert_eq!(
776 assert_eq!(
777 value,
777 value,
778 &ConfigValue {
778 &ConfigValue {
779 bytes: b"value2".to_vec(),
779 bytes: b"value2".to_vec(),
780 line: Some(4)
780 line: Some(4)
781 }
781 }
782 );
782 );
783
783
784 let value = config.get(b"section", b"item").unwrap();
784 let value = config.get(b"section", b"item").unwrap();
785 assert_eq!(value, b"value2",);
785 assert_eq!(value, b"value2",);
786 assert_eq!(
786 assert_eq!(
787 config.get_all(b"section", b"item"),
787 config.get_all(b"section", b"item"),
788 [b"value2", b"value1", b"value0"]
788 [b"value2", b"value1", b"value0"]
789 );
789 );
790
790
791 assert_eq!(config.get_u32(b"section2", b"count").unwrap(), Some(4));
791 assert_eq!(config.get_u32(b"section2", b"count").unwrap(), Some(4));
792 assert_eq!(
792 assert_eq!(
793 config.get_byte_size(b"section2", b"size").unwrap(),
793 config.get_byte_size(b"section2", b"size").unwrap(),
794 Some(1024 + 512)
794 Some(1024 + 512)
795 );
795 );
796 assert!(config.get_u32(b"section2", b"not-count").is_err());
796 assert!(config.get_u32(b"section2", b"not-count").is_err());
797 assert!(config.get_byte_size(b"section2", b"not-size").is_err());
797 assert!(config.get_byte_size(b"section2", b"not-size").is_err());
798 }
798 }
799
799
800 #[test]
800 #[test]
801 fn test_default_parse() {
801 fn test_default_parse() {
802 let config = Config::load_from_explicit_sources(vec![])
802 let config = Config::load_from_explicit_sources(vec![])
803 .expect("expected valid config");
803 .expect("expected valid config");
804 let ret = config.get_byte_size(b"cmdserver", b"max-log-size");
804 let ret = config.get_byte_size(b"cmdserver", b"max-log-size");
805 assert!(ret.is_ok(), "{:?}", ret);
805 assert!(ret.is_ok(), "{:?}", ret);
806
806
807 let ret = config.get_byte_size(b"ui", b"formatted");
807 let ret = config.get_byte_size(b"ui", b"formatted");
808 // FIXME should be `is_none()`
808 assert!(ret.unwrap().is_none());
809 assert!(ret.unwrap().is_some());
810 }
809 }
811 }
810 }
General Comments 0
You need to be logged in to leave comments. Login now