Skip to content
Snippets Groups Projects
Select Git revision
  • c5eb2e70dd5dc81ea7a5a5cebba35753a13f5363
  • master default protected
  • network/gdev-800 protected
  • cgeek/issue-297-cpu
  • gdev-800-tests
  • update-docker-compose-rpc-squid-names
  • fix-252
  • 1000i100-test
  • hugo/tmp-0.9.1
  • network/gdev-803 protected
  • hugo/endpoint-gossip
  • network/gdev-802 protected
  • hugo/distance-precompute
  • network/gdev-900 protected
  • tuxmain/anonymous-tx
  • debug/podman
  • hugo/195-doc
  • hugo/195-graphql-schema
  • hugo-tmp-dockerfile-cache
  • release/client-800.2 protected
  • release/runtime-800 protected
  • gdev-900-0.10.1 protected
  • gdev-900-0.10.0 protected
  • gdev-900-0.9.2 protected
  • gdev-800-0.8.0 protected
  • gdev-900-0.9.1 protected
  • gdev-900-0.9.0 protected
  • gdev-803 protected
  • gdev-802 protected
  • runtime-801 protected
  • gdev-800 protected
  • runtime-800-bis protected
  • runtime-800 protected
  • runtime-800-backup protected
  • runtime-701 protected
  • runtime-700 protected
  • runtime-600 protected
  • runtime-500 protected
  • v0.4.1 protected
  • runtime-401 protected
  • v0.4.0 protected
41 results

setup.md

Blame
  • gen_doc.rs 27.24 KiB
    // Copyright 2021 Axiom-Team
    //
    // This file is part of Duniter-v2S.
    //
    // Duniter-v2S is free software: you can redistribute it and/or modify
    // it under the terms of the GNU Affero General Public License as published by
    // the Free Software Foundation, version 3 of the License.
    //
    // Duniter-v2S is distributed in the hope that it will be useful,
    // but WITHOUT ANY WARRANTY; without even the implied warranty of
    // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    // GNU Affero General Public License for more details.
    //
    // You should have received a copy of the GNU Affero General Public License
    // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
    
    use anyhow::{bail, Context, Result};
    use codec::Decode;
    use core::hash::Hash;
    use frame_metadata::v15::{StorageEntryModifier, StorageEntryType};
    use scale_info::{form::PortableForm, PortableRegistry, Type, TypeDef};
    use serde::Serialize;
    use std::{
        collections::HashMap,
        fs::File,
        io::{Read, Write},
        path::Path,
        process::Command,
    };
    use tera::Tera;
    use weightanalyzer::{analyze_weight, MaxBlockWeight, WeightInfo};
    
    fn rename_key<K, V>(h: &mut HashMap<K, V>, old_key: &K, new_key: K)
    where
        K: Eq + Hash,
    {
        if let Some(v) = h.remove(old_key) {
            h.insert(new_key, v);
        }
    }
    
    // consts
    
    const CALLS_DOC_FILEPATH: &str = "docs/api/runtime-calls.md";
    const EVENTS_DOC_FILEPATH: &str = "docs/api/runtime-events.md";
    const STORAGES_DOC_FILEPATH: &str = "docs/api/runtime-storages.md";
    const CONSTANTS_DOC_FILEPATH: &str = "docs/api/runtime-constants.md";
    const ERRORS_DOC_FILEPATH: &str = "docs/api/runtime-errors.md";
    const ERRORS_PO_FILEPATH: &str = "docs/api/runtime-errors.po";
    const TEMPLATES_GLOB: &str = "xtask/res/templates/*.{md,po}";
    const WEIGHT_FILEPATH: &str = "runtime/gdev/src/weights/";
    
    // define structs and implementations
    
    type RuntimePallets = Vec<Pallet>;
    
    #[derive(Clone, Serialize)]
    struct Pallet {
        index: u8,
        name: String,
        type_name: String,
        calls: Vec<Call>,
        events: Vec<Event>,
        errors: Vec<ErroR>,
        storages: Vec<Storage>,
        constants: Vec<Constant>,
    }
    #[derive(Clone, Serialize)]
    struct Call {
        documentation: String,
        index: u8,
        name: String,
        params: Vec<CallParam>,
        weight: f64,
    }
    #[derive(Clone, Serialize)]
    struct CallParam {
        name: String,
        type_name: String,
    }
    #[derive(Clone, Serialize)]
    struct Event {
        documentation: String,
        index: u8,
        name: String,
        params: Vec<EventParam>,
    }
    #[derive(Clone, Serialize)]
    struct EventParam {
        name: String,
        type_name: String,
    }
    #[derive(Clone, Serialize)]
    struct ErroR {
        documentation: String,
        index: u8,
        name: String,
    }
    #[derive(Clone, Serialize)]
    struct Storage {
        documentation: String,
        name: String,
        type_key: String,
        type_value: String,
    }
    #[derive(Clone, Serialize)]
    struct Constant {
        documentation: String,
        name: String,
        value: String,
        type_value: String,
    }
    
    impl Pallet {
        #![allow(clippy::too_many_arguments)]
        fn new(
            index: u8,
            name: String,
            type_name: String,
            call_scale_type_def: &Option<scale_info::TypeDef<PortableForm>>,
            event_scale_type_def: &Option<scale_info::TypeDef<PortableForm>>,
            error_scale_type_def: &Option<scale_info::TypeDef<PortableForm>>,
            storages: Vec<Storage>,
            constants: Vec<Constant>,
        ) -> Result<Self> {
            let calls = if let Some(call_scale_type_def) = call_scale_type_def {
                if let scale_info::TypeDef::Variant(calls_enum) = call_scale_type_def {
                    calls_enum.variants.iter().map(Into::into).collect()
                } else {
                    bail!("Invalid metadata")
                }
            } else {
                vec![]
            };
            let events = if let Some(event_scale_type_def) = event_scale_type_def {
                if let scale_info::TypeDef::Variant(events_enum) = event_scale_type_def {
                    events_enum.variants.iter().map(Into::into).collect()
                } else {
                    bail!("Invalid metadata")
                }
            } else {
                vec![]
            };
            let errors = if let Some(error_scale_type_def) = error_scale_type_def {
                if let scale_info::TypeDef::Variant(errors_enum) = error_scale_type_def {
                    errors_enum.variants.iter().map(Into::into).collect()
                } else {
                    bail!("Invalid metadata")
                }
            } else {
                vec![]
            };
    
            Ok(Self {
                index,
                name,
                type_name,
                calls,
                events,
                errors,
                storages,
                constants,
            })
        }
    }
    
    impl From<&scale_info::Variant<PortableForm>> for Call {
        fn from(variant: &scale_info::Variant<PortableForm>) -> Self {
            Self {
                documentation: variant
                    .docs
                    .iter()
                    .take_while(|line| !line.starts_with("# <weight>"))
                    .cloned()
                    .collect::<Vec<_>>()
                    .join("\n"),
                index: variant.index,
                name: variant.name.to_owned(),
                params: variant.fields.iter().map(Into::into).collect(),
                weight: Default::default(),
            }
        }
    }
    impl From<&scale_info::Field<PortableForm>> for CallParam {
        fn from(field: &scale_info::Field<PortableForm>) -> Self {
            Self {
                name: field.clone().name.unwrap_or_default().to_string(),
                type_name: field.clone().type_name.unwrap_or_default().to_string(),
            }
        }
    }
    
    impl From<&scale_info::Variant<PortableForm>> for Event {
        fn from(variant: &scale_info::Variant<PortableForm>) -> Self {
            Self {
                documentation: variant.docs.to_vec().join("\n"),
                index: variant.index,
                name: variant.name.to_owned(),
                params: variant.fields.iter().map(Into::into).collect(),
            }
        }
    }
    impl From<&scale_info::Field<PortableForm>> for EventParam {
        fn from(field: &scale_info::Field<PortableForm>) -> Self {
            Self {
                name: field.clone().name.unwrap_or_default().to_string(),
                type_name: field.clone().type_name.unwrap_or_default().to_string(),
            }
        }
    }
    
    impl From<&scale_info::Variant<PortableForm>> for ErroR {
        fn from(variant: &scale_info::Variant<PortableForm>) -> Self {
            Self {
                documentation: variant.docs.to_vec().join("\n"),
                index: variant.index,
                name: variant.name.to_owned(),
            }
        }
    }
    
    // classify calls into categories depending on their origin
    enum CallCategory {
        // calls filtered by runtime
        Disabled,
        // inherents
        Inherent,
        // ensure_root
        Root,
        // sudo
        Sudo,
        // user calls
        User,
        // other (like a certain proportion of technical comittee)
        OtherOrigin,
    }
    
    impl CallCategory {
        fn is(pallet_name: &str, call_name: &str) -> Self {
            match (pallet_name, call_name) {
                // substrate "system"
                ("System", _) => Self::Root,
                ("Scheduler", _) => Self::Root,
                ("Babe", "report_equivocation" | "report_equivocation_unsigned") => Self::Inherent,
                ("Babe", "plan_config_change") => Self::Root,
                ("Authorship", _) => Self::Inherent,
                ("Session", _) => Self::Disabled,
                ("Grandpa", "report_equivocation" | "report_equivocation_unsigned") => Self::Inherent,
                ("Grandpa", "note_stalled") => Self::Root,
                ("Timestamp", _) => Self::Inherent,
                ("ImOnline", _) => Self::Inherent,
                // substrate "common"
                (
                    "Balances",
                    "force_set_balance"
                    | "force_transfer"
                    | "force_unreserve"
                    | "force_adjust_total_issuance",
                ) => Self::Root,
                ("Balances", "burn") => Self::Disabled,
                ("Sudo", _) => Self::Sudo,
                ("Treasury", "approve_proposal" | "reject_proposal") => Self::OtherOrigin,
                ("Utility", "dispatch_as" | "with_weight") => Self::Root,
                // duniter
                ("Distance", "force_update_evaluation" | "force_valid_distance_status") => Self::Root,
                ("Distance", "update_evaluation") => Self::Inherent,
                ("AuthorityMembers", "remove_member_from_blacklist" | "remove_member") => Self::Root,
                ("UpgradeOrigin", "dispatch_as_root" | "dispatch_as_root_unchecked_weight") => {
                    Self::OtherOrigin
                }
                ("Identity", "remove_identity" | "prune_item_identities_names" | "fix_sufficients") => {
                    Self::Root
                }
                ("Certification", "del_cert" | "remove_all_certs_received_by") => Self::Root,
                ("TechnicalCommittee", "set_members" | "disapprove_proposal") => Self::Root,
                // if not classified, consider it at a user call
                _ => Self::User,
            }
        }
    
        // only user calls
        fn is_user(pallet_name: &str, call_name: &str) -> bool {
            matches!(Self::is(pallet_name, call_name), Self::User)
        }
    }
    
    /// generate runtime calls documentation
    pub(super) fn gen_doc() -> Result<()> {
        // Read metadata
        let mut file = std::fs::File::open("resources/metadata.scale")
            .with_context(|| "Failed to open metadata file")?;
    
        let mut bytes = Vec::new();
        file.read_to_end(&mut bytes)
            .with_context(|| "Failed to read metadata file")?;
    
        let metadata = frame_metadata::RuntimeMetadataPrefixed::decode(&mut &bytes[..])
            .with_context(|| "Failed to decode metadata")?;
    
        println!("Metadata successfully loaded!");
    
        let (mut runtime, max_weight) =
            if let frame_metadata::RuntimeMetadata::V15(ref metadata_v15) = metadata.1 {
                (
                    get_from_metadata_v15(metadata_v15.clone())?,
                    get_max_weight_from_metadata_v15(metadata_v15.clone())?,
                )
            } else {
                bail!("unsuported metadata version")
            };
    
        let mut weights = get_weights(max_weight)?;
    
        // Ad hoc names conversion between pallet filename and instance name
        rename_key(&mut weights, &"FrameSystem".into(), "System".into());
        rename_key(&mut weights, &"DuniterAccount".into(), "Account".into());
        rename_key(
            &mut weights,
            &"Collective".into(),
            "TechnicalCommittee".into(),
        );
    
        // We enforce weight for each pallet.
        // For pallets with manual or no weight, we define a default value.
        weights.insert("Babe".to_string(), Default::default()); // Manual
        weights.insert("Grandpa".to_string(), Default::default()); // Manual
        weights.insert("AtomicSwap".to_string(), Default::default()); // No weight
    
        // Insert weights for each call of each pallet.
        // If no weight is available, the weight is set to -1.
        // We use the relative weight in percent computed as the extrinsic base +
        // the extrinsic execution divided by the total weight available in
        // one block. If the weight depends on a complexity parameter,
        // we display the worst possible weight, taking the upper limit as
        // defined during the benchmark.
        runtime.iter_mut().for_each(|pallet| {
            pallet.calls.iter_mut().for_each(|call| {
                call.weight = weights
                    .get(&pallet.name)
                    .expect(&("No weight for ".to_owned() + &pallet.name))
                    .get(&call.name)
                    .map_or(-1f64, |weight| {
                        (weight.relative_weight * 10000.).round() / 10000.
                    })
            })
        });
    
        let (call_doc, event_doc, error_doc, error_po, storage_doc, constant_doc) =
            print_runtime(runtime);
    
        // Generate docs from rust code
        Command::new("cargo")
            .args([
                "doc",
                "--package=duniter",
                "--package=pallet-*",
                "--package=*-runtime",
                "--package=*distance*",
                "--package=*membership*",
                "--no-deps",
                "--document-private-items",
                "--features=runtime-benchmarks",
                "--package=pallet-atomic-swap",
                "--package=pallet-authority-discovery",
                "--package=pallet-balances",
                "--package=pallet-collective",
                "--package=pallet-im-online",
                "--package=pallet-preimage",
                "--package=pallet-proxy",
                "--package=pallet-scheduler",
                "--package=pallet-session",
                "--package=pallet-sudo",
                "--package=pallet-timestamp",
                "--package=pallet-treasury",
                "--package=pallet-utility",
            ])
            .status()
            .expect("cargo doc failed to execute");
    
        let mut file = File::create(CALLS_DOC_FILEPATH)
            .with_context(|| format!("Failed to create file '{}'", CALLS_DOC_FILEPATH))?;
        file.write_all(call_doc.as_bytes())
            .with_context(|| format!("Failed to write to file '{}'", CALLS_DOC_FILEPATH))?;
        let mut file = File::create(EVENTS_DOC_FILEPATH)
            .with_context(|| format!("Failed to create file '{}'", EVENTS_DOC_FILEPATH))?;
        file.write_all(event_doc.as_bytes())
            .with_context(|| format!("Failed to write to file '{}'", EVENTS_DOC_FILEPATH))?;
        let mut file = File::create(ERRORS_DOC_FILEPATH)
            .with_context(|| format!("Failed to create file '{}'", ERRORS_DOC_FILEPATH))?;
        file.write_all(error_doc.as_bytes())
            .with_context(|| format!("Failed to write to file '{}'", ERRORS_DOC_FILEPATH))?;
        let mut file = File::create(STORAGES_DOC_FILEPATH)
            .with_context(|| format!("Failed to create file '{}'", STORAGES_DOC_FILEPATH))?;
        file.write_all(storage_doc.as_bytes())
            .with_context(|| format!("Failed to write to file '{}'", STORAGES_DOC_FILEPATH))?;
        let mut file = File::create(CONSTANTS_DOC_FILEPATH)
            .with_context(|| format!("Failed to create file '{}'", CONSTANTS_DOC_FILEPATH))?;
        file.write_all(constant_doc.as_bytes())
            .with_context(|| format!("Failed to write to file '{}'", CONSTANTS_DOC_FILEPATH))?;
        let mut file = File::create(ERRORS_PO_FILEPATH)
            .with_context(|| format!("Failed to create file '{}'", ERRORS_PO_FILEPATH))?;
        file.write_all(error_po.as_bytes())
            .with_context(|| format!("Failed to write to file '{}'", ERRORS_PO_FILEPATH))?;
    
        Ok(())
    }
    
    fn get_max_weight_from_metadata_v15(
        metadata_v15: frame_metadata::v15::RuntimeMetadataV15,
    ) -> Result<u128> {
        // Extract the maximal weight available in one block
        // from the metadata.
        let block_weights = metadata_v15
            .pallets
            .iter()
            .find(|pallet| pallet.name == "System")
            .expect("Can't find System pallet metadata")
            .constants
            .iter()
            .find(|constant| constant.name == "BlockWeights")
            .expect("Can't find BlockWeights");
    
        let block_weights = scale_value::scale::decode_as_type(
            &mut &*block_weights.value,
            &block_weights.ty.id,
            &metadata_v15.types,
        )
        .expect("Can't decode max_weight")
        .value;
    
        if let scale_value::ValueDef::Composite(scale_value::Composite::Named(i)) = block_weights
            && let scale_value::ValueDef::Composite(scale_value::Composite::Named(j)) =
                &i.iter().find(|name| name.0 == "max_block").unwrap().1.value
            && let scale_value::ValueDef::Primitive(scale_value::Primitive::U128(k)) =
                &j.iter().find(|name| name.0 == "ref_time").unwrap().1.value
        {
            Ok(*k)
        } else {
            bail!("Invalid max_weight")
        }
    }
    
    /// Converts a `Type<PortableForm>` into a human-readable string representation.
    ///
    /// This function is a core part of the type resolution pipeline, working together with:
    /// - `resolve_type()` to obtain type definitions.
    /// - `format_generics()` to correctly format generic parameters.
    ///
    /// It processes a `Type<PortableForm>` from the metadata registry and outputs a Rust-like string representation,
    /// handling all supported type definitions (composite types, sequences, arrays, tuples, primitives, etc.).
    ///
    /// # Returns
    /// - `Ok(String)`: A formatted type string (e.g., `"Vec<u32>"`, `"(i32, bool)"`).
    /// - `Err(anyhow::Error)`: If metadata are incorrect.
    ///
    /// # How It Works With Other Functions:
    /// - Calls `format_generics()` to handle generic type parameters.
    /// - Calls `resolve_type()` when resolving inner types inside sequences, arrays, and tuples.
    /// - Used by `resolve_type()` as a formatting step after retrieving a type.
    ///
    fn format_type(ty: &Type<PortableForm>, types: &PortableRegistry) -> Result<String> {
        let path = ty.path.to_string();
    
        match &ty.type_def {
            TypeDef::Composite(_) => {
                let generics = format_generics(&ty.type_params, types)?;
                Ok(format!("{}{}", path, generics))
            }
            TypeDef::Variant(_) => {
                let generics = format_generics(&ty.type_params, types)?;
                Ok(format!("{}{}", path, generics))
            }
            TypeDef::Sequence(seq) => {
                let element_type = resolve_type(seq.type_param.id, types)?;
                Ok(format!("Vec<{}>", element_type))
            }
            TypeDef::Array(arr) => {
                let element_type = resolve_type(arr.type_param.id, types)?;
                Ok(format!("[{}; {}]", element_type, arr.len))
            }
            TypeDef::Tuple(tuple) => {
                let elements = tuple
                    .fields
                    .iter()
                    .map(|f| resolve_type(f.id, types))
                    .collect::<Result<Vec<String>>>()?;
                Ok(format!("({})", elements.join(", ")))
            }
            TypeDef::Primitive(primitive) => Ok(format!("{:?}", primitive)),
            TypeDef::Compact(compact) => {
                let inner_type = resolve_type(compact.type_param.id, types)?;
                Ok(format!("Compact<{}>", inner_type))
            }
            TypeDef::BitSequence(_) => Ok(String::default()),
        }
    }
    
    /// Resolves a type ID to a formatted string representation.
    ///
    /// This function serves as a bridge between raw type IDs and fully formatted types.
    /// It works closely with `format_type()`, ensuring that once a type is found, it is properly formatted.
    ///
    /// # How It Works With Other Functions:
    /// - Retrieves a type from the registry using `types.resolve(type_id)`.
    /// - If successful, calls `format_type()` to get a human-readable format.
    /// - Used internally by `format_type()` when resolving type dependencies.
    ///
    fn resolve_type(type_id: u32, types: &PortableRegistry) -> Result<String> {
        types
            .resolve(type_id)
            .map(|t| format_type(t, types))
            .unwrap_or_else(|| bail!("Invalid metadata"))
    }
    
    /// Formats generic type parameters into a Rust-like string representation.
    ///
    /// This function helps `format_type()` handle generic types, ensuring that type parameters
    /// are formatted correctly when they exist. If a type has generic parameters, they are enclosed
    /// in angle brackets (e.g., `<T, U>`).
    ///
    /// # How It Works With Other Functions:
    /// - Called inside `format_type()` to process generic type parameters.
    /// - Uses `resolve_type()` to retrieve and format each generic type.
    ///
    fn format_generics(
        params: &[scale_info::TypeParameter<PortableForm>],
        types: &PortableRegistry,
    ) -> Result<String> {
        if params.is_empty() {
            Ok(String::default())
        } else {
            let generics = params
                .iter()
                .map(|p| {
                    p.ty.map(|ty| resolve_type(ty.id, types))
                        .unwrap_or_else(|| Ok(String::default()))
                })
                .collect::<Result<Vec<String>>>()?;
            Ok(format!("<{}>", generics.join(", ")))
        }
    }
    
    fn parse_storage_entry(
        variant: &frame_metadata::v15::StorageEntryMetadata<scale_info::form::PortableForm>,
        types: &PortableRegistry,
    ) -> Result<Storage> {
        match &variant.ty {
            StorageEntryType::Map { key, value, .. } => {
                let type_key = resolve_type(key.id, types)?;
                let type_value = resolve_type(value.id, types)?;
                Ok(Storage {
                    documentation: variant.docs.join("\n"),
                    name: variant.name.clone(),
                    type_key,
                    type_value,
                })
            }
            StorageEntryType::Plain(v) => {
                let type_value = resolve_type(v.id, types)?;
                let type_value = if let StorageEntryModifier::Optional = &variant.modifier {
                    format!("Option<{}>", type_value)
                } else {
                    type_value
                };
                Ok(Storage {
                    documentation: variant.docs.join("\n"),
                    name: variant.name.clone(),
                    type_key: String::default(),
                    type_value,
                })
            }
        }
    }
    
    fn get_from_metadata_v15(
        metadata_v15: frame_metadata::v15::RuntimeMetadataV15,
    ) -> Result<RuntimePallets> {
        println!("Number of pallets: {}", metadata_v15.pallets.len());
        let mut pallets = Vec::new();
        for pallet in metadata_v15.pallets {
            let mut type_name: String = Default::default();
            let calls_type_def = if let Some(calls) = pallet.calls {
                let Some(calls_type) = metadata_v15.types.resolve(calls.ty.id) else {
                    bail!("Invalid metadata")
                };
                type_name = calls_type
                    .path
                    .segments
                    .first()
                    .expect("cannot decode pallet type")
                    .to_string();
                Some(calls_type.type_def.clone())
            } else {
                println!("{}: {} (0 calls)", pallet.index, pallet.name);
                None
            };
            let events_type_def = if let Some(events) = pallet.event {
                let Some(events_type) = metadata_v15.types.resolve(events.ty.id) else {
                    bail!("Invalid metadata")
                };
                Some(events_type.type_def.clone())
            } else {
                println!("{}: {} (0 events)", pallet.index, pallet.name);
                None
            };
            let errors_type_def = if let Some(errors) = pallet.error {
                let Some(errors_type) = metadata_v15.types.resolve(errors.ty.id) else {
                    bail!("Invalid metadata")
                };
                Some(errors_type.type_def.clone())
            } else {
                println!("{}: {} (0 errors)", pallet.index, pallet.name);
                None
            };
    
            let storages = pallet
                .storage
                .map(|storage| {
                    storage
                        .entries
                        .iter()
                        .map(|v| parse_storage_entry(v, &metadata_v15.types))
                        .collect::<Result<Vec<Storage>>>()
                })
                .unwrap_or_else(|| {
                    println!("{}: {} (0 storage)", pallet.index, pallet.name);
                    Ok(Vec::default())
                })?;
    
            let constants = pallet
                .constants
                .iter()
                .map(|i| {
                    let type_value = resolve_type(i.ty.id, &metadata_v15.types)?;
                    let value = scale_value::scale::decode_as_type(
                        &mut &*i.value,
                        &i.ty.id,
                        &metadata_v15.types,
                    )
                    .map_err(|e| anyhow::anyhow!("{}", e))?;
                    Ok(Constant {
                        documentation: i.docs.join("\n"),
                        name: i.name.clone(),
                        value: value.to_string(),
                        type_value,
                    })
                })
                .collect::<Result<Vec<Constant>>>()?;
    
            let pallet = Pallet::new(
                pallet.index,
                pallet.name.clone(),
                type_name,
                &calls_type_def,
                &events_type_def,
                &errors_type_def,
                storages,
                constants,
            )?;
    
            println!(
                "{}: {} ({} calls)",
                pallet.index,
                pallet.name,
                pallet.calls.len()
            );
            println!(
                "{}: {} ({} events)",
                pallet.index,
                pallet.name,
                pallet.events.len()
            );
            println!(
                "{}: {} ({} errors)",
                pallet.index,
                pallet.name,
                pallet.errors.len()
            );
            println!(
                "{}: {} ({} storages)",
                pallet.index,
                pallet.name,
                pallet.storages.len()
            );
            println!(
                "{}: {} ({} constants)",
                pallet.index,
                pallet.name,
                pallet.constants.len()
            );
            pallets.push(pallet);
        }
        Ok(pallets)
    }
    
    fn get_weights(max_weight: u128) -> Result<HashMap<String, HashMap<String, WeightInfo>>> {
        analyze_weight(
            Path::new(WEIGHT_FILEPATH),
            &MaxBlockWeight::new(max_weight as f64),
        )
        .map_err(|e| anyhow::anyhow!(e))
    }
    
    /// use template to render markdown file with runtime calls documentation
    fn print_runtime(pallets: RuntimePallets) -> (String, String, String, String, String, String) {
        // init variables
        // -- user calls
        let mut user_calls_counter = 0;
        let user_calls_pallets: RuntimePallets = pallets
            .iter()
            .cloned()
            .filter_map(|mut pallet| {
                let pallet_name = pallet.name.clone();
                pallet
                    .calls
                    .retain(|call| CallCategory::is_user(&pallet_name, &call.name));
                if pallet.calls.is_empty() {
                    None
                } else {
                    user_calls_counter += pallet.calls.len();
                    Some(pallet)
                }
            })
            .collect();
    
        // event counter
        let mut event_counter = 0;
        pallets
            .iter()
            .for_each(|pallet| event_counter += pallet.events.len());
    
        // error counter
        let mut error_counter = 0;
        pallets
            .iter()
            .for_each(|pallet| error_counter += pallet.errors.len());
    
        // storage counter
        let mut storage_counter = 0;
        pallets
            .iter()
            .for_each(|pallet| storage_counter += pallet.storages.len());
    
        // constant counter
        let mut constant_counter = 0;
        pallets
            .iter()
            .for_each(|pallet| constant_counter += pallet.constants.len());
    
        // compile template
        let tera = match Tera::new(TEMPLATES_GLOB) {
            Ok(t) => t,
            Err(e) => {
                println!("Parsing error(s): {}", e);
                ::std::process::exit(1);
            }
        };
    
        // fills tera context for rendering calls
        let mut context = tera::Context::new();
        context.insert("user_calls_counter", &user_calls_counter);
        context.insert("user_calls_pallets", &user_calls_pallets);
    
        let call_doc = tera
            .render("runtime-calls.md", &context)
            .expect("template error");
    
        // render events
        context.insert("pallets", &pallets);
        context.insert("event_counter", &event_counter);
        let event_doc = tera
            .render("runtime-events.md", &context)
            .expect("template error");
    
        // render errors
        context.insert("error_counter", &error_counter);
        let error_doc = tera
            .render("runtime-errors.md", &context)
            .expect("template error");
    
        let error_po = tera
            .render("runtime-errors.po", &context)
            .expect("template error");
    
        // render storages
        context.insert("pallets", &pallets);
        context.insert("storage_counter", &event_counter);
        let storage_doc = tera
            .render("runtime-storages.md", &context)
            .expect("template storage");
    
        // render constant
        context.insert("pallets", &pallets);
        context.insert("constant_counter", &constant_counter);
        let constant_doc = tera
            .render("runtime-constants.md", &context)
            .expect("template constant");
    
        (
            call_doc,
            event_doc,
            error_doc,
            error_po,
            storage_doc,
            constant_doc,
        )
    }