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#[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#[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 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#[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 #[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(Vec<S>),
318 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 #[cfg_attr(feature = "serde", serde(default))]
567 pub workspace: bool,
568 #[cfg_attr(feature = "serde", serde(default))]
569 pub assets: AssetsOptions,
570 }
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 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#[derive(Debug, Clone, Default, PartialEq)]
656#[cfg_attr(feature = "serde", derive(Deserialize))]
657pub struct Support {
658 }
661
662
663#[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 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 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 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 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}