playdate_build/metadata/
format.rs

1use std::ops::Deref;
2use std::cmp::Eq;
3use std::hash::Hash;
4use std::borrow::Cow;
5use std::collections::HashMap;
6
7#[cfg(feature = "serde")]
8use serde::{Deserialize, Deserializer};
9
10use super::source::*;
11
12
13#[derive(Debug)]
14#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
15#[cfg_attr(feature = "serde",
16           serde(bound(deserialize = "S: Deserialize<'de> + Eq + Hash")))]
17pub struct CrateMetadata<S: Eq + Hash = String> {
18	#[cfg_attr(feature = "serde", serde(rename = "playdate"))]
19	pub inner: Option<Metadata<S>>,
20}
21
22/// Just ensure that `METADATA_FIELD` is not changed and something missed.
23#[cfg(test)]
24#[cfg_attr(test, test)]
25fn eq_metadata_field() {
26	assert_eq!(super::METADATA_FIELD, "playdate");
27}
28
29
30pub mod ws {
31	#[derive(Debug)]
32	#[cfg_attr(feature = "serde", derive(super::Deserialize))]
33	pub struct WorkspaceMetadata {
34		#[cfg_attr(feature = "serde", serde(rename = "playdate"))]
35		pub inner: Option<Metadata>,
36	}
37
38	#[derive(Debug, Clone, PartialEq)]
39	#[cfg_attr(feature = "serde", derive(super::Deserialize))]
40	#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
41	pub struct Metadata {
42		pub options: Option<OptionsDefault>,
43		pub support: Option<super::Support>,
44	}
45
46	#[derive(Debug, Clone, Default, PartialEq)]
47	#[cfg_attr(feature = "serde", derive(super::Deserialize))]
48	#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
49	pub struct OptionsDefault {
50		#[cfg_attr(feature = "serde", serde(default))]
51		pub assets: super::AssetsOptions,
52	}
53}
54
55
56/// Package Playdate Metadata, contains:
57/// - Package Manifest fields
58/// - Assets tables - `assets` & `dev-assets`
59/// - Configuration table - `options`
60#[derive(Debug, Clone, PartialEq)]
61
62pub struct Metadata<S: Eq + Hash = String> {
63	pub(super) inner: MetadataInner<S>,
64}
65
66
67#[cfg(feature = "serde")]
68impl<'de, S: Deserialize<'de> + Eq + Hash> Deserialize<'de> for Metadata<S> {
69	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
70		where D: Deserializer<'de> {
71		let meta = MetadataInner::<S>::deserialize(deserializer)?;
72		// here is should be some validation
73		Ok(Self { inner: meta })
74	}
75}
76
77
78impl<S> MetadataSource for Metadata<S>
79	where S: Eq + Hash + AsRef<str>,
80	      Override<S>: ManifestSourceOptExt,
81	      Ext<Manifest<S>>: ManifestSourceOptExt,
82	      for<'t> &'t Ext<Manifest<S>>: ManifestSourceOptExt
83{
84	type S = S;
85	type Manifest = Ext<Manifest<S>>;
86	type TargetManifest = Override<S>;
87
88
89	fn manifest(&self) -> &Self::Manifest { &self.inner.manifest }
90
91	fn bins(&self) -> &[Self::TargetManifest] { self.inner.bins.as_slice() }
92	fn examples(&self) -> &[Self::TargetManifest] { self.inner.examples.as_slice() }
93
94	fn bin_targets(&self) -> impl IntoIterator<Item = &str> { self.inner.bins.iter().map(|o| o.target.as_ref()) }
95	fn example_targets(&self) -> impl IntoIterator<Item = &str> {
96		self.inner.examples.iter().map(|o| o.target.as_ref())
97	}
98
99	fn assets(&self) -> &AssetsRules<S> { &self.inner.assets }
100	fn dev_assets(&self) -> &AssetsRules<S> { &self.inner.dev_assets }
101
102
103	fn options(&self) -> &Options { &self.inner.options }
104	fn assets_options(&self) -> Cow<'_, AssetsOptions> { Cow::Borrowed(&self.options().assets) }
105
106	fn support(&self) -> &Support { &self.inner.support }
107}
108
109
110/// Package Metadata, contains:
111/// - Package Manifest fields
112/// - Assets tables - `assets` & `dev-assets`
113/// - Configuration table - `options`
114#[derive(Debug, Clone, PartialEq)]
115#[cfg_attr(feature = "serde", derive(Deserialize))]
116#[cfg_attr(feature = "serde",
117           serde(bound(deserialize = "S: Deserialize<'de> + Eq + Hash")))]
118pub(super) struct MetadataInner<S: Eq + Hash = String> {
119	#[cfg_attr(feature = "serde", serde(flatten))]
120	pub(super) manifest: Ext<Manifest<S>>,
121
122	#[cfg_attr(feature = "serde", serde(default))]
123	#[cfg_attr(feature = "serde", serde(deserialize_with = "one_of::assets_rules"))]
124	pub(super) assets: AssetsRules<S>,
125	#[cfg_attr(feature = "serde", serde(default, alias = "dev-assets"))]
126	#[cfg_attr(feature = "serde", serde(deserialize_with = "one_of::assets_rules"))]
127	pub(super) dev_assets: AssetsRules<S>,
128
129	#[cfg_attr(feature = "serde", serde(default))]
130	pub(super) options: Options,
131	#[cfg_attr(feature = "serde", serde(default))]
132	pub(super) support: Support,
133
134	#[cfg_attr(feature = "serde", serde(default, alias = "bin", rename = "bin"))]
135	#[cfg_attr(feature = "serde", serde(deserialize_with = "one_of::targets_overrides"))]
136	pub(super) bins: Vec<Override<S>>,
137	#[cfg_attr(feature = "serde", serde(default, alias = "example", rename = "example"))]
138	#[cfg_attr(feature = "serde", serde(deserialize_with = "one_of::targets_overrides"))]
139	pub(super) examples: Vec<Override<S>>,
140}
141
142
143#[derive(Debug, Clone, PartialEq)]
144#[cfg_attr(feature = "serde", derive(Deserialize))]
145#[cfg_attr(feature = "serde", serde(bound(deserialize = "Main: Deserialize<'de>")))]
146pub struct Ext<Main> {
147	#[cfg_attr(feature = "serde", serde(flatten))]
148	pub(super) main: Main,
149	#[cfg_attr(feature = "serde", serde(flatten))]
150	pub(super) extra: ExtraFields<ExtraValue>,
151}
152
153impl<T> Ext<T> {
154	pub fn new(main: T, extra: ExtraFields<ExtraValue>) -> Self { Self { main, extra } }
155}
156
157impl<T> Ext<T> {
158	pub fn inner(&self) -> &T { &self.main }
159	pub fn extra(&self) -> &ExtraFields<ExtraValue> { &self.extra }
160}
161
162impl<S> Ext<Manifest<S>> where S: ToOwned {
163	pub fn clone_owned(&self) -> Ext<Manifest<<S as ToOwned>::Owned>> {
164		Ext { main: self.main.clone_owned(),
165		      extra: self.extra.to_owned() }
166	}
167}
168
169
170#[derive(Debug, Clone, PartialEq)]
171#[cfg_attr(feature = "serde", derive(Deserialize))]
172#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
173#[cfg_attr(feature = "serde", serde(bound(deserialize = "S: Deserialize<'de>")))]
174pub struct Manifest<S> {
175	pub name: Option<S>,
176	pub version: Option<S>,
177	pub author: Option<S>,
178	#[cfg_attr(feature = "serde", serde(alias = "bundle-id"))]
179	pub bundle_id: Option<S>,
180	pub description: Option<S>,
181	#[cfg_attr(feature = "serde", serde(alias = "image-path"))]
182	pub image_path: Option<S>,
183	#[cfg_attr(feature = "serde", serde(alias = "launch-sound-path"))]
184	pub launch_sound_path: Option<S>,
185	#[cfg_attr(feature = "serde", serde(alias = "content-warning"))]
186	pub content_warning: Option<S>,
187	#[cfg_attr(feature = "serde", serde(alias = "content-warning2"))]
188	pub content_warning2: Option<S>,
189	#[cfg_attr(feature = "serde", serde(default, alias = "build-number"))]
190	#[cfg_attr(feature = "serde", serde(deserialize_with = "one_of::usize_or_from_str"))]
191	pub build_number: Option<usize>,
192}
193
194
195impl<'t, S> Cob<'t> for Manifest<S> where S: Cob<'t> {
196	type Output = Manifest<<S as Cob<'t>>::Output>;
197
198	fn as_borrow(&'t self) -> Self::Output {
199		Manifest { name: self.name.as_ref().map(Cob::as_borrow),
200		           version: self.version.as_ref().map(Cob::as_borrow),
201		           author: self.author.as_ref().map(Cob::as_borrow),
202		           bundle_id: self.bundle_id.as_ref().map(Cob::as_borrow),
203		           description: self.description.as_ref().map(Cob::as_borrow),
204		           image_path: self.image_path.as_ref().map(Cob::as_borrow),
205		           launch_sound_path: self.launch_sound_path.as_ref().map(Cob::as_borrow),
206		           content_warning: self.content_warning.as_ref().map(Cob::as_borrow),
207		           content_warning2: self.content_warning2.as_ref().map(Cob::as_borrow),
208		           build_number: self.build_number }
209	}
210}
211
212impl<'t, T> Cob<'t> for Ext<T> where T: Cob<'t> {
213	type Output = Ext<<T as Cob<'t>>::Output>;
214
215	fn as_borrow(&'t self) -> Self::Output {
216		let main = self.main.as_borrow();
217		Ext { main,
218		      extra: self.extra
219		                 .iter()
220		                 .map(|(k, v)| (k.to_owned(), v.to_owned()))
221		                 .collect() }
222	}
223}
224
225impl<'t, T> Cob<'t> for Override<T> where T: Cob<'t> {
226	type Output = Override<<T as Cob<'t>>::Output>;
227
228	fn as_borrow(&'t self) -> Self::Output {
229		let Override { target, manifest } = self;
230		Override { target: target.as_borrow(),
231		           manifest: manifest.as_borrow() }
232	}
233}
234
235
236impl IntoOwned<Manifest<<str as ToOwned>::Owned>> for Manifest<Cow<'_, str>> {
237	fn into_owned(self) -> Manifest<<str as ToOwned>::Owned> {
238		Manifest { name: self.name.map(|s| s.into_owned()),
239		           version: self.version.map(|s| s.into_owned()),
240		           author: self.author.map(|s| s.into_owned()),
241		           bundle_id: self.bundle_id.map(|s| s.into_owned()),
242		           description: self.description.map(|s| s.into_owned()),
243		           image_path: self.image_path.map(|s| s.into_owned()),
244		           launch_sound_path: self.launch_sound_path.map(|s| s.into_owned()),
245		           content_warning: self.content_warning.map(|s| s.into_owned()),
246		           content_warning2: self.content_warning2.map(|s| s.into_owned()),
247		           build_number: self.build_number }
248	}
249}
250
251impl<S> Manifest<S> where S: ToOwned {
252	pub fn clone_owned(&self) -> Manifest<<S as ToOwned>::Owned> {
253		Manifest { name: self.name.as_ref().map(|s| s.to_owned()),
254		           version: self.version.as_ref().map(|s| s.to_owned()),
255		           author: self.author.as_ref().map(|s| s.to_owned()),
256		           bundle_id: self.bundle_id.as_ref().map(|s| s.to_owned()),
257		           description: self.description.as_ref().map(|s| s.to_owned()),
258		           image_path: self.image_path.as_ref().map(|s| s.to_owned()),
259		           launch_sound_path: self.launch_sound_path.as_ref().map(|s| s.to_owned()),
260		           content_warning: self.content_warning.as_ref().map(|s| s.to_owned()),
261		           content_warning2: self.content_warning2.as_ref().map(|s| s.to_owned()),
262		           build_number: self.build_number }
263	}
264}
265
266
267#[derive(Debug, Clone, PartialEq)]
268#[cfg_attr(feature = "serde", derive(Deserialize))]
269#[cfg_attr(feature = "serde", serde(bound(deserialize = "S: Deserialize<'de>")))]
270pub struct Override<S> {
271	/// Associated cargo-target name
272	#[cfg_attr(feature = "serde", serde(rename = "id", alias = "target"))]
273	pub(super) target: S,
274	#[cfg_attr(feature = "serde", serde(flatten))]
275	pub(super) manifest: Ext<Manifest<S>>,
276}
277
278impl<S: AsRef<str>> Override<S> {
279	pub fn into_parts(self) -> (S, Ext<Manifest<S>>) {
280		let Override { target, manifest } = self;
281		(target, manifest)
282	}
283
284	pub fn as_parts(&self) -> (&S, &Ext<Manifest<S>>) {
285		let Override { target, manifest } = self;
286		(target, manifest)
287	}
288}
289
290impl<S: AsRef<str>> TargetId for Override<S> {
291	fn target(&self) -> &str { self.target.as_ref() }
292}
293
294
295impl<'t> IntoOwned<Override<String>> for Override<Cow<'t, str>> {
296	fn into_owned(self) -> Override<String> {
297		Override { target: self.target.into_owned(),
298		           manifest: self.manifest.into_owned() }
299	}
300}
301
302impl<S> Override<S> where S: ToOwned {
303	pub fn clone_owned(&self) -> Override<<S as ToOwned>::Owned> {
304		Override { target: self.target.to_owned(),
305		           manifest: self.manifest.clone_owned() }
306	}
307}
308
309
310#[derive(Debug, Clone, PartialEq)]
311#[cfg_attr(feature = "serde", derive(Deserialize))]
312#[cfg_attr(feature = "serde", serde(untagged, deny_unknown_fields))]
313#[cfg_attr(feature = "serde",
314           serde(bound(deserialize = "S: Deserialize<'de> + Default + Eq + Hash")))]
315pub enum AssetsRules<S: Eq + Hash = String> {
316	/// List of paths to include.
317	List(Vec<S>),
318	/// Rules & queries used to resolve paths to include.
319	Map(HashMap<S, RuleValue>),
320}
321
322impl<S: Eq + Hash> Default for AssetsRules<S> {
323	fn default() -> Self { Self::List(Vec::with_capacity(0)) }
324}
325
326impl<S: Eq + Hash> AssetsRules<S> {
327	pub fn is_empty(&self) -> bool {
328		match self {
329			Self::List(list) => list.is_empty(),
330			Self::Map(map) => map.is_empty(),
331		}
332	}
333}
334
335
336#[derive(Debug, Clone, PartialEq)]
337#[cfg_attr(feature = "serde", derive(Deserialize))]
338#[cfg_attr(feature = "serde", serde(untagged))]
339pub enum RuleValue {
340	Boolean(bool),
341	String(String),
342}
343
344impl Default for RuleValue {
345	fn default() -> Self { Self::Boolean(true) }
346}
347
348
349pub type ExtraFields<V> = HashMap<String, V>;
350
351
352#[derive(Debug, Clone, PartialEq)]
353#[cfg_attr(feature = "serde", derive(Deserialize))]
354#[cfg_attr(feature = "serde", serde(untagged))]
355pub enum ExtraValue {
356	Boolean(bool),
357	String(String),
358	Int(i64),
359}
360
361impl ExtraValue {
362	pub fn is_empty(&self) -> bool {
363		match self {
364			Self::String(s) => s.trim().is_empty(),
365			_ => false,
366		}
367	}
368}
369
370impl std::fmt::Display for ExtraValue {
371	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
372		match self {
373			Self::Boolean(v) => v.fmt(f),
374			Self::String(v) => v.trim().fmt(f),
375			Self::Int(v) => v.fmt(f),
376		}
377	}
378}
379
380impl From<bool> for ExtraValue {
381	fn from(value: bool) -> Self { Self::Boolean(value) }
382}
383impl From<i64> for ExtraValue {
384	fn from(value: i64) -> Self { Self::Int(value) }
385}
386impl From<isize> for ExtraValue {
387	fn from(value: isize) -> Self { Self::Int(value as _) }
388}
389impl From<u64> for ExtraValue {
390	fn from(value: u64) -> Self { Self::Int(value as _) }
391}
392impl From<usize> for ExtraValue {
393	fn from(value: usize) -> Self { Self::Int(value as _) }
394}
395impl From<String> for ExtraValue {
396	fn from(value: String) -> Self { Self::String(value) }
397}
398impl From<&str> for ExtraValue {
399	fn from(value: &str) -> Self { Self::String(value.to_string()) }
400}
401impl<'t> From<Cow<'t, str>> for ExtraValue {
402	fn from(value: Cow<'t, str>) -> Self { Self::String(value.into_owned()) }
403}
404
405impl AsRef<ExtraValue> for ExtraValue {
406	fn as_ref(&self) -> &ExtraValue { self }
407}
408impl AsMut<ExtraValue> for ExtraValue {
409	fn as_mut(&mut self) -> &mut ExtraValue { self }
410}
411
412
413impl<S> ManifestSourceOpt for Manifest<S> where S: Deref<Target = str> {
414	const MAY_BE_INCOMPLETE: bool = true;
415
416	fn name(&self) -> Option<&str> { self.name.as_deref() }
417	fn version(&self) -> Option<&str> { self.version.as_deref() }
418	fn author(&self) -> Option<&str> { self.author.as_deref() }
419	fn bundle_id(&self) -> Option<&str> { self.bundle_id.as_deref() }
420	fn description(&self) -> Option<&str> { self.description.as_deref() }
421	fn image_path(&self) -> Option<&str> { self.image_path.as_deref() }
422	fn launch_sound_path(&self) -> Option<&str> { self.launch_sound_path.as_deref() }
423	fn content_warning(&self) -> Option<&str> { self.content_warning.as_deref() }
424	fn content_warning2(&self) -> Option<&str> { self.content_warning2.as_deref() }
425	fn build_number(&self) -> Option<usize> { self.build_number }
426}
427
428impl<T: ManifestSourceOpt> ManifestSourceOpt for Ext<T> {
429	const MAY_BE_INCOMPLETE: bool = Manifest::<String>::MAY_BE_INCOMPLETE;
430
431	fn name(&self) -> Option<&str> { self.inner().name() }
432	fn version(&self) -> Option<&str> { self.inner().version() }
433	fn author(&self) -> Option<&str> { self.inner().author() }
434	fn bundle_id(&self) -> Option<&str> { self.inner().bundle_id() }
435	fn description(&self) -> Option<&str> { self.inner().description() }
436	fn image_path(&self) -> Option<&str> { self.inner().image_path() }
437	fn launch_sound_path(&self) -> Option<&str> { self.inner().launch_sound_path() }
438	fn content_warning(&self) -> Option<&str> { self.inner().content_warning() }
439	fn content_warning2(&self) -> Option<&str> { self.inner().content_warning2() }
440	fn build_number(&self) -> Option<usize> { self.inner().build_number() }
441}
442impl<T: ManifestSourceOpt> ManifestSourceOpt for &Ext<T> {
443	const MAY_BE_INCOMPLETE: bool = T::MAY_BE_INCOMPLETE;
444
445	fn name(&self) -> Option<&str> { (*self).name() }
446	fn version(&self) -> Option<&str> { (*self).version() }
447	fn author(&self) -> Option<&str> { (*self).author() }
448	fn bundle_id(&self) -> Option<&str> { (*self).bundle_id() }
449	fn description(&self) -> Option<&str> { (*self).description() }
450	fn image_path(&self) -> Option<&str> { (*self).image_path() }
451	fn launch_sound_path(&self) -> Option<&str> { (*self).launch_sound_path() }
452	fn content_warning(&self) -> Option<&str> { (*self).content_warning() }
453	fn content_warning2(&self) -> Option<&str> { (*self).content_warning2() }
454	fn build_number(&self) -> Option<usize> { (*self).build_number() }
455}
456
457
458impl<T: ManifestSourceOpt> ManifestSourceOptExt for Ext<T> {
459	const MAY_HAVE_EXTRA: bool = true;
460
461	fn has_extra(&self) -> bool { !self.extra.is_empty() }
462	fn iter_extra(&self) -> Option<impl IntoIterator<Item = (impl AsRef<str>, impl AsRef<ExtraValue>)>> {
463		if self.extra.is_empty() {
464			None
465		} else {
466			Some(self.extra.iter())
467		}
468	}
469}
470
471impl<S> ManifestSourceOptExt for Manifest<S> where S: Deref<Target = str> {
472	const MAY_HAVE_EXTRA: bool = false;
473
474	fn has_extra(&self) -> bool { false }
475	fn iter_extra(&self) -> Option<impl IntoIterator<Item = (impl AsRef<str>, impl AsRef<ExtraValue>)>> {
476		None::<std::collections::hash_map::Iter<'_, &str, &ExtraValue>>
477	}
478}
479
480impl<'s, T: ManifestSourceOpt, S: From<&'s str>> From<&'s T> for Manifest<S> {
481	fn from(source: &'s T) -> Self {
482		Self { name: source.name().map(Into::into),
483		       version: source.version().map(Into::into),
484		       author: source.author().map(Into::into),
485		       bundle_id: source.bundle_id().map(Into::into),
486		       description: source.description().map(Into::into),
487		       image_path: source.image_path().map(Into::into),
488		       launch_sound_path: source.launch_sound_path().map(Into::into),
489		       content_warning: source.content_warning().map(Into::into),
490		       content_warning2: source.content_warning2().map(Into::into),
491		       build_number: source.build_number() }
492	}
493}
494
495
496impl<T: ManifestSourceOptExt> From<&T> for Ext<Manifest<String>> {
497	fn from(source: &T) -> Self {
498		let main = Manifest::from(source);
499		Ext { main,
500		      extra: source.iter_extra()
501		                   .map(|i| {
502			                   i.into_iter()
503			                    .map(|(k, v)| (k.as_ref().to_owned(), v.as_ref().clone()))
504			                    .collect()
505		                   })
506		                   .unwrap_or_default() }
507	}
508}
509
510impl<'t, T: ManifestSourceOptExt> From<&'t T> for Ext<Manifest<Cow<'t, str>>> {
511	fn from(source: &'t T) -> Self {
512		Ext { main: Manifest::from(source),
513		      extra: source.iter_extra()
514		                   .map(|i| {
515			                   i.into_iter()
516			                    .map(|(k, v)| (k.as_ref().to_owned(), v.as_ref().clone()))
517			                    .collect()
518		                   })
519		                   .unwrap_or_default() }
520	}
521}
522
523impl<'t, T: ManifestSourceOptExt + 't> IntoOwned<Ext<Manifest<String>>> for T {
524	fn into_owned(self) -> Ext<Manifest<String>> {
525		Ext { main: Manifest::from(&self).into_owned(),
526		      extra: self.iter_extra()
527		                 .map(|i| {
528			                 i.into_iter()
529			                  .map(|(k, v)| (k.as_ref().to_owned(), v.as_ref().clone()))
530			                  .collect()
531		                 })
532		                 .unwrap_or_default() }
533	}
534}
535
536
537impl<S> ManifestSourceOpt for Override<S> where Manifest<S>: ManifestSourceOpt {
538	const MAY_BE_INCOMPLETE: bool = Manifest::<S>::MAY_BE_INCOMPLETE;
539
540	fn name(&self) -> Option<&str> { self.manifest.name() }
541	fn version(&self) -> Option<&str> { self.manifest.version() }
542	fn author(&self) -> Option<&str> { self.manifest.author() }
543	fn bundle_id(&self) -> Option<&str> { self.manifest.bundle_id() }
544	fn description(&self) -> Option<&str> { self.manifest.description() }
545	fn image_path(&self) -> Option<&str> { self.manifest.image_path() }
546	fn launch_sound_path(&self) -> Option<&str> { self.manifest.launch_sound_path() }
547	fn content_warning(&self) -> Option<&str> { self.manifest.content_warning() }
548	fn content_warning2(&self) -> Option<&str> { self.manifest.content_warning2() }
549	fn build_number(&self) -> Option<usize> { self.manifest.build_number() }
550}
551
552impl<S> ManifestSourceOptExt for Override<S> where Manifest<S>: ManifestSourceOpt {
553	const MAY_HAVE_EXTRA: bool = Ext::<Manifest<S>>::MAY_HAVE_EXTRA;
554
555	fn iter_extra(&self) -> Option<impl IntoIterator<Item = (impl AsRef<str>, impl AsRef<ExtraValue>)>> {
556		self.manifest.iter_extra()
557	}
558}
559
560
561#[derive(Debug, Clone, Default, PartialEq)]
562#[cfg_attr(feature = "serde", derive(Deserialize))]
563#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
564pub struct Options {
565	/// Use [`PackageSource::default_options`] as defaults for this.
566	#[cfg_attr(feature = "serde", serde(default))]
567	pub workspace: bool,
568	#[cfg_attr(feature = "serde", serde(default))]
569	pub assets: AssetsOptions,
570	// Output layout ctrl, temporary removed.
571}
572
573impl Options {
574	pub fn with_workspace(&self, def: Option<&ws::OptionsDefault>) -> Cow<'_, Options> {
575		let merge_assets = |assets: &AssetsOptions| {
576			if def.is_some() {
577				log::debug!("merge options.assets with ws.defaults")
578			}
579
580			let overwrite = assets.overwrite
581			                      .or_else(|| def.and_then(|d| d.assets.overwrite))
582			                      .unwrap_or(AssetsOptions::default_overwrite());
583			let follow_symlinks = assets.follow_symlinks
584			                            .or_else(|| def.and_then(|d| d.assets.follow_symlinks))
585			                            .unwrap_or(AssetsOptions::default_follow_symlinks());
586			let method = assets.method
587			                   .or_else(|| def.and_then(|d| d.assets.method))
588			                   .unwrap_or_default();
589			let dependencies = assets.dependencies
590			                         .or_else(|| def.and_then(|d| d.assets.dependencies))
591			                         .unwrap_or(AssetsOptions::default_dependencies());
592
593			AssetsOptions { overwrite: Some(overwrite),
594			                follow_symlinks: Some(follow_symlinks),
595			                method: Some(method),
596			                dependencies: Some(dependencies) }
597		};
598
599
600		if self.workspace {
601			let res = Self { workspace: self.workspace,
602			                 assets: merge_assets(&self.assets) };
603			Cow::Owned(res)
604		} else {
605			Cow::Borrowed(self)
606		}
607	}
608}
609
610
611#[derive(Default, Debug, Clone, PartialEq)]
612#[cfg_attr(feature = "serde", derive(Deserialize))]
613#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
614pub struct AssetsOptions {
615	#[cfg_attr(feature = "serde", serde(alias = "override"))]
616	overwrite: Option<bool>,
617
618	#[cfg_attr(feature = "serde", serde(alias = "follow-symlinks"))]
619	follow_symlinks: Option<bool>,
620
621	#[cfg_attr(feature = "serde", serde(alias = "build-method"))]
622	method: Option<AssetsBuildMethod>,
623
624	/// Allow building assets for dependencies
625	dependencies: Option<bool>,
626}
627
628impl AssetsOptions {
629	pub fn overwrite(&self) -> bool { self.overwrite.unwrap_or(Self::default_overwrite()) }
630	pub fn dependencies(&self) -> bool { self.dependencies.unwrap_or(Self::default_dependencies()) }
631	pub fn follow_symlinks(&self) -> bool { self.follow_symlinks.unwrap_or(Self::default_follow_symlinks()) }
632	pub fn method(&self) -> AssetsBuildMethod { self.method.unwrap_or_default() }
633
634	const fn default_overwrite() -> bool { true }
635	const fn default_follow_symlinks() -> bool { true }
636	const fn default_dependencies() -> bool { false }
637}
638
639
640#[derive(Debug, Clone, Copy, PartialEq, Eq)]
641#[cfg_attr(feature = "serde", derive(Deserialize))]
642#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
643pub enum AssetsBuildMethod {
644	Copy,
645	Link,
646}
647
648impl Default for AssetsBuildMethod {
649	fn default() -> Self { Self::Link }
650}
651
652
653/// Compatibility options.
654/// e.g. Crank manifest path.
655#[derive(Debug, Clone, Default, PartialEq)]
656#[cfg_attr(feature = "serde", derive(Deserialize))]
657pub struct Support {
658	// #[serde(alias = "crank-manifest")]
659	// pub crank_manifest: Option<PathBuf>
660}
661
662
663/// Because serde's error for untagged enum with various inner types is not pretty helpful,
664/// like "data did not match any variant of untagged enum AssetsRules",
665/// we need some custom implementations with more detailed error messages.
666#[cfg(feature = "serde")]
667mod one_of {
668	use std::marker::PhantomData;
669
670	use std::fmt;
671	use serde::de;
672	use serde::de::MapAccess;
673	use serde::de::SeqAccess;
674	use serde::de::Visitor;
675
676	use super::*;
677
678
679	pub fn usize_or_from_str<'de, D>(deserializer: D) -> Result<Option<usize>, D::Error>
680		where D: Deserializer<'de> {
681		struct OneOf;
682
683		impl<'de> Visitor<'de> for OneOf {
684			type Value = Option<usize>;
685
686			fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
687				formatter.write_str("unsigned integer or string with it")
688			}
689
690			fn visit_u8<E>(self, v: u8) -> Result<Self::Value, E> { Ok(Some(v as _)) }
691			fn visit_u16<E>(self, v: u16) -> Result<Self::Value, E> { Ok(Some(v as _)) }
692			fn visit_u32<E>(self, v: u32) -> Result<Self::Value, E> { Ok(Some(v as _)) }
693
694			fn visit_u64<E: de::Error>(self, v: u64) -> Result<Self::Value, E> {
695				Ok(Some(v.try_into().map_err(de::Error::custom)?))
696			}
697
698			fn visit_u128<E: de::Error>(self, v: u128) -> Result<Self::Value, E> {
699				Ok(Some(v.try_into().map_err(de::Error::custom)?))
700			}
701
702			fn visit_i64<E: de::Error>(self, v: i64) -> Result<Self::Value, E> {
703				if v.is_negative() {
704					Err(de::Error::invalid_type(de::Unexpected::Signed(v), &self))
705				} else {
706					Ok(Some(v.try_into().map_err(de::Error::custom)?))
707				}
708			}
709
710			fn visit_str<E: de::Error>(self, s: &str) -> Result<Self::Value, E> {
711				let v = s.parse().map_err(serde::de::Error::custom)?;
712				Ok(Some(v))
713			}
714		}
715
716		deserializer.deserialize_any(OneOf)
717	}
718
719
720	pub fn assets_rules<'de, S, D>(deserializer: D) -> Result<super::AssetsRules<S>, D::Error>
721		where D: Deserializer<'de>,
722		      S: Deserialize<'de> + Eq + Hash {
723		struct OneOf<S>(PhantomData<S>);
724
725		impl<'de, S> Visitor<'de> for OneOf<S> where S: Deserialize<'de> + Eq + Hash {
726			type Value = super::AssetsRules<S>;
727
728			fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
729				formatter.write_str("list of includes or map of rules")
730			}
731
732			fn visit_seq<A: SeqAccess<'de>>(self, seq: A) -> Result<Self::Value, A::Error> {
733				let deserializer = de::value::SeqAccessDeserializer::new(seq);
734				let res: Vec<S> = Deserialize::deserialize(deserializer)?;
735				Ok(super::AssetsRules::List(res))
736			}
737
738			fn visit_map<M: MapAccess<'de>>(self, map: M) -> Result<Self::Value, M::Error> {
739				let deserializer = de::value::MapAccessDeserializer::new(map);
740				let res: HashMap<S, super::RuleValue> = Deserialize::deserialize(deserializer)?;
741				Ok(super::AssetsRules::Map(res))
742			}
743		}
744
745		deserializer.deserialize_any(OneOf::<S>(PhantomData))
746	}
747
748
749	pub fn targets_overrides<'de, S, D>(deserializer: D) -> Result<Vec<super::Override<S>>, D::Error>
750		where D: Deserializer<'de>,
751		      S: Deserialize<'de> + Eq + Hash {
752		struct OneOf<S>(PhantomData<S>);
753
754		impl<'de, S> Visitor<'de> for OneOf<S> where S: Deserialize<'de> + Eq + Hash {
755			type Value = Vec<super::Override<S>>;
756
757			fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
758				formatter.write_str("list of includes or map of rules")
759			}
760
761			fn visit_seq<A: SeqAccess<'de>>(self, seq: A) -> Result<Self::Value, A::Error> {
762				let deserializer = de::value::SeqAccessDeserializer::new(seq);
763				Deserialize::deserialize(deserializer)
764			}
765
766			fn visit_map<M: MapAccess<'de>>(self, map: M) -> Result<Self::Value, M::Error> {
767				use super::{Ext, Manifest};
768
769				let deserializer = de::value::MapAccessDeserializer::new(map);
770				let res: HashMap<S, Ext<Manifest<S>>> = Deserialize::deserialize(deserializer)?;
771				Ok(res.into_iter()
772				      .map(|(k, v)| {
773					      Override::<S> { target: k,
774					                      manifest: v }
775				      })
776				      .collect())
777			}
778		}
779
780		deserializer.deserialize_any(OneOf::<S>(PhantomData))
781	}
782}
783
784
785#[cfg(test)]
786#[cfg(feature = "toml")]
787mod tests {
788	use super::*;
789	use crate::manifest::format::ManifestFmt;
790
791	use std::assert_matches::assert_matches;
792
793
794	type ManifestWithAny = Ext<Manifest<String>>;
795	type ManifestStrict = Manifest<String>;
796	type ManifestStrictRef<'t> = Manifest<Cow<'t, str>>;
797
798
799	#[test]
800	fn minimal_strict() {
801		let src = r#"
802		             bundle-id = "test.workspace.main.crate"
803		             description = "test"
804		          "#;
805		let m: ManifestStrict = toml::from_str(src).unwrap();
806		assert!(m.bundle_id.is_some());
807		let m: ManifestStrictRef = toml::from_str(src).unwrap();
808		assert!(m.bundle_id.is_some());
809	}
810
811	#[test]
812	fn minimal_strict_err() {
813		let src = r#"
814		             bundle-id = "test.workspace.main.crate"
815		             foo = "bar"
816		          "#;
817		assert!(toml::from_str::<ManifestStrict>(src).is_err());
818
819		let src = r#"foo = "bar""#;
820		assert!(toml::from_str::<ManifestStrict>(src).is_err());
821		assert!(toml::from_str::<ManifestStrictRef>(src).is_err());
822	}
823
824	#[test]
825	fn minimal_extra() {
826		let src = r#"bundle-id = "test.workspace.main.crate""#;
827		assert!(toml::from_str::<ManifestWithAny>(src).is_ok());
828
829
830		let src = r#"
831		             bundle-id = "test.workspace.main.crate"
832		             description = "test"
833		             foo = "bar"
834		          "#;
835
836		let m: ManifestWithAny = toml::from_str(src).unwrap();
837
838		assert!(m.inner().bundle_id.is_some());
839		assert!(m.inner().description.is_some());
840		assert!(m.extra().get("foo").is_some());
841	}
842
843	#[test]
844	fn meta_minimal() {
845		assert!(toml::from_str::<Metadata>("").is_ok());
846
847		let src = r#"
848		             bundle-id = "test.workspace.main.crate"
849		             description = "test"
850		          "#;
851
852		let m = toml::from_str::<Metadata>(src).unwrap();
853		assert!(m.inner.manifest.inner().bundle_id.is_some());
854		assert!(m.inner.manifest.inner().description.is_some());
855		assert!(m.inner.manifest.extra.is_empty());
856	}
857
858
859	#[test]
860	fn meta_extra() {
861		let src = r#"
862		             bundle-id = "test.workspace.main.crate"
863		             description = "test"
864		             boo = 42
865		             [assets]
866		             foo = "bar"
867		          "#;
868		let expected_id = Some("test.workspace.main.crate");
869
870		let m: super::MetadataInner = toml::from_str(src).unwrap();
871		assert_eq!(expected_id, m.manifest.inner().bundle_id.as_deref());
872		assert!(m.manifest.inner().description.is_some());
873		assert!(m.manifest.extra().get("boo").is_some());
874
875		let m: Metadata = toml::from_str(src).unwrap();
876		assert_eq!(expected_id, m.inner.manifest.inner().bundle_id.as_deref());
877		assert!(m.inner.manifest.inner().description.is_some());
878		assert!(m.inner.manifest.extra().get("boo").is_some());
879
880		let src = r#"
881		             bundle-id = "test.workspace.main.crate"
882		             description = "test"
883		             foo = "bar"
884		             assets.target = "source"
885		          "#;
886		let m: Metadata = toml::from_str(src).unwrap();
887		assert_eq!(expected_id, m.inner.manifest.inner().bundle_id.as_deref());
888		assert!(m.inner.manifest.inner().description.is_some());
889		assert!(m.inner.manifest.extra().get("foo").is_some());
890		assert!(!m.inner.assets.is_empty());
891	}
892
893
894	#[test]
895	fn meta_strict_bins() {
896		let src = r#"
897		             bundle-id = "test.workspace.main.crate"
898		             description = "test"
899		             [[bin]]
900		             target = "cargo-target-name"
901		             name = "Other Name"
902		             [[bin]]
903		             target = "cargo-another-target"
904		             name = "Another Name"
905		          "#;
906
907		let m = toml::from_str::<Metadata>(src).unwrap();
908		assert!(m.inner.manifest.inner().bundle_id.is_some());
909		assert!(m.inner.manifest.inner().description.is_some());
910		assert_eq!(2, m.inner.bins.len());
911	}
912
913	#[test]
914	fn meta_extra_bins() {
915		let src = r#"
916		             bundle-id = "test.workspace.main.crate"
917		             foo = "bar"
918
919		             [[bin]]
920		             target = "cargo-target-name"
921		             name = "Other Name"
922		             boo = "bar"
923		          "#;
924
925		let m = toml::from_str::<Metadata>(src).unwrap();
926		assert!(m.inner.manifest.inner().bundle_id.is_some());
927		assert!(m.inner.manifest.extra().get("foo").is_some());
928		assert_eq!(1, m.inner.bins.len());
929		assert!(
930		        m.inner
931		         .bins
932		         .first()
933		         .unwrap()
934		         .manifest
935		         .extra()
936		         .get("boo")
937		         .is_some()
938		);
939	}
940
941	#[test]
942	fn meta_strict_examples() {
943		let src = r#"
944		             bundle-id = "test.workspace.main.crate"
945		             description = "test"
946		             [[example]]
947		             target = "cargo-target-name"
948		             name = "Other Name"
949		             [[example]]
950		             target = "cargo-another-target"
951		             name = "Another Name"
952		          "#;
953
954		let m = toml::from_str::<Metadata>(src).unwrap();
955		assert!(m.inner.manifest.inner().bundle_id.is_some());
956		assert!(m.inner.manifest.inner().description.is_some());
957		assert_eq!(2, m.inner.examples.len());
958	}
959
960	#[test]
961	fn meta_strict_examples_map() {
962		let src = r#"
963		             bundle-id = "test.workspace.main.crate"
964		             description = "test"
965		             [example.cargo-target-name]
966		             name = "Other Name"
967		             [example.cargo-another-target]
968		             name = "Another Name"
969		          "#;
970
971		let m = toml::from_str::<Metadata>(src).unwrap();
972		assert!(m.inner.manifest.inner().bundle_id.is_some());
973		assert!(m.inner.manifest.inner().description.is_some());
974		assert_eq!(2, m.inner.examples.len());
975	}
976
977	#[test]
978	fn meta_strict_examples_mix_err() {
979		let src = r#"
980		             bundle-id = "test.workspace.main.crate"
981		             description = "test"
982		             [example.cargo-target-name]
983		             name = "Other Name"
984		             [[example]]
985		             target = "cargo-another-target"
986		             name = "Another Name"
987		          "#;
988
989		assert!(toml::from_str::<Metadata>(src).is_err());
990	}
991
992	#[test]
993	fn meta_extra_examples_mix_err() {
994		let src = r#"
995		             bundle-id = "test.workspace.main.crate"
996		             foo = "bar"
997		             [example.cargo-target-name]
998		             name = "Other Name"
999		             [[example]]
1000		             target = "cargo-another-target"
1001		             name = "Another Name"
1002		          "#;
1003
1004		assert!(toml::from_str::<Metadata>(src).is_err());
1005	}
1006
1007
1008	#[test]
1009	fn assets_num_err() {
1010		let src = r#"
1011		             [playdate]
1012		             bundle-id = "test.workspace.main.crate"
1013						 [playdate.assets]
1014						 foo = "bar" # ok
1015						 num = 42 # err
1016		          "#;
1017		assert!(toml::from_str::<CrateMetadata>(src).is_err());
1018	}
1019
1020
1021	#[test]
1022	fn options_empty() {
1023		let m = toml::from_str::<Options>("").unwrap();
1024		assert_eq!(Options::default(), m);
1025	}
1026
1027	#[test]
1028	fn options_assets_deps() {
1029		// default is false
1030		assert!(!AssetsOptions::default_dependencies());
1031		let src = r#" [assets] "#;
1032		let m = toml::from_str::<Options>(src).unwrap();
1033		assert_matches!(
1034		                m.assets,
1035		                AssetsOptions { dependencies: None,
1036		                                .. }
1037		);
1038
1039		// overrides default
1040		let src = r#"
1041		             [assets]
1042		             dependencies = true
1043		          "#;
1044		let m = toml::from_str::<Options>(src).unwrap();
1045		assert_matches!(
1046		                m.assets,
1047		                AssetsOptions { dependencies: Some(true),
1048		                                .. }
1049		);
1050	}
1051
1052	#[test]
1053	fn assets_rules_empty() {
1054		let m = toml::from_str::<AssetsRules>("").unwrap();
1055		assert!(m.is_empty());
1056		match m {
1057			AssetsRules::List(rules) => assert!(rules.is_empty()),
1058			AssetsRules::Map(rules) => assert!(rules.is_empty()),
1059		}
1060	}
1061
1062	#[test]
1063	fn assets_rules_list_wrapped() {
1064		#[derive(Debug, Clone, PartialEq, Deserialize)]
1065		pub(super) struct Temp {
1066			assets: AssetsRules,
1067		}
1068
1069		let src = r#"
1070		             assets = ["one", "two"]
1071		          "#;
1072		let m = toml::from_str::<Temp>(src).unwrap();
1073		assert!(!m.assets.is_empty());
1074		assert_matches!(m.assets, AssetsRules::List(rules) if rules.len() == 2);
1075	}
1076
1077	#[test]
1078	fn assets_rules_map() {
1079		let src = r#"
1080		             included = true
1081		             excluded = false
1082		             "into/" = "files.*"
1083		          "#;
1084		let m = toml::from_str::<AssetsRules>(src).unwrap();
1085		assert_matches!(m, AssetsRules::Map(rules) if rules.len() == 3);
1086	}
1087
1088
1089	#[test]
1090	fn assets_rules_map_wrapped() {
1091		#[derive(Debug, Clone, PartialEq, Deserialize)]
1092		pub(super) struct Temp {
1093			assets: AssetsRules,
1094		}
1095		let src = r#"
1096		             [assets]
1097		             included = true
1098		             excluded = false
1099		             "into/" = "files.*"
1100		          "#;
1101		let m = toml::from_str::<Temp>(src).unwrap();
1102		assert_matches!(m.assets, AssetsRules::Map(rules) if rules.len() == 3);
1103	}
1104
1105
1106	#[test]
1107	fn options_assets_err() {
1108		let src = r#"
1109		             [playdate]
1110		             bundle-id = "test.workspace.main.crate"
1111		             [playdate.options.assets]
1112		             foo = "bar" # err
1113		          "#;
1114		let result = toml::from_str::<CrateMetadata>(src);
1115		assert!(result.is_err(), "must be err, but {result:?}");
1116		assert!(result.as_ref()
1117		              .unwrap_err()
1118		              .to_string()
1119		              .contains("unknown field `foo`"));
1120	}
1121
1122	#[test]
1123	fn assets_options_err() {
1124		let src = r#"
1125		             [playdate]
1126		             bundle-id = "test.workspace.main.crate"
1127		             [playdate.assets]
1128		             foo = "bar"
1129		             options = { dependencies = true }
1130		          "#;
1131		let result = toml::from_str::<CrateMetadata>(src);
1132		assert!(result.is_err(), "must be err, but {result:?}");
1133
1134		let src = r#"
1135		             [playdate]
1136		             bundle-id = "test.workspace.main.crate"
1137		             [playdate.assets.options]
1138		             dependencies = true
1139		          "#;
1140		let result = toml::from_str::<CrateMetadata>(src);
1141		assert!(result.is_err(), "must be err, but {result:?}");
1142	}
1143
1144	#[test]
1145	fn meta_assets_options() {
1146		let src = r#"
1147		             bundle-id = "test.workspace.main.crate"
1148		             [options.assets]
1149		             [assets]
1150		          "#;
1151		assert!(toml::from_str::<Metadata>(src).is_ok());
1152
1153		let src = r#"
1154		             bundle-id = "test.workspace.main.crate"
1155		             [options.assets]
1156		             dependencies = true
1157		          "#;
1158		let m = toml::from_str::<MetadataInner>(src).unwrap();
1159		assert!(m.assets.is_empty());
1160		assert_matches!(
1161		                m.options.assets,
1162		                AssetsOptions { dependencies: Some(true),
1163		                                .. }
1164		);
1165	}
1166
1167	#[test]
1168	fn meta_assets_options_legacy() {
1169		let src = r#"
1170		             bundle-id = "test.workspace.main.crate"
1171		             [assets]
1172		             options = {}
1173		          "#;
1174		assert!(toml::from_str::<Metadata>(src).is_err());
1175
1176		let src = r#"
1177		             bundle-id = "test.workspace.main.crate"
1178		             [assets]
1179		             options = { dependencies = true }
1180		          "#;
1181		assert!(toml::from_str::<Metadata>(src).is_err());
1182
1183		let src = r#"
1184		             bundle-id = "test.workspace.main.crate"
1185		             [assets]
1186						 foo = "bar"
1187						 boo = true
1188		             options = { }
1189		          "#;
1190		assert!(toml::from_str::<Metadata>(src).is_err());
1191
1192
1193		let src = r#"
1194		             [playdate]
1195		             bundle-id = "test.workspace.main.crate"
1196		             [playdate.assets]
1197		             [playdate.assets.options] # err
1198		          "#;
1199		assert!(toml::from_str::<CrateMetadata>(src).is_err());
1200	}
1201
1202	#[test]
1203	fn meta_options_assets() {
1204		let src = r#"
1205		             bundle-id = "test.workspace.main.crate"
1206		             [options]
1207		             assets = {}
1208		          "#;
1209
1210		assert!(toml::from_str::<Metadata>(src).is_ok());
1211	}
1212
1213	#[test]
1214	fn meta_assets_options_mix() {
1215		let src = r#"
1216		             bundle-id = "test.workspace.main.crate"
1217		             [options]
1218		             assets = {}
1219		             [assets]
1220		             options = {}
1221		          "#;
1222
1223		assert!(toml::from_str::<Metadata>(src).is_err());
1224	}
1225
1226
1227	#[test]
1228	fn meta_assets_maps() {
1229		let src = r#"
1230		             [assets]
1231		             included = true
1232		             excluded = false
1233		             other = "from/path"
1234		             [dev-assets]
1235		             a = true
1236		             b = false
1237		             c = "/c/path"
1238		          "#;
1239
1240		let m = toml::from_str::<Metadata>(src).unwrap();
1241
1242		assert_matches!(m.assets(), AssetsRules::Map(_));
1243		match m.assets() {
1244			AssetsRules::Map(rules) => {
1245				assert_eq!(3, rules.len());
1246				assert_eq!(Some(&RuleValue::Boolean(true)), rules.get("included"));
1247				assert_eq!(Some(&RuleValue::Boolean(false)), rules.get("excluded"));
1248				assert_eq!(Some(&RuleValue::String("from/path".into())), rules.get("other"));
1249			},
1250			_ => unreachable!(),
1251		}
1252
1253		assert_matches!(m.dev_assets(), AssetsRules::Map(_));
1254		match m.dev_assets() {
1255			AssetsRules::Map(rules) => {
1256				assert_eq!(3, rules.len());
1257				assert_eq!(Some(&RuleValue::Boolean(true)), rules.get("a"));
1258				assert_eq!(Some(&RuleValue::Boolean(false)), rules.get("b"));
1259				assert_eq!(Some(&RuleValue::String("/c/path".into())), rules.get("c"));
1260			},
1261			_ => unreachable!(),
1262		}
1263	}
1264
1265	#[test]
1266	fn meta_assets_lists() {
1267		let src = r#"
1268		             assets = ["a", "b", "c"]
1269		             dev-assets = ["d", "e", "f"]
1270		          "#;
1271
1272		let m = toml::from_str::<Metadata>(src).unwrap();
1273
1274		assert_matches!(m.assets(), AssetsRules::List(_));
1275		assert_matches!(m.dev_assets(), AssetsRules::List(_));
1276		match m.assets() {
1277			AssetsRules::List(rules) => assert_eq!(&["a", "b", "c"], &rules[..]),
1278			_ => unreachable!(),
1279		}
1280		match m.dev_assets() {
1281			AssetsRules::List(rules) => assert_eq!(&["d", "e", "f"], &rules[..]),
1282			_ => unreachable!(),
1283		}
1284	}
1285
1286	#[test]
1287	fn meta_assets_mix() {
1288		let src = r#"
1289		             assets = ["d", "e", "f"]
1290		             [dev-assets]
1291		             a = true
1292		             b = true
1293		          "#;
1294
1295		let m = toml::from_str::<Metadata>(src).unwrap();
1296
1297		assert_matches!(m.assets(), AssetsRules::List(_));
1298		match m.assets() {
1299			AssetsRules::List(rules) => {
1300				assert_eq!(3, rules.len());
1301				assert_eq!(&["d", "e", "f"], &rules[..]);
1302			},
1303			_ => unreachable!(),
1304		}
1305
1306		assert_matches!(m.dev_assets(), AssetsRules::Map(_));
1307		match m.dev_assets() {
1308			AssetsRules::Map(rules) => {
1309				assert_eq!(2, rules.len());
1310				assert_eq!(Some(&RuleValue::Boolean(true)), rules.get("a"));
1311				assert_eq!(Some(&RuleValue::Boolean(true)), rules.get("b"));
1312			},
1313			_ => unreachable!(),
1314		}
1315	}
1316
1317
1318	#[test]
1319	fn meta_full() {
1320		let src = r#"
1321		             foo = "bar" # custom field
1322		             name = "Crate Name"
1323		             version = "0.1"
1324		             bundle-id = "test.workspace.main.crate"
1325		             description = "Crate description"
1326		             author = "Crate Author"
1327		             image-path = "image/path"
1328		             launch-sound-path = "launch-sound/path"
1329		             content-warning = "Attention!"
1330		             content-warning2 = "Alarm!"
1331		             build-number = 42
1332		             options.assets.dependencies = true
1333		             [assets]
1334		             included = true
1335		             excluded = false
1336		             other = "from/path"
1337		             [dev-assets]
1338		             "dev-included" = true
1339		             [[bin]]
1340		             target = "cargo-target-bin-name"
1341		             name = "Bin Name"
1342		             bundle-id = "test.workspace.main.bin"
1343		             description = "This is a bin"
1344		             [[example]]
1345		             target = "cargo-target-example-name"
1346		             name = "Example Name"
1347		             bundle-id = "test.workspace.main.example"
1348		             description = "This is an example"
1349		             example-extra = 101
1350		          "#;
1351
1352		let m = toml::from_str::<Metadata>(src).unwrap();
1353		assert_eq!(Some("Crate Name"), m.manifest().name());
1354		assert_eq!(Some("0.1"), m.manifest().version());
1355		assert_eq!(Some("test.workspace.main.crate"), m.manifest().bundle_id());
1356		assert_eq!(Some("Crate description"), m.manifest().description());
1357		assert_eq!(Some("Crate Author"), m.manifest().author());
1358		assert_eq!(Some("image/path"), m.manifest().image_path());
1359		assert_eq!(Some("launch-sound/path"), m.manifest().launch_sound_path());
1360		assert_eq!(Some("Attention!"), m.manifest().content_warning());
1361		assert_eq!(Some("Alarm!"), m.manifest().content_warning2());
1362
1363		{
1364			let s = m.manifest().to_manifest_string().unwrap();
1365			println!("meta manifest:\n{}", s.trim())
1366		}
1367
1368
1369		let opts = m.assets_options();
1370		assert!(opts.dependencies());
1371		assert!(!AssetsOptions::default_dependencies());
1372
1373		assert_matches!(m.assets(), AssetsRules::Map(_));
1374		match m.assets() {
1375			AssetsRules::Map(rules) => {
1376				assert_eq!(3, rules.len());
1377				assert_eq!(Some(&RuleValue::Boolean(true)), rules.get("included"));
1378				assert_eq!(Some(&RuleValue::Boolean(false)), rules.get("excluded"));
1379				assert_eq!(Some(&RuleValue::String("from/path".into())), rules.get("other"));
1380			},
1381			_ => unreachable!(),
1382		}
1383		assert_matches!(m.dev_assets(), AssetsRules::Map(rules) if rules.get("dev-included").is_some());
1384
1385		assert_eq!(1, m.bins().len());
1386		assert_eq!(1, m.examples().len());
1387
1388		let bin_trg = m.bin_targets().into_iter().next().unwrap();
1389		assert_eq!("cargo-target-bin-name", bin_trg);
1390
1391		let example_trg = m.example_targets().into_iter().next().unwrap();
1392		assert_eq!("cargo-target-example-name", example_trg);
1393
1394		let (bin_trg_by_iter, bin) = m.bins_iter().and_then(|mut i| i.next()).unwrap().as_parts();
1395		assert_eq!(bin_trg, bin_trg_by_iter);
1396
1397		let (example_trg_by_iter, example) = m.examples_iter().and_then(|mut i| i.next()).unwrap().as_parts();
1398		assert_eq!(example_trg, example_trg_by_iter);
1399
1400
1401		assert_eq!(Some("Bin Name"), bin.name());
1402		assert_eq!(Some("test.workspace.main.bin"), bin.bundle_id());
1403		assert_eq!(Some("This is a bin"), bin.description());
1404		assert!(bin.version().is_none());
1405		assert!(bin.author().is_none());
1406		assert!(bin.image_path().is_none());
1407		assert!(bin.launch_sound_path().is_none());
1408		assert!(bin.content_warning().is_none());
1409		assert!(bin.content_warning2().is_none());
1410		assert!(!bin.has_extra());
1411
1412		{
1413			let s = bin.to_manifest_string().unwrap();
1414			println!("bin over:\n{}", s.trim())
1415		}
1416
1417
1418		assert_eq!(Some("Example Name"), example.name());
1419		assert_eq!(Some("test.workspace.main.example"), example.bundle_id());
1420		assert_eq!(Some("This is an example"), example.description());
1421		assert!(example.version().is_none());
1422		assert!(example.author().is_none());
1423		assert!(example.image_path().is_none());
1424		assert!(example.launch_sound_path().is_none());
1425		assert!(example.content_warning().is_none());
1426		assert!(example.content_warning2().is_none());
1427		assert!(example.has_extra());
1428		let example_extra: HashMap<_, _> = example.iter_extra()
1429		                                          .unwrap()
1430		                                          .into_iter()
1431		                                          .map(|(k, v)| (k.as_ref().to_owned(), v.as_ref().to_owned()))
1432		                                          .collect();
1433		assert_eq!(1, example_extra.len());
1434		assert_eq!(Some(&ExtraValue::Int(101)), example_extra.get("example-extra"));
1435
1436
1437		{
1438			let s = example.to_manifest_string().unwrap();
1439			println!("example over:\n{}", s.trim())
1440		}
1441
1442
1443		// test merged
1444
1445		let bin = m.manifest_for_target(bin_trg, false).unwrap();
1446		assert_eq!(Some("Bin Name"), bin.name());
1447		assert_eq!(Some("0.1"), bin.version());
1448		assert_eq!(Some("test.workspace.main.bin"), bin.bundle_id());
1449		assert_eq!(Some("This is a bin"), bin.description());
1450		assert_eq!(Some("Crate Author"), bin.author());
1451		assert_eq!(Some("image/path"), bin.image_path());
1452		assert_eq!(Some("launch-sound/path"), bin.launch_sound_path());
1453		assert_eq!(Some("Attention!"), bin.content_warning());
1454		assert_eq!(Some("Alarm!"), bin.content_warning2());
1455		{
1456			let s = bin.to_manifest_string().unwrap();
1457			println!("bin manifest:\n{}", s.trim())
1458		}
1459
1460		let example = m.manifest_for_target(example_trg, true).unwrap();
1461		assert_eq!(Some("Example Name"), example.name());
1462		assert_eq!(Some("0.1"), example.version());
1463		assert_eq!(Some("test.workspace.main.example"), example.bundle_id());
1464		assert_eq!(Some("This is an example"), example.description());
1465		assert_eq!(Some("Crate Author"), example.author());
1466		assert_eq!(Some("image/path"), example.image_path());
1467		assert_eq!(Some("launch-sound/path"), example.launch_sound_path());
1468		assert_eq!(Some("Attention!"), example.content_warning());
1469		assert_eq!(Some("Alarm!"), example.content_warning2());
1470		{
1471			let s = example.to_manifest_string().unwrap();
1472			println!("example manifest:\n{}", s.trim())
1473		}
1474
1475
1476		// test merged any kind of target, just named
1477
1478		let example = m.manifest_for_target_any(example_trg).unwrap();
1479		assert_eq!(Some("Example Name"), example.name());
1480		assert_eq!(Some("0.1"), example.version());
1481		assert_eq!(Some("test.workspace.main.example"), example.bundle_id());
1482		assert_eq!(Some("This is an example"), example.description());
1483		assert_eq!(Some("Crate Author"), example.author());
1484		assert_eq!(Some("image/path"), example.image_path());
1485		assert_eq!(Some("launch-sound/path"), example.launch_sound_path());
1486		assert_eq!(Some("Attention!"), example.content_warning());
1487		assert_eq!(Some("Alarm!"), example.content_warning2());
1488		{
1489			let s = example.to_manifest_string().unwrap();
1490			println!("example manifest:\n{}", s.trim())
1491		}
1492
1493		let missing = m.manifest_for_target_any("missing, wrong name").unwrap();
1494		assert_eq!(Some("Crate Name"), missing.name());
1495		assert_eq!(Some("0.1"), missing.version());
1496		assert_eq!(Some("test.workspace.main.crate"), missing.bundle_id());
1497		assert_eq!(Some("Crate description"), missing.description());
1498		assert_eq!(Some("Crate Author"), missing.author());
1499		assert_eq!(Some("image/path"), missing.image_path());
1500		assert_eq!(Some("launch-sound/path"), missing.launch_sound_path());
1501		assert_eq!(Some("Attention!"), missing.content_warning());
1502		assert_eq!(Some("Alarm!"), missing.content_warning2());
1503		{
1504			let s = missing.to_manifest_string().unwrap();
1505			println!("missing (base meta) manifest:\n{}", s.trim())
1506		}
1507		assert_eq!(m.manifest().into_owned(), missing.into_owned());
1508	}
1509}