Skip to main content

vivacity_core/
c3_plugin.rs

1//! Emulation of the `codeception/c3` plugin (docs/reference/plugins/
2//! codeception-c3/, MIT), read at 2.9.0 under Composer 2: on
3//! `POST_INSTALL_CMD` (`copyC3V2`) and `POST_UPDATE_CMD` (`askForUpdateV2`)
4//! it copies `vendor/codeception/c3/c3.php` to `<cwd>/c3.php` — unless the
5//! two are already identical (md5), or a different `c3.php` is there and
6//! the confirmation to replace it is declined (the default, so always
7//! under `--no-interaction`). `uninstall` of the plugin deletes the file.
8//! Its messages go to stdout (`$io->write`).
9
10use crate::error::{Error, Result};
11use std::path::Path;
12
13pub const PLUGIN_NAME: &str = "codeception/c3";
14
15/// What the listener did, for the lines it prints.
16#[derive(Debug, PartialEq, Eq)]
17pub enum C3Action {
18    /// `c3.php is already up-to-date` (printed on install, not on update).
19    UpToDate,
20    /// A different `c3.php` is in place: not replaced without a yes.
21    Kept,
22    Copied,
23}
24
25/// `copyC3V2` / `askForUpdateV2`: `vendor` is the vendor directory,
26/// `root` the working directory (`getcwd()`).
27pub fn copy_c3(vendor: &Path, root: &Path) -> Result<C3Action> {
28    let source = vendor.join("codeception").join("c3").join("c3.php");
29    let target = root.join("c3.php");
30    let src_bytes = std::fs::read(&source).map_err(Error::io(&source))?;
31    if let Ok(existing) = std::fs::read(&target) {
32        if existing == src_bytes {
33            return Ok(C3Action::UpToDate);
34        }
35        return Ok(C3Action::Kept);
36    }
37    std::fs::write(&target, &src_bytes).map_err(Error::io(&target))?;
38    Ok(C3Action::Copied)
39}
40
41/// `Installer::uninstall` → `deleteFile`: `true` when a file was removed.
42pub fn delete_c3(root: &Path) -> Result<bool> {
43    let target = root.join("c3.php");
44    if !target.exists() {
45        return Ok(false);
46    }
47    std::fs::remove_file(&target).map_err(Error::io(&target))?;
48    Ok(true)
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54
55    #[test]
56    fn copies_once_and_never_overwrites() {
57        let d = tempfile::tempdir().expect("tmp");
58        let vendor = d.path().join("vendor");
59        std::fs::create_dir_all(vendor.join("codeception/c3")).expect("dirs");
60        std::fs::write(vendor.join("codeception/c3/c3.php"), b"<?php // c3").expect("src");
61        assert_eq!(copy_c3(&vendor, d.path()).expect("copy"), C3Action::Copied);
62        assert_eq!(
63            std::fs::read(d.path().join("c3.php")).expect("read"),
64            b"<?php // c3"
65        );
66        assert_eq!(
67            copy_c3(&vendor, d.path()).expect("again"),
68            C3Action::UpToDate
69        );
70        std::fs::write(d.path().join("c3.php"), b"edited").expect("edit");
71        assert_eq!(copy_c3(&vendor, d.path()).expect("kept"), C3Action::Kept);
72        assert_eq!(
73            std::fs::read(d.path().join("c3.php")).expect("read"),
74            b"edited"
75        );
76        assert!(delete_c3(d.path()).expect("delete"));
77        assert!(!delete_c3(d.path()).expect("nothing"));
78    }
79}