Skip to content

Commit

Permalink
Add Deployments preprocessor to forge doc (#5092)
Browse files Browse the repository at this point in the history
* Add `Deployments` preprocessor to `forge doc`

Remove reference project

:broom:

* Add optional relative path / remove short flag
  • Loading branch information
clabby committed May 31, 2023
1 parent b0c95d0 commit 680f2c6
Show file tree
Hide file tree
Showing 9 changed files with 153 additions and 10 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 16 additions & 5 deletions cli/src/cmd/forge/doc.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{cmd::Cmd, opts::GH_REPO_PREFIX_REGEX};
use clap::{Parser, ValueHint};
use forge_doc::{ContractInheritance, DocBuilder, GitSource, Inheritdoc, Server};
use forge_doc::{ContractInheritance, Deployments, DocBuilder, GitSource, Inheritdoc, Server};
use foundry_config::{find_project_root_path, load_config_with_root};
use std::{path::PathBuf, process::Command};

Expand Down Expand Up @@ -39,6 +39,11 @@ pub struct DocArgs {
/// Port for serving documentation.
#[clap(long, short, requires = "serve")]
port: Option<usize>,

/// The relative path to the `hardhat-deploy` or `forge-deploy` artifact directory. Leave blank
/// for default.
#[clap(long)]
deployments: Option<Option<PathBuf>>,
}

impl Cmd for DocArgs {
Expand Down Expand Up @@ -79,18 +84,24 @@ impl Cmd for DocArgs {
}
});

DocBuilder::new(root.clone(), config.project_paths().sources)
let mut builder = DocBuilder::new(root.clone(), config.project_paths().sources)
.with_should_build(self.build)
.with_config(doc_config.clone())
.with_fmt(config.fmt)
.with_preprocessor(ContractInheritance::default())
.with_preprocessor(Inheritdoc::default())
.with_preprocessor(GitSource {
root,
root: root.clone(),
commit,
repository: doc_config.repository.clone(),
})
.build()?;
});

// If deployment docgen is enabled, add the [Deployments] preprocessor
if let Some(deployments) = self.deployments {
builder = builder.with_preprocessor(Deployments { root, deployments });
}

builder.build()?;

if self.serve {
Server::new(doc_config.out)
Expand Down
2 changes: 2 additions & 0 deletions doc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,5 @@ toml = "0.7"
auto_impl = "1"
derive_more = "0.99"
once_cell = "1"
serde = "1.0.163"
serde_json = "1.0.96"
4 changes: 2 additions & 2 deletions doc/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,8 +243,8 @@ impl DocBuilder {
// Grab the root readme.
let root_readme = self.root.join(Self::README);

//Check to see if there is a 'homepage' option specified in config.
//If not, fall back to src and root readme files, in that order.
// Check to see if there is a 'homepage' option specified in config.
// If not, fall back to src and root readme files, in that order.
if homepage_or_src_readme.exists() {
fs::read_to_string(homepage_or_src_readme)?
} else if root_readme.exists() {
Expand Down
88 changes: 88 additions & 0 deletions doc/src/preprocessor/deployments.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
use ethers_core::types::Address;

use super::{Preprocessor, PreprocessorId};
use crate::{Document, PreprocessorOutput};
use std::{fs, 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(serde::Deserialize, Debug, Clone)]
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_ref().unwrap_or(&PathBuf::from("deployments")));

// Gather all networks from the deployments directory.
let networks = fs::read_dir(&deployments_dir)?
.map(|x| {
x.map(|y| {
if y.file_type()?.is_dir() {
Ok(y.file_name().into_string().map_err(|e| {
eyre::eyre!("Failed to extract directory name: {:?}", e)
})?)
} else {
eyre::bail!("Not a directory.")
}
})?
})
.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(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)
}
}
2 changes: 1 addition & 1 deletion doc/src/preprocessor/git_source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use super::{Preprocessor, PreprocessorId};
use crate::{Document, PreprocessorOutput};
use std::path::PathBuf;

/// [ContractInheritance] preprocessor id.
/// [GitSource] preprocessor id.
pub const GIT_SOURCE_ID: PreprocessorId = PreprocessorId("git_source");

/// The git source preprocessor.
Expand Down
6 changes: 6 additions & 0 deletions doc/src/preprocessor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ pub use inheritdoc::{Inheritdoc, INHERITDOC_ID};
mod git_source;
pub use git_source::{GitSource, GIT_SOURCE_ID};

mod deployments;
pub use deployments::{Deployment, Deployments, DEPLOYMENTS_ID};

/// The preprocessor id.
#[derive(Debug, Eq, Hash, PartialEq)]
pub struct PreprocessorId(&'static str);
Expand All @@ -30,6 +33,9 @@ pub enum PreprocessorOutput {
/// The git source output.
/// The git url of the item path.
GitSource(String),
/// The deployments output.
/// The deployment address of the item path.
Deployments(Vec<Deployment>),
}

/// Trait for preprocessing and/or modifying existing documents
Expand Down
6 changes: 5 additions & 1 deletion doc/src/writer/as_doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::{
parser::ParseSource,
writer::BufWriter,
CommentTag, Comments, CommentsRef, Document, Markdown, PreprocessorOutput,
CONTRACT_INHERITANCE_ID, GIT_SOURCE_ID, INHERITDOC_ID,
CONTRACT_INHERITANCE_ID, DEPLOYMENTS_ID, GIT_SOURCE_ID, INHERITDOC_ID,
};
use forge_fmt::solang_ext::SafeUnwrap;
use itertools::Itertools;
Expand Down Expand Up @@ -119,6 +119,10 @@ impl AsDoc for Document {
writer.writeln()?;
}

if let Some(deployments) = read_context!(self, DEPLOYMENTS_ID, Deployments) {
writer.write_deployments_table(deployments)?;
}

match &item.source {
ParseSource::Contract(contract) => {
if !contract.base.is_empty() {
Expand Down
32 changes: 31 additions & 1 deletion doc/src/writer/buf_writer.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use ethers_core::utils::hex;
use itertools::Itertools;
use once_cell::sync::Lazy;
use solang_parser::pt::Parameter;
use std::fmt::{self, Display, Write};

use crate::{AsDoc, CommentTag, Comments, Markdown};
use crate::{AsDoc, CommentTag, Comments, Deployment, Markdown};

/// Solidity language name.
const SOLIDITY: &str = "solidity";
Expand All @@ -13,6 +14,11 @@ const PARAM_TABLE_HEADERS: &[&str] = &["Name", "Type", "Description"];
static PARAM_TABLE_SEPARATOR: Lazy<String> =
Lazy::new(|| PARAM_TABLE_HEADERS.iter().map(|h| "-".repeat(h.len())).join("|"));

/// Headers and separator for rendering the deployments table.
const DEPLOYMENTS_TABLE_HEADERS: &[&str] = &["Network", "Address"];
static DEPLOYMENTS_TABLE_SEPARATOR: Lazy<String> =
Lazy::new(|| DEPLOYMENTS_TABLE_HEADERS.iter().map(|h| "-".repeat(h.len())).join("|"));

/// The buffered writer.
/// Writes various display items into the internal buffer.
#[derive(Default, Debug)]
Expand Down Expand Up @@ -163,6 +169,30 @@ impl BufWriter {
Ok(())
}

/// Writes the deployment table to the buffer.
pub fn write_deployments_table(&mut self, deployments: Vec<Deployment>) -> fmt::Result {
self.write_bold("Deployments")?;
self.writeln()?;

self.write_piped(&DEPLOYMENTS_TABLE_HEADERS.join("|"))?;
self.write_piped(&DEPLOYMENTS_TABLE_SEPARATOR)?;

for deployment in deployments {
let mut network = deployment.network.ok_or(fmt::Error)?;
network[0..1].make_ascii_uppercase();

let row = [
Markdown::Bold(&network).as_doc()?,
Markdown::Code(&format!("0x{}", hex::encode(deployment.address))).as_doc()?,
];
self.write_piped(&row.join("|"))?;
}

self.writeln()?;

Ok(())
}

/// Write content to the buffer surrounded by pipes.
pub fn write_piped(&mut self, content: &str) -> fmt::Result {
self.write_raw("|")?;
Expand Down

0 comments on commit 680f2c6

Please sign in to comment.