selectors/
ir.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use fidl_fuchsia_diagnostics as fdiagnostics;
6use std::borrow::Cow;
7use std::fmt::Debug;
8
9#[derive(Debug, Eq, PartialEq)]
10pub enum Segment<'a> {
11    ExactMatch(Cow<'a, str>),
12    Pattern(Cow<'a, str>),
13}
14
15fn contains_unescaped(s: &str, unescaped_char: char) -> bool {
16    let mut iter = s.chars();
17    while let Some(c) = iter.next() {
18        match c {
19            c2 if c2 == unescaped_char => return true,
20            '\\' => {
21                // skip escaped characters
22                let _ = iter.next();
23            }
24            _ => {}
25        }
26    }
27    false
28}
29
30impl<'a> From<&'a str> for Segment<'a> {
31    fn from(s: &'a str) -> Segment<'a> {
32        if contains_unescaped(s, '*') {
33            return Segment::Pattern(Cow::Owned(unescape(s, &['*'])));
34        }
35        if !s.contains('\\') {
36            return Segment::ExactMatch(Cow::from(s));
37        }
38        Segment::ExactMatch(Cow::Owned(unescape(s, &[])))
39    }
40}
41
42#[derive(Debug, Eq, PartialEq)]
43pub enum TreeNames<'a> {
44    Some(Vec<Cow<'a, str>>),
45    All,
46}
47
48impl<'a> From<Vec<&'a str>> for TreeNames<'a> {
49    fn from(vec: Vec<&'a str>) -> TreeNames<'a> {
50        let mut payload = vec![];
51        for name in vec {
52            if name.contains('\\') {
53                payload.push(Cow::Owned(unescape(name, &[])));
54            } else {
55                payload.push(Cow::Borrowed(name));
56            }
57        }
58        TreeNames::Some(payload)
59    }
60}
61
62// Given a escaped string, removes all the escaped characters (`\\`) and returns a new string
63// without them. It'll keep characters present in the `except` list escaped.
64fn unescape(value: &str, except: &[char]) -> String {
65    let mut result = String::with_capacity(value.len());
66    let mut iter = value.chars();
67    while let Some(c) = iter.next() {
68        match c {
69            '\\' => {
70                // push unescaped character since we are constructing an exact match.
71                if let Some(c) = iter.next() {
72                    if except.contains(&c) {
73                        result.push('\\')
74                    }
75                    result.push(c);
76                }
77            }
78            c => result.push(c),
79        }
80    }
81    result
82}
83
84#[derive(Debug, Eq, PartialEq)]
85pub struct TreeSelector<'a> {
86    pub node: Vec<Segment<'a>>,
87    pub property: Option<Segment<'a>>,
88    pub tree_names: Option<TreeNames<'a>>,
89}
90
91#[derive(Debug, Eq, PartialEq)]
92pub struct ComponentSelector<'a> {
93    pub segments: Vec<Segment<'a>>,
94}
95
96#[derive(Debug, Eq, PartialEq)]
97pub struct Selector<'a> {
98    pub component: ComponentSelector<'a>,
99    pub tree: TreeSelector<'a>,
100}
101
102impl From<Selector<'_>> for fdiagnostics::Selector {
103    fn from(mut selector: Selector<'_>) -> fdiagnostics::Selector {
104        let tree_names = selector.tree.tree_names.take();
105        fdiagnostics::Selector {
106            component_selector: Some(selector.component.into()),
107            tree_selector: Some(selector.tree.into()),
108            tree_names: tree_names.map(|names| names.into()),
109            ..Default::default()
110        }
111    }
112}
113
114impl From<ComponentSelector<'_>> for fdiagnostics::ComponentSelector {
115    fn from(component_selector: ComponentSelector<'_>) -> fdiagnostics::ComponentSelector {
116        fdiagnostics::ComponentSelector {
117            moniker_segments: Some(
118                component_selector.segments.into_iter().map(|segment| segment.into()).collect(),
119            ),
120            ..Default::default()
121        }
122    }
123}
124
125impl From<TreeSelector<'_>> for fdiagnostics::TreeSelector {
126    fn from(tree_selector: TreeSelector<'_>) -> fdiagnostics::TreeSelector {
127        let node_path = tree_selector.node.into_iter().map(|s| s.into()).collect();
128        match tree_selector.property {
129            None => fdiagnostics::TreeSelector::SubtreeSelector(fdiagnostics::SubtreeSelector {
130                node_path,
131            }),
132            Some(property) => {
133                fdiagnostics::TreeSelector::PropertySelector(fdiagnostics::PropertySelector {
134                    node_path,
135                    target_properties: property.into(),
136                })
137            }
138        }
139    }
140}
141
142impl From<TreeNames<'_>> for fdiagnostics::TreeNames {
143    fn from(tree_names: TreeNames<'_>) -> fdiagnostics::TreeNames {
144        match tree_names {
145            TreeNames::All => fdiagnostics::TreeNames::All(fdiagnostics::All {}),
146            TreeNames::Some(names) => {
147                fdiagnostics::TreeNames::Some(names.iter().map(|n| n.to_string()).collect())
148            }
149        }
150    }
151}
152
153impl From<Segment<'_>> for fdiagnostics::StringSelector {
154    fn from(segment: Segment<'_>) -> fdiagnostics::StringSelector {
155        match segment {
156            Segment::ExactMatch(s) => fdiagnostics::StringSelector::ExactMatch(s.into_owned()),
157            Segment::Pattern(s) => fdiagnostics::StringSelector::StringPattern(s.into_owned()),
158        }
159    }
160}
161
162#[cfg(test)]
163mod tests {
164    use super::*;
165
166    #[fuchsia::test]
167    fn convert_string_to_segment() {
168        assert_eq!(Segment::ExactMatch(Cow::Borrowed("abc")), "abc".into());
169        assert_eq!(Segment::Pattern("a*c".into()), "a*c".into());
170        assert_eq!(Segment::ExactMatch(Cow::Owned("ac*".into())), "ac\\*".into());
171        assert_eq!(Segment::Pattern("a\\*c*".into()), "a\\*c*".into());
172    }
173}