1pub mod yaml;
10
11use yaml::*;
12use yaml_rust::ScanError;
13
14#[derive(Copy, Clone, Debug)]
16pub enum TbdVersion {
17 V1,
18 V2,
19 V3,
20 V4,
21}
22
23pub enum TbdVersionedRecord {
27 V1(TbdVersion1),
28 V2(TbdVersion2),
29 V3(TbdVersion3),
30 V4(TbdVersion4),
31}
32
33#[derive(Debug)]
35pub enum ParseError {
36 YamlError(yaml_rust::ScanError),
37 DocumentCountMismatch,
38 Serde(serde_yaml::Error),
39}
40
41impl std::fmt::Display for ParseError {
42 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43 match self {
44 Self::YamlError(e) => e.fmt(f),
45 Self::DocumentCountMismatch => {
46 f.write_str("mismatch in expected document count when parsing YAML")
47 }
48 Self::Serde(e) => e.fmt(f),
49 }
50 }
51}
52
53impl std::error::Error for ParseError {}
54
55impl From<yaml_rust::ScanError> for ParseError {
56 fn from(e: ScanError) -> Self {
57 Self::YamlError(e)
58 }
59}
60
61impl From<serde_yaml::Error> for ParseError {
62 fn from(e: serde_yaml::Error) -> Self {
63 Self::Serde(e)
64 }
65}
66
67const TBD_V2_DOCUMENT_START: &str = "--- !tapi-tbd-v2";
68const TBD_V3_DOCUMENT_START: &str = "--- !tapi-tbd-v3";
69const TBD_V4_DOCUMENT_START: &str = "--- !tapi-tbd";
70
71pub fn parse_str(data: &str) -> Result<Vec<TbdVersionedRecord>, ParseError> {
75 let yamls = yaml_rust::YamlLoader::load_from_str(data)?;
86
87 let mut document_versions = vec![];
90
91 for line in data.lines() {
92 if line.starts_with("---") {
94 let version = if line.starts_with(TBD_V2_DOCUMENT_START) {
95 TbdVersion::V2
96 } else if line.starts_with(TBD_V3_DOCUMENT_START) {
97 TbdVersion::V3
98 } else if line.starts_with(TBD_V4_DOCUMENT_START) {
99 TbdVersion::V4
100 } else {
101 TbdVersion::V1
103 };
104
105 document_versions.push(version);
106 }
107 }
108
109 if document_versions.len() == yamls.len() - 1 {
113 document_versions.insert(0, TbdVersion::V1);
114 } else if document_versions.len() != yamls.len() {
115 return Err(ParseError::DocumentCountMismatch);
116 }
117
118 let mut res = vec![];
119
120 for (index, value) in yamls.iter().enumerate() {
121 let mut s = String::new();
123 yaml_rust::YamlEmitter::new(&mut s).dump(value).unwrap();
124
125 res.push(match document_versions[index] {
126 TbdVersion::V1 => TbdVersionedRecord::V1(serde_yaml::from_str(&s)?),
127 TbdVersion::V2 => TbdVersionedRecord::V2(serde_yaml::from_str(&s)?),
128 TbdVersion::V3 => TbdVersionedRecord::V3(serde_yaml::from_str(&s)?),
129 TbdVersion::V4 => TbdVersionedRecord::V4(serde_yaml::from_str(&s)?),
130 })
131 }
132
133 Ok(res)
134}
135
136#[cfg(test)]
137mod tests {
138 use {
139 super::*,
140 apple_sdk::{AppleSdk, SdkSearch, SdkSearchLocation, SimpleSdk},
141 rand::seq::SliceRandom,
142 rayon::prelude::*,
143 };
144
145 #[test]
146 fn test_parse_apple_sdk_tbds() {
147 let sdks = SdkSearch::empty()
150 .location(SdkSearchLocation::SystemXcodes)
151 .location(SdkSearchLocation::CommandLineTools)
152 .search::<SimpleSdk>()
153 .unwrap();
154
155 sdks.into_par_iter().for_each(|sdk| {
156 let mut tbd_paths = walkdir::WalkDir::new(sdk.path())
157 .into_iter()
158 .filter_map(|entry| {
159 let entry = entry.unwrap();
160
161 let file_name = entry.file_name().to_string_lossy();
162 if file_name.ends_with(".tbd") {
163 Some(entry.path().to_path_buf())
164 } else {
165 None
166 }
167 })
168 .collect::<Vec<_>>();
169
170 let percentage = if let Ok(percentage) = std::env::var("TBD_SAMPLE_PERCENTAGE") {
173 percentage.parse::<usize>().unwrap()
174 } else {
175 10
176 };
177
178 let mut rng = rand::thread_rng();
179 tbd_paths.shuffle(&mut rng);
180
181 for path in tbd_paths.iter().take(tbd_paths.len() * percentage / 100) {
182 eprintln!("parsing {}", path.display());
183 let data = std::fs::read(path).unwrap();
184 let data = String::from_utf8(data).unwrap();
185
186 parse_str(&data).unwrap_or_else(|e| {
187 eprintln!("path: {}", path.display());
188 eprint!("{}", data);
189 eprint!("{:?}", e);
190 panic!("parse error");
191 });
192 }
193 });
194 }
195}