apple_bundle/
lib.rs

1//! # Apple Bundle Resources
2//!
3//! Resources located in an app, framework, or plugin bundle.
4//!
5//! A bundle is a directory with a standardized hierarchical structure that holds
6//! executable code and the resources used by that code. The bundle contains resources
7//! that may be accessed at runtime, such as images, audio files, user interface files,
8//! and property lists.
9//!
10//! Official documentation: https://developer.apple.com/documentation/bundleresources
11
12/// Entitlements
13pub mod entitlements;
14/// Information Property List
15pub mod info_plist;
16/// Prelude
17pub mod prelude {
18    pub use super::entitlements::prelude::*;
19    pub use super::info_plist::prelude::*;
20    #[cfg(feature = "plist")]
21    pub use plist;
22}
23#[cfg(feature = "plist")]
24pub use plist::{
25    self, from_bytes, from_file, from_reader, from_reader_xml, to_file_binary, to_file_xml,
26    to_writer_binary, to_writer_xml,
27};
28
29use serde::{ser::SerializeSeq, Serialize, Serializer};
30
31fn serialize_enum_option<S: Serializer, T: Serialize>(
32    value: &Option<T>,
33    s: S,
34) -> Result<S::Ok, S::Error> {
35    s.serialize_str(&serde_plain::to_string(value).unwrap())
36}
37
38fn serialize_vec_enum_option<S: Serializer, T: Serialize>(
39    value: &Option<Vec<T>>,
40    s: S,
41) -> Result<S::Ok, S::Error> {
42    match value {
43        Some(ref val) => {
44            let mut seq = s.serialize_seq(Some(val.len()))?;
45            for element in val.iter() {
46                seq.serialize_element(&serde_plain::to_string(element).unwrap())?;
47            }
48            seq.end()
49        }
50        None => panic!("unsupported"),
51    }
52}
53
54fn serialize_option<S, T>(value: &Option<T>, ser: S) -> Result<S::Ok, S::Error>
55where
56    S: Serializer,
57    T: Serialize,
58{
59    value
60        .as_ref()
61        .expect(r#"`serialize_option` must be used with `skip_serializing_if = "Option::is_none"`"#)
62        .serialize(ser)
63}
64
65#[cfg(test)]
66mod tests {
67    use super::prelude::*;
68
69    pub const PLIST_FILE_NAME: &str = "Info.plist";
70
71    pub const PLIST_TEST_EXAMPLE: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
72<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
73<plist version="1.0">
74<dict>
75    <key>CFBundlePackageType</key>
76    <string>APPL</string>
77    <key>LSApplicationCategoryType</key>
78    <string>public.app-category.business</string>
79    <key>CFBundleIdentifier</key>
80    <string>com.test.test-id</string>
81    <key>CFBundleName</key>
82    <string>Test</string>
83    <key>CFBundleVersion</key>
84    <string>1</string>
85    <key>CFBundleShortVersionString</key>
86    <string>1.0</string>
87    <key>CFBundleInfoDictionaryVersion</key>
88    <string>1.0</string>
89    <key>CFBundleDevelopmentRegion</key>
90    <string>en</string>
91    <key>UILaunchStoryboardName</key>
92    <string>LaunchScreen</string>
93    <key>UISupportedInterfaceOrientations</key>
94    <array>
95        <string>UIInterfaceOrientationPortrait</string>
96        <string>UIInterfaceOrientationPortraitUpsideDown</string>
97        <string>UIInterfaceOrientationLandscapeLeft</string>
98        <string>UIInterfaceOrientationLandscapeRight</string>
99    </array>
100    <key>UIRequiresFullScreen</key>
101    <false/>
102    <key>CFBundleExecutable</key>
103    <string>test</string>
104</dict>
105</plist>"#;
106
107    #[test]
108    fn test_plist_equality() {
109        let dir = tempfile::tempdir().unwrap();
110        let properties = InfoPlist {
111            localization: Localization {
112                bundle_development_region: Some("en".to_owned()),
113                ..Default::default()
114            },
115            launch: Launch {
116                bundle_executable: Some("test".to_owned()),
117                ..Default::default()
118            },
119            identification: Identification {
120                bundle_identifier: "com.test.test-id".to_owned(),
121                ..Default::default()
122            },
123            bundle_version: BundleVersion {
124                bundle_version: Some("1".to_owned()),
125                bundle_info_dictionary_version: Some("1.0".to_owned()),
126                bundle_short_version_string: Some("1.0".to_owned()),
127                ..Default::default()
128            },
129            naming: Naming {
130                bundle_name: Some("Test".to_owned()),
131                ..Default::default()
132            },
133            categorization: Categorization {
134                bundle_package_type: Some("APPL".to_owned()),
135                application_category_type: Some(AppCategoryType::Business),
136            },
137            launch_interface: LaunchInterface {
138                launch_storyboard_name: Some("LaunchScreen".to_owned()),
139                ..Default::default()
140            },
141            styling: Styling {
142                requires_full_screen: Some(false),
143                ..Default::default()
144            },
145            orientation: Orientation {
146                supported_interface_orientations: Some(vec![
147                    InterfaceOrientation::Portrait,
148                    InterfaceOrientation::PortraitUpsideDown,
149                    InterfaceOrientation::LandscapeLeft,
150                    InterfaceOrientation::LandscapeRight,
151                ]),
152                ..Default::default()
153            },
154            ..Default::default()
155        };
156        // Create Info.plist file
157        let file_path = dir.path().join(PLIST_FILE_NAME);
158        let file = std::fs::File::create(file_path).unwrap();
159        // Write to Info.plist file
160        plist::to_writer_xml(file, &properties).unwrap();
161        // Read Info.plist
162        let file_path = dir.path().join(PLIST_FILE_NAME);
163        let result = std::fs::read_to_string(&file_path).unwrap();
164        assert_eq!(result, PLIST_TEST_EXAMPLE.replace("    ", "\t"));
165        // Parse Info.plist
166        let got_props: InfoPlist = plist::from_bytes(result.as_bytes()).unwrap();
167        assert_eq!(properties, got_props);
168    }
169}