1use crate::msbuild;
2use nom::{
3 IResult, Parser,
4 branch::alt,
5 bytes::complete::{is_not, tag, take_until},
6 character::complete::{self, char},
7 combinator::{self, recognize},
8 error::{Error, ParseError},
9 sequence::{self, pair},
10};
11
12const ACTIVE_CFG_TAG: &str = ".ActiveCfg";
13const BUILD_TAG: &str = ".Build.0";
14const DEPLOY_TAG: &str = ".Deploy.0";
15
16#[derive(Debug)]
18pub enum Node<'a> {
19 Comment(&'a str),
20 Version(&'a str, &'a str),
21 FirstLine(&'a str),
22 Global(Vec<Node<'a>>),
23 Project(Box<Node<'a>>, Vec<Node<'a>>),
24 ProjectBegin(&'a str, &'a str, &'a str, &'a str),
25 Section(Box<Node<'a>>, Vec<Node<'a>>),
26 SectionBegin(Vec<&'a str>, &'a str),
27 SectionContent(&'a str, &'a str),
28 Solution(Box<Node<'a>>, Vec<Node<'a>>),
29}
30
31#[derive(Debug, Clone, Default)]
33pub struct Sol<'a> {
34 pub path: &'a str,
37 pub format: &'a str,
38 pub product: &'a str,
39 pub projects: Vec<Prj<'a>>,
40 pub versions: Vec<Ver<'a>>,
41 pub solution_configs: Vec<Conf<'a>>,
42 pub project_configs: Vec<PrjConfAggregate<'a>>,
43}
44
45#[derive(Debug, Copy, Clone)]
47pub struct Ver<'a> {
48 pub name: &'a str,
49 pub ver: &'a str,
50}
51
52#[derive(Debug, Clone)]
54pub struct PrjConfAggregate<'a> {
55 pub project_id: &'a str,
56 pub configs: Vec<PrjConf<'a>>,
57}
58
59#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)]
61pub struct Conf<'a> {
62 pub config: &'a str,
63 pub platform: &'a str,
64}
65
66#[derive(Debug, Clone, Default)]
68pub struct Prj<'a> {
69 pub type_id: &'a str,
70 pub type_descr: &'a str,
71 pub id: &'a str,
72 pub name: &'a str,
73 pub path_or_uri: &'a str,
74 pub items: Vec<&'a str>,
75 pub depends_from: Vec<&'a str>,
76}
77
78impl<'a> Prj<'a> {
79 #[must_use]
80 pub fn new(id: &'a str, type_id: &'a str) -> Self {
81 let type_descr = msbuild::describe_project(type_id);
82
83 Self {
84 type_id,
85 type_descr,
86 id,
87 ..Default::default()
88 }
89 }
90
91 #[must_use]
92 pub fn from_begin(head: &Node<'a>) -> Option<Self> {
93 if let Node::ProjectBegin(project_type, name, path_or_uri, id) = head {
94 let prj = Prj::from(project_type, name, path_or_uri, id);
95 Some(prj)
96 } else {
97 None
98 }
99 }
100
101 #[must_use]
102 pub fn from(project_type: &'a str, name: &'a str, path_or_uri: &'a str, id: &'a str) -> Self {
103 let mut prj = Prj::new(id, project_type);
104 prj.name = name;
105 prj.path_or_uri = path_or_uri;
106
107 prj
108 }
109}
110
111impl<'a> Ver<'a> {
112 #[must_use]
113 pub fn new(name: &'a str, ver: &'a str) -> Self {
114 Self { name, ver }
115 }
116
117 #[must_use]
118 pub fn from(name: &'a str, val: &'a str) -> Self {
119 Ver::new(name, val)
120 }
121}
122
123impl<'a> From<&'a str> for Conf<'a> {
124 fn from(s: &'a str) -> Self {
125 pipe_terminated::<Error<&str>>(s)
126 .map(|(platform, config)| Self { config, platform })
127 .unwrap_or_default()
128 }
129}
130
131impl<'a> Conf<'a> {
132 #[must_use]
133 pub fn new(configuration: &'a str, platform: &'a str) -> Self {
134 Self {
135 config: configuration,
136 platform,
137 }
138 }
139
140 #[must_use]
141 pub fn from_node(node: &Node<'a>) -> Option<Self> {
142 if let Node::SectionContent(left, _) = node {
143 let conf = Conf::from(*left);
144 Some(conf)
145 } else {
146 None
147 }
148 }
149}
150
151#[derive(Default, PartialEq, Debug, Clone)]
152pub struct PrjConf<'a> {
153 pub id: &'a str,
154 pub solution_config: &'a str,
155 pub project_config: &'a str,
156 pub platform: &'a str,
157 pub tag: ProjectConfigTag,
158}
159
160#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
161pub enum ProjectConfigTag {
162 #[default]
163 ActiveCfg,
164 Build,
165 Deploy,
166}
167
168impl<'a> PrjConfAggregate<'a> {
169 #[must_use]
170 pub fn from_id_and_configs(project_id: &'a str, configs: Vec<PrjConf<'a>>) -> Self {
171 Self {
172 project_id,
173 configs,
174 }
175 }
176
177 #[must_use]
178 pub fn handle_project_config_platform(node: &Node<'a>) -> Option<Self> {
179 if let Node::SectionContent(left, right) = node {
180 PrjConfAggregate::from_project_configuration_platform(left, right)
181 } else {
182 None
183 }
184 }
185
186 #[must_use]
187 pub fn handle_project_config(node: &Node<'a>) -> Option<Self> {
188 if let Node::SectionContent(left, right) = node {
189 PrjConfAggregate::from_project_configuration(left, right)
190 } else {
191 None
192 }
193 }
194
195 fn from_project_configuration_platform(k: &'a str, v: &'a str) -> Option<Self> {
196 let r = PrjConfAggregate::parse_project_configuration_platform::<Error<&str>>(k, v);
197 Self::new(r)
198 }
199
200 fn from_project_configuration(k: &'a str, v: &'a str) -> Option<Self> {
201 let r = PrjConfAggregate::parse_project_configuration::<Error<&str>>(k, v);
202 Self::new(r)
203 }
204
205 fn new(r: IResult<&'a str, PrjConf<'a>, Error<&'a str>>) -> Option<Self> {
206 r.ok().map(|(_, pc)| Self {
207 project_id: pc.id,
208 configs: vec![pc],
209 })
210 }
211
212 fn parse_project_configuration_platform<'b, E>(
216 key: &'b str,
217 value: &'b str,
218 ) -> IResult<&'b str, PrjConf<'b>, E>
219 where
220 E: ParseError<&'b str> + std::fmt::Debug,
221 {
222 let parser =
223 sequence::separated_pair(guid, char('.'), pair(pipe_terminated, tag_terminated));
224
225 let project_conf = Conf::from(value);
226
227 combinator::map(parser, |(project_id, (solution_config, platform))| {
228 PrjConf {
229 id: project_id,
230 solution_config,
231 project_config: project_conf.config,
232 platform,
233 tag: define_tag(key),
234 }
235 })
236 .parse(key)
237 }
238
239 fn parse_project_configuration<'b, E>(
240 key: &'b str,
241 value: &'b str,
242 ) -> IResult<&'b str, PrjConf<'b>, E>
243 where
244 E: ParseError<&'b str> + std::fmt::Debug,
245 {
246 let parser = sequence::separated_pair(guid, char('.'), tag_terminated);
247
248 let project_conf = Conf::from(value);
249
250 combinator::map(parser, |(project_id, solution_config)| PrjConf {
251 id: project_id,
252 solution_config,
253 project_config: project_conf.config,
254 platform: project_conf.platform,
255 tag: define_tag(key),
256 })
257 .parse(key)
258 }
259}
260
261fn define_tag(key: &str) -> ProjectConfigTag {
262 if key.ends_with(BUILD_TAG) {
263 ProjectConfigTag::Build
264 } else if key.ends_with(DEPLOY_TAG) {
265 ProjectConfigTag::Deploy
266 } else {
267 ProjectConfigTag::ActiveCfg
268 }
269}
270
271fn guid<'a, E>(input: &'a str) -> IResult<&'a str, &'a str, E>
272where
273 E: ParseError<&'a str> + std::fmt::Debug,
274{
275 recognize(sequence::delimited(
276 complete::char('{'),
277 is_not("{}"),
278 complete::char('}'),
279 ))
280 .parse(input)
281}
282
283fn tag_terminated<'a, E>(input: &'a str) -> IResult<&'a str, &'a str, E>
284where
285 E: ParseError<&'a str> + std::fmt::Debug,
286{
287 sequence::terminated(
288 alt((
289 take_until(ACTIVE_CFG_TAG),
290 take_until(BUILD_TAG),
291 take_until(DEPLOY_TAG),
292 )),
293 alt((tag(ACTIVE_CFG_TAG), tag(BUILD_TAG), tag(DEPLOY_TAG))),
294 )
295 .parse(input)
296}
297
298fn pipe_terminated<'a, E>(input: &'a str) -> IResult<&'a str, &'a str, E>
299where
300 E: ParseError<&'a str> + std::fmt::Debug,
301{
302 sequence::terminated(is_not("|"), char('|')).parse(input)
303}
304
305impl Node<'_> {
306 #[must_use]
307 pub fn is_section(&self, name: &str) -> bool {
308 if let Node::SectionBegin(names, _) = self {
309 names.iter().any(|n| *n == name)
310 } else {
311 false
312 }
313 }
314}
315
316#[cfg(test)]
317mod tests {
318 use super::*;
319 use rstest::rstest;
320
321 #[rstest]
322 #[case("Release|Any CPU", Conf { config: "Release", platform: "Any CPU" })]
323 #[case("", Conf { config: "", platform: "" })]
324 #[case("Release Any CPU", Conf { config: "", platform: "" })]
325 #[case("Release|Any CPU|test", Conf { config: "Release", platform: "Any CPU|test" })]
326 #[trace]
327 fn from_configuration_tests(#[case] i: &str, #[case] expected: Conf) {
328 let c = Conf::from(i);
332
333 assert_eq!(c, expected);
335 }
336
337 #[test]
338 fn from_project_configurations_correct() {
339 let k = "{27060CA7-FB29-42BC-BA66-7FC80D498354}.Debug|Any CPU.ActiveCfg";
341 let v = "Debug|x86";
342
343 let c = PrjConfAggregate::from_project_configuration_platform(k, v);
345
346 assert!(c.is_some());
348 let c = c.unwrap();
349 assert_eq!(c.project_id, "{27060CA7-FB29-42BC-BA66-7FC80D498354}");
350 assert_eq!(c.configs.len(), 1);
351 assert_eq!(c.configs[0].solution_config, "Debug");
352 assert_eq!(c.configs[0].project_config, "Debug");
353 assert_eq!(c.configs[0].platform, "Any CPU");
354 }
355
356 #[test]
357 fn from_project_configurations_config_with_dot() {
358 let k = "{27060CA7-FB29-42BC-BA66-7FC80D498354}.Debug .NET 4.0|Any CPU.ActiveCfg";
360 let v = "Debug|x86";
361
362 let c = PrjConfAggregate::from_project_configuration_platform(k, v);
364
365 assert!(c.is_some());
367 let c = c.unwrap();
368 assert_eq!(c.project_id, "{27060CA7-FB29-42BC-BA66-7FC80D498354}");
369 assert_eq!(c.configs.len(), 1);
370 assert_eq!(c.configs[0].solution_config, "Debug .NET 4.0");
371 assert_eq!(c.configs[0].project_config, "Debug");
372 assert_eq!(c.configs[0].platform, "Any CPU");
373 }
374
375 #[test]
376 fn from_project_configurations_platform_with_dot_active() {
377 let k = "{7C2EF610-BCA0-4D1F-898A-DE9908E4970C}.Release|.NET.ActiveCfg";
379 let v = "Release|x86";
380
381 let c = PrjConfAggregate::from_project_configuration_platform(k, v);
383
384 assert!(c.is_some());
386 let c = c.unwrap();
387 assert_eq!(c.project_id, "{7C2EF610-BCA0-4D1F-898A-DE9908E4970C}");
388 assert_eq!(c.configs.len(), 1);
389 assert_eq!(c.configs[0].solution_config, "Release");
390 assert_eq!(c.configs[0].project_config, "Release");
391 assert_eq!(c.configs[0].platform, ".NET");
392 }
393
394 #[test]
395 fn from_project_configurations_without_platform() {
396 let k = "{5228E9CE-A216-422F-A5E6-58E95E2DD71D}.DLL Debug.ActiveCfg";
398 let v = "Debug|x86";
399
400 let c = PrjConfAggregate::from_project_configuration_platform(k, v);
402
403 assert!(c.is_none());
405 }
406
407 #[test]
408 fn guid_test() {
409 let s = "{7C2EF610-BCA0-4D1F-898A-DE9908E4970C}.Release|.NET.Build.0";
411
412 let result = guid::<Error<&str>>(s);
414
415 assert_eq!(
417 result,
418 Ok((
419 ".Release|.NET.Build.0",
420 "{7C2EF610-BCA0-4D1F-898A-DE9908E4970C}",
421 ))
422 );
423 }
424
425 #[rstest]
426 #[case(".NET.Build.0", ".NET")]
427 #[case(".NET.ActiveCfg", ".NET")]
428 #[trace]
429 fn tag_terminated_tests(#[case] i: &str, #[case] expected: &str) {
430 let result = tag_terminated::<Error<&str>>(i);
434
435 assert_eq!(result, Ok(("", expected)));
437 }
438
439 #[rstest]
440 #[case("{7C2EF610-BCA0-4D1F-898A-DE9908E4970C}.Release|.NET.Build.0", "Release|.NET", PrjConf { id: "{7C2EF610-BCA0-4D1F-898A-DE9908E4970C}", solution_config: "Release", project_config: "Release", platform: ".NET", tag: ProjectConfigTag::Build })]
441 #[case("{7C2EF610-BCA0-4D1F-898A-DE9908E4970C}.SolutionRelease|.NET.Build.0", "ProjectRelease|.NET", PrjConf { id: "{7C2EF610-BCA0-4D1F-898A-DE9908E4970C}", solution_config: "SolutionRelease", project_config: "ProjectRelease", platform: ".NET", tag: ProjectConfigTag::Build })]
442 #[case("{60BB14A5-0871-4656-BC38-4F0958230F9A}.Debug|ARM.Deploy.0", "Debug|ARM", PrjConf { id: "{60BB14A5-0871-4656-BC38-4F0958230F9A}", solution_config: "Debug", project_config: "Debug", platform: "ARM", tag: ProjectConfigTag::Deploy })]
443 #[case("{7C2EF610-BCA0-4D1F-898A-DE9908E4970C}.Release|.NET.ActiveCfg", "Release|.NET", PrjConf { id: "{7C2EF610-BCA0-4D1F-898A-DE9908E4970C}", solution_config: "Release", project_config: "Release", platform: ".NET", tag: ProjectConfigTag::ActiveCfg })]
444 #[trace]
445 fn project_configs_parse_project_configuration_platform_tests(
446 #[case] k: &str,
447 #[case] v: &str,
448 #[case] expected: PrjConf,
449 ) {
450 let result = PrjConfAggregate::parse_project_configuration_platform::<Error<&str>>(k, v);
454
455 assert_eq!(result, Ok(("", expected)));
457 }
458
459 #[rstest]
460 #[case("{5228E9CE-A216-422F-A5E6-58E95E2DD71D}.DLL Debug.ActiveCfg", "Debug|x64", PrjConf { id: "{5228E9CE-A216-422F-A5E6-58E95E2DD71D}", solution_config: "DLL Debug", project_config: "Debug", platform: "x64", tag: ProjectConfigTag::ActiveCfg })]
461 #[trace]
462 fn project_configs_parse_project_configuration_tests(
463 #[case] k: &str,
464 #[case] v: &str,
465 #[case] expected: PrjConf,
466 ) {
467 let result = PrjConfAggregate::parse_project_configuration::<Error<&str>>(k, v);
471
472 assert_eq!(result, Ok(("", expected)));
474 }
475}