Skip to main content

perforce_cli/cmd/
where.rs

1use std::{
2    ffi::OsStr,
3    path::PathBuf,
4    process::{Child, Command, Stdio},
5};
6
7use super::SubCommand;
8
9use crate::global::GlobalOpts;
10use crate::spawn::ParameterizedSpawn;
11
12#[cfg_attr(feature = "lt2015_1", doc = "`p4 [g-opts] where [file ...]`")]
13#[cfg_attr(
14    all(feature = "lt2016_1", not(feature = "lt2015_1")),
15    doc = "`p4 [g-opts] where [file …]`"
16)]
17#[cfg_attr(not(feature = "lt2016_1"), doc = "`p4 [g-opts] where [file ...]`")]
18///
19#[cfg_attr(
20    feature = "lt2023_2",
21    doc = "Show where a particular file is located, as determined by the client view.",
22    doc = "",
23    doc = "`p4 where` uses the client view and root (as set in `p4 client`) to print files'",
24    doc = "locations relative to the top of the depot, relative to the top of the client",
25    doc = "workspace, and relative to the top of the local OS directory tree. The command",
26    doc = "does not check to see if the file exists; it merely reports where the file",
27    doc = "*would be* located if it *did* exist.",
28    doc = "",
29    doc = "For each file provided as a parameter, a set of mappings is output. Each set",
30    doc = "of mappings is composed of lines consisting of three parts: the first part",
31    doc = "is the filename expressed in depot syntax, the second part is the filename",
32    doc = "expressed in client syntax, and the third is the local OS path of the file."
33)]
34#[cfg_attr(
35    not(feature = "lt2023_2"),
36    doc = "Show how the specified files are mapped by the client view.",
37    doc = "",
38    doc = "`p4 where` uses the client view and root to print files locations relative to",
39    doc = "the top of the depot, relative to the top of the client workspace, and",
40    doc = "relative to the top of the local OS directory tree. The client mappings are",
41    doc = "set by the `p4 client` command. The `p4 where` command does not check to see",
42    doc = "whether the file exists. Instead, it reports where the file *would be*",
43    doc = "located if it *did* exist.",
44    doc = "",
45    doc = "The command accepts wildcards, such as `p4 where *.html`",
46    doc = "",
47    doc = "For each file provided as a parameter, a set of mappings is output. Each set",
48    doc = "of mappings consists of:",
49    doc = "",
50    doc = "- the filename in depot syntax. such as `//depot/project1/my-file.html`",
51    doc = "- the filename in client syntax, such as `//maria/depot/project1/my-file.html`",
52    doc = "- the filename in local syntax, which is the local operating system path,",
53    doc = "such as `C:\\Users\\maria\\project1\\my-file.html`"
54)]
55#[derive(Debug, Clone, Default)]
56pub struct Where {
57    bin: PathBuf,
58
59    global_opts: GlobalOpts,
60}
61
62impl SubCommand for Where {
63    fn name(&self) -> &str {
64        "where"
65    }
66
67    fn inject_local_args(&self, _: &mut Command) {}
68
69    fn global_opts(&self) -> Option<&GlobalOpts> {
70        Some(&self.global_opts)
71    }
72}
73
74impl<S, I> ParameterizedSpawn<(S,)> for Where
75where
76    S: IntoIterator<Item = I>,
77    I: AsRef<OsStr>,
78{
79    type Output = Child;
80    type Error = std::io::Error;
81
82    /// Spawns `p4 where` for the given files as a child process with piped
83    /// standard output and error streams; use the returned [`Child`] handle
84    /// to wait for it or interact with it.
85    ///
86    /// For each file provided as a parameter, a set of mappings is output.
87    fn spawn_with(&mut self, (files,): (S,)) -> Result<Self::Output, Self::Error> {
88        self.setup_command(&self.bin)
89            .args(files)
90            .stdout(Stdio::piped())
91            .stderr(Stdio::piped())
92            .spawn()
93    }
94}
95
96impl Where {
97    /// Creates a new `p4 where` command.
98    ///
99    /// `bin` is the path to the Perforce command-line executable.
100    pub fn new(bin: impl Into<PathBuf>, global_opts: GlobalOpts) -> Self {
101        Self {
102            bin: bin.into(),
103            global_opts,
104        }
105    }
106
107    /// # Description
108    ///
109    /// g-opts
110    ///
111    #[cfg_attr(
112        feature = "lt2014_2",
113        doc = "See the [Global Options](GlobalOpts) section."
114    )]
115    #[cfg_attr(
116        all(feature = "lt2015_1", not(feature = "lt2014_2")),
117        doc = "See the [“Global Options”](GlobalOpts) section."
118    )]
119    #[cfg_attr(
120        all(feature = "lt2017_1", not(feature = "lt2015_1")),
121        doc = "See [“Global Options”](GlobalOpts)."
122    )]
123    #[cfg_attr(
124        all(feature = "lt2018_2", not(feature = "lt2017_1")),
125        doc = "See [Global Options](GlobalOpts)."
126    )]
127    #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
128    pub fn get_global_opts(&self) -> &GlobalOpts {
129        &self.global_opts
130    }
131
132    /// # Description
133    ///
134    /// g-opts
135    ///
136    #[cfg_attr(
137        feature = "lt2014_2",
138        doc = "See the [Global Options](GlobalOpts) section."
139    )]
140    #[cfg_attr(
141        all(feature = "lt2015_1", not(feature = "lt2014_2")),
142        doc = "See the [“Global Options”](GlobalOpts) section."
143    )]
144    #[cfg_attr(
145        all(feature = "lt2017_1", not(feature = "lt2015_1")),
146        doc = "See [“Global Options”](GlobalOpts)."
147    )]
148    #[cfg_attr(
149        all(feature = "lt2018_2", not(feature = "lt2017_1")),
150        doc = "See [Global Options](GlobalOpts)."
151    )]
152    #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
153    pub fn set_global_opts(&mut self, v: GlobalOpts) -> &mut Self {
154        self.global_opts = v;
155        self
156    }
157
158    /// # Description
159    ///
160    /// g-opts
161    ///
162    #[cfg_attr(
163        feature = "lt2014_2",
164        doc = "See the [Global Options](GlobalOpts) section."
165    )]
166    #[cfg_attr(
167        all(feature = "lt2015_1", not(feature = "lt2014_2")),
168        doc = "See the [“Global Options”](GlobalOpts) section."
169    )]
170    #[cfg_attr(
171        all(feature = "lt2017_1", not(feature = "lt2015_1")),
172        doc = "See [“Global Options”](GlobalOpts)."
173    )]
174    #[cfg_attr(
175        all(feature = "lt2018_2", not(feature = "lt2017_1")),
176        doc = "See [Global Options](GlobalOpts)."
177    )]
178    #[cfg_attr(not(feature = "lt2018_2"), doc = "See [Global options](GlobalOpts).")]
179    pub fn global_opts(mut self, v: GlobalOpts) -> Self {
180        self.global_opts = v;
181        self
182    }
183}
184
185#[cfg(test)]
186mod tests {
187    use super::*;
188    use crate::cmd::args_of;
189
190    /// Dry-run check of the assembled `p4 where` command line; no process is
191    /// spawned.
192    #[test]
193    fn without_options() {
194        let r#where = Where::new("p4", GlobalOpts::new());
195
196        assert_eq!(args_of(&r#where.setup_command("p4")), ["where"]);
197    }
198
199    #[test]
200    fn with_global_opts() {
201        let r#where = Where::new(
202            "p4",
203            GlobalOpts::new().port("localhost:1666").quiet_mode(true),
204        );
205
206        assert_eq!(
207            args_of(&r#where.setup_command("p4")),
208            ["-p", "localhost:1666", "-q", "where"]
209        );
210    }
211
212    #[test]
213    fn with_files() {
214        let r#where = Where::new("p4", GlobalOpts::new());
215
216        assert_eq!(
217            args_of(r#where.setup_command("p4").args(["file.c"])),
218            ["where", "file.c"]
219        );
220    }
221}