forge_doc/preprocessor/
deployments.rs

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
85
86
87
88
89
90
use super::{Preprocessor, PreprocessorId};
use crate::{Document, PreprocessorOutput};
use alloy_primitives::Address;
use std::{
    fs,
    path::{Path, PathBuf},
};

/// [Deployments] preprocessor id.
pub const DEPLOYMENTS_ID: PreprocessorId = PreprocessorId("deployments");

/// The deployments preprocessor.
///
/// This preprocessor writes to [Document]'s context.
#[derive(Debug)]
pub struct Deployments {
    /// The project root.
    pub root: PathBuf,
    /// The deployments directory.
    pub deployments: Option<PathBuf>,
}

/// A contract deployment.
#[derive(Clone, Debug, serde::Deserialize)]
pub struct Deployment {
    /// The contract address
    pub address: Address,
    /// The network name
    pub network: Option<String>,
}

impl Preprocessor for Deployments {
    fn id(&self) -> PreprocessorId {
        DEPLOYMENTS_ID
    }

    fn preprocess(&self, documents: Vec<Document>) -> Result<Vec<Document>, eyre::Error> {
        let deployments_dir =
            self.root.join(self.deployments.as_deref().unwrap_or(Path::new("deployments")));

        // Gather all networks from the deployments directory.
        let networks = fs::read_dir(&deployments_dir)?
            .map(|entry| {
                let entry = entry?;
                let path = entry.path();
                if entry.file_type()?.is_dir() {
                    entry
                        .file_name()
                        .into_string()
                        .map_err(|e| eyre::eyre!("failed to extract directory name: {e:?}"))
                } else {
                    eyre::bail!("not a directory: {}", path.display())
                }
            })
            .collect::<Result<Vec<_>, _>>()?;

        // Iterate over all documents to find any deployments.
        for document in documents.iter() {
            let mut deployments = Vec::default();

            // Iterate over all networks and check if there is a deployment for the given contract.
            for network in &networks {
                // Clone the item path of the document and change it from ".sol" -> ".json"
                let mut item_path_clone = document.item_path.clone();
                item_path_clone.set_extension("json");

                // Determine the path of the deployment artifact relative to the root directory.
                let deployment_path =
                    deployments_dir.join(network).join(item_path_clone.file_name().ok_or_else(
                        || eyre::eyre!("Failed to extract file name from item path"),
                    )?);

                // If the deployment file for the given contract is found, add the deployment
                // address to the document context.
                let mut deployment: Deployment =
                    serde_json::from_str(&fs::read_to_string(deployment_path)?)?;
                deployment.network = Some(network.clone());
                deployments.push(deployment);
            }

            // If there are any deployments for the given contract, add them to the document
            // context.
            if !deployments.is_empty() {
                document.add_context(self.id(), PreprocessorOutput::Deployments(deployments));
            }
        }

        Ok(documents)
    }
}