Skip to main content

bob/
trend.rs

1//! Recognising the ways BOY screens open a trend of some PVs, which bobview
2//! shows in its own trend window instead of running them: a script that
3//! opens a Data Browser, and a command that starts StripTool.
4
5/// The Eclipse command that opens a new Data Browser window.
6const DATABROWSER: &str = "org.csstudio.trends.databrowser2.newWindow";
7
8/// The PVs of a BOY script that opens a Data Browser on them:
9///
10/// ```python
11/// from org.csstudio.opibuilder.scriptUtil import ScriptUtil
12///
13/// command_id="org.csstudio.trends.databrowser2.newWindow"
14/// ScriptUtil.executeEclipseCommand(command_id, ['pvnames', 'A:PLOG,B:PLOG'])
15/// ```
16///
17/// The command may also be given in place, and the script may be
18/// JavaScript. The names are as written, macros and all.
19pub fn databrowser_pvs(script: &str) -> Option<Vec<String>> {
20    if !script.contains(DATABROWSER) {
21        return None;
22    }
23    let call = &script[script.find("executeEclipseCommand")?..];
24    // The value after the "pvnames" parameter name: the next quoted string.
25    let key = call.find("pvnames")?;
26    let rest = call[key + "pvnames".len()..]
27        .trim_start_matches(['\'', '"'])
28        .trim_start();
29    let rest = rest.strip_prefix(',')?.trim_start();
30    let quote = rest.chars().next().filter(|c| matches!(c, '\'' | '"'))?;
31    let body = &rest[1..];
32    let names = &body[..body.find(quote)?];
33    let pvs: Vec<String> = names
34        .split(',')
35        .map(str::trim)
36        .filter(|n| !n.is_empty())
37        .map(str::to_string)
38        .collect();
39    (!pvs.is_empty()).then_some(pvs)
40}
41
42/// The DLS scripts that start StripTool on the PVs they are given.
43const STRIP_TOOLS: &[&str] = &["strip.py", "strip.sh", "buildStripToolconfig.sh"];
44
45/// The PVs of a command that starts StripTool, such as
46/// `strip.py SR04S-VA-GAUGE-01:PLOG SR04S-VA-GAUGE-02:PLOG log_mbar`, or
47/// `None` for any other command. The last word, when it has no `:`, says
48/// how to scale the chart (`log_mbar`) and is not a PV.
49pub fn strip_tool_pvs(command: &str) -> Option<Vec<String>> {
50    let mut words = command.split_whitespace();
51    let program = words.next()?;
52    let name = program.rsplit('/').next().unwrap_or(program);
53    if !STRIP_TOOLS.contains(&name) {
54        return None;
55    }
56    let mut pvs: Vec<String> = words.map(str::to_string).collect();
57    if pvs.last().is_some_and(|w| !w.contains(':')) {
58        pvs.pop();
59    }
60    (!pvs.is_empty()).then_some(pvs)
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66
67    #[test]
68    fn databrowser_scripts_name_their_pvs() {
69        // As in the DLS vacuum screens, with the command in a variable.
70        let py = "from org.csstudio.opibuilder.scriptUtil import ScriptUtil\n\n\
71            command_id=\"org.csstudio.trends.databrowser2.newWindow\"\n\
72            ScriptUtil.executeEclipseCommand(command_id, ['pvnames', \
73            '$(dom)-VA-GAUGE-01:PLOG,$(dom)-VA-GAUGE-02:PLOG, $(dom)-VA-GAUGE-03:PLOG'])";
74        assert_eq!(
75            databrowser_pvs(py).unwrap(),
76            [
77                "$(dom)-VA-GAUGE-01:PLOG",
78                "$(dom)-VA-GAUGE-02:PLOG",
79                "$(dom)-VA-GAUGE-03:PLOG"
80            ]
81        );
82        let js = "ScriptUtil.executeEclipseCommand(\"org.csstudio.trends.databrowser2.newWindow\", \
83            [\"pvnames\", \"A:B\"]);";
84        assert_eq!(databrowser_pvs(js).unwrap(), ["A:B"]);
85        // Other commands, scripts and empty lists are not trends.
86        let other = "ScriptUtil.executeEclipseCommand(\"org.csstudio.other\", ['pvnames', 'A'])";
87        assert_eq!(databrowser_pvs(other), None);
88        assert_eq!(
89            databrowser_pvs("ScriptUtil.closeAssociatedOPI(widget)"),
90            None
91        );
92        let empty = format!("ScriptUtil.executeEclipseCommand(\"{DATABROWSER}\", ['pvnames', ''])");
93        assert_eq!(databrowser_pvs(&empty), None);
94    }
95
96    #[test]
97    fn strip_tool_commands_name_their_pvs() {
98        assert_eq!(
99            strip_tool_pvs("strip.py $(device):PLOG log_mbar").unwrap(),
100            ["$(device):PLOG"]
101        );
102        assert_eq!(
103            strip_tool_pvs("buildStripToolconfig.sh SR04S-VA-GAUGE-01:PLOG SR04S-VA-GAUGE-03:PLOG")
104                .unwrap(),
105            ["SR04S-VA-GAUGE-01:PLOG", "SR04S-VA-GAUGE-03:PLOG"]
106        );
107        assert_eq!(
108            strip_tool_pvs("/dls_sw/prod/tools/strip.sh BL01T:A:M2  BL01T:A:M4 log_mbar").unwrap(),
109            ["BL01T:A:M2", "BL01T:A:M4"]
110        );
111        assert_eq!(strip_tool_pvs("strip.py log_mbar"), None);
112        assert_eq!(strip_tool_pvs("./srCellVacuum 04"), None);
113        assert_eq!(strip_tool_pvs("mystrip.py A:B"), None);
114        assert_eq!(strip_tool_pvs(""), None);
115    }
116}