1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
//! This is a crate that writes the collected contracts to a directory named "multisol-contract_name".

use std::fs;
use std::path::PathBuf;

use anyhow::{Context, Result};
use multisol_structs::Contract;

/// Entry point to the crate.
pub fn run(contracts: Vec<Contract>) -> Result<()> {
    // The last contract is the one provided by the user since that is the first contract that
    // gets inserted in the vector.
    let user_provided_contract = contracts.last().with_context(|| "No contracts found")?;

    // Delete the output directory if it exists already.
    let output_dir = get_output_dir(&user_provided_contract)?;
    if output_dir.is_dir() {
        fs::remove_dir_all(&output_dir)?;
    }
    fs::create_dir(&output_dir)?;

    for contract in contracts.iter().rev() {
        let output_file_path = output_dir.join(contract.file_name());
        fs::write(output_file_path, contract.source_code())?;
    }

    println!("✨ contracts written to {:?} folder", output_dir);

    Ok(())
}

/// Appends the contract name to "multisol-" to generate the name of the output directory.
fn get_output_dir(user_provided_contract: &Contract) -> Result<PathBuf> {
    let contract_file_stem = user_provided_contract.full_path().file_stem().with_context(|| {
        format!(
            "Could not get file stem out of contract path: {:?}",
            user_provided_contract.full_path()
        )
    })?;

    let contract_name = contract_file_stem.to_str().with_context(|| {
        format!(
            "Contract name contains invalid UTF-8: {:?}",
            user_provided_contract.file_name()
        )
    })?;
    let contract_name_lowercase = contract_name.to_lowercase();
    let output_dir_str = ["multisol", &contract_name_lowercase].join("-");

    Ok(PathBuf::from(&output_dir_str))
}

#[cfg(test)]
mod tests {
    use super::{get_output_dir, Contract};
    use std::ffi::OsString;
    use std::path::PathBuf;

    #[test]
    fn gets_correct_output_dir() {
        let directory = PathBuf::from("/tmp/contracts");
        let full_path = PathBuf::from("/tmp/contracts/Foo.sol");
        let contract = Contract::new(directory, false, OsString::from("Foo.sol"), full_path, String::from(""));
        let output_dir = get_output_dir(&contract).unwrap();
        assert_eq!(output_dir, PathBuf::from("multisol-foo"));
    }

    /// Technically this edge case should never occur, because the contract path is sanity checked on program entry.
    /// But let's be comprehensive.
    #[test]
    fn cant_get_file_stem_out_of_contract_path() {
        let directory = PathBuf::from("/tmp/contracts");
        let full_path = PathBuf::from("/tmp/contracts/Foo.sol/..");
        let contract = Contract::new(directory, false, OsString::from("Foo.sol"), full_path, String::from(""));
        let err = get_output_dir(&contract).unwrap_err();
        assert_eq!(
            err.to_string(),
            format!(
                "Could not get file stem out of contract path: {:?}",
                contract.full_path()
            )
        );
    }
}