From 477fe3daacf8efcec29cae5483b8c7369f30b3da Mon Sep 17 00:00:00 2001
From: Hugo Trentesaux <hugo@trentesaux.fr>
Date: Tue, 24 Jan 2023 13:36:28 +0100
Subject: [PATCH] use tera templates to generate runtime calls doc

---
 Cargo.lock                                    |  71 ++++-
 xtask/Cargo.toml                              |   1 +
 xtask/res/templates/runtime-calls-category.md |  25 ++
 xtask/res/templates/runtime-calls.md          |  34 +++
 xtask/src/gen_calls_doc.rs                    | 254 +++++++-----------
 5 files changed, 222 insertions(+), 163 deletions(-)
 create mode 100644 xtask/res/templates/runtime-calls-category.md
 create mode 100644 xtask/res/templates/runtime-calls.md

diff --git a/Cargo.lock b/Cargo.lock
index e2b6bba2a..470e820e8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -8947,6 +8947,22 @@ dependencies = [
  "winapi 0.3.9",
 ]
 
+[[package]]
+name = "tera"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d4685e72cb35f0eb74319c8fe2d3b61e93da5609841cde2cb87fcc3bea56d20"
+dependencies = [
+ "globwalk",
+ "lazy_static",
+ "pest",
+ "pest_derive",
+ "regex",
+ "serde",
+ "serde_json",
+ "unic-segment",
+]
+
 [[package]]
 name = "termcolor"
 version = "1.1.2"
@@ -9374,9 +9390,9 @@ version = "1.6.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675"
 dependencies = [
- "cfg-if 0.1.10",
+ "cfg-if 1.0.0",
  "digest 0.10.3",
- "rand 0.7.3",
+ "rand 0.8.4",
  "static_assertions",
 ]
 
@@ -9415,6 +9431,56 @@ dependencies = [
  "static_assertions",
 ]
 
+[[package]]
+name = "unic-char-property"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221"
+dependencies = [
+ "unic-char-range",
+]
+
+[[package]]
+name = "unic-char-range"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc"
+
+[[package]]
+name = "unic-common"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc"
+
+[[package]]
+name = "unic-segment"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23"
+dependencies = [
+ "unic-ucd-segment",
+]
+
+[[package]]
+name = "unic-ucd-segment"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700"
+dependencies = [
+ "unic-char-property",
+ "unic-char-range",
+ "unic-ucd-version",
+]
+
+[[package]]
+name = "unic-ucd-version"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4"
+dependencies = [
+ "unic-common",
+]
+
 [[package]]
 name = "unicase"
 version = "2.6.0"
@@ -10159,6 +10225,7 @@ dependencies = [
  "scale-info",
  "serde",
  "serde_json",
+ "tera",
  "tokio",
  "version-compare",
  "version_check",
diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml
index d6d2b863e..528564f2a 100644
--- a/xtask/Cargo.toml
+++ b/xtask/Cargo.toml
@@ -29,3 +29,4 @@ serde_json = "1.0"
 tokio = { version = "1.15.0", features = ["macros"] }
 version_check = "0.9.2"
 version-compare = "0.0.11"
+tera = { version = "1", default-features = false }
\ No newline at end of file
diff --git a/xtask/res/templates/runtime-calls-category.md b/xtask/res/templates/runtime-calls-category.md
new file mode 100644
index 000000000..2553bfb35
--- /dev/null
+++ b/xtask/res/templates/runtime-calls-category.md
@@ -0,0 +1,25 @@
+There are **{{ calls_counter }}** {{ category_name }} calls from **{{ pallets | length }}** pallets.
+
+{% for pallet in pallets -%}
+### {{ pallet.name }} - {{ pallet.index }}
+
+{% for call in pallet.calls -%}
+#### {{ call.name }} - {{ call.index }}
+
+<details><summary><code>{{ call.name }}(
+    {%- for param in call.params -%}
+    {{ param.name }}{% if loop.last != true %}, {% endif %} 
+    {%- endfor -%}
+    )</code></summary>
+
+```rust
+{% for param in call.params -%}
+{{ param.name }}: {{ param.type_name }}
+{% endfor -%}
+```
+</details>
+
+{{ call.documentation }}
+
+{% endfor -%}
+{% endfor -%}
diff --git a/xtask/res/templates/runtime-calls.md b/xtask/res/templates/runtime-calls.md
new file mode 100644
index 000000000..d2c4c0fd0
--- /dev/null
+++ b/xtask/res/templates/runtime-calls.md
@@ -0,0 +1,34 @@
+# Runtime calls
+
+Calls are categorized according to the dispatch origin they require:
+
+1. **User calls**: the dispatch origin for this kind of call must be signed by
+the transactor. This is the only call category that can be submitted with an extrinsic.
+1. **Root calls**: This kind of call requires a special origin that can only be invoked
+through on-chain governance mechanisms.
+1. **Inherent calls**: This kind of call is invoked by the author of the block itself
+(usually automatically by the node).
+1. **Disabled calls**: These calls are disabled for different reasons (to be documented).
+
+
+{% set pallets = user_calls_pallets -%}
+{% set calls_counter = user_calls_counter -%}
+{% set category_name = "user" -%}
+## User calls
+
+{% include "runtime-calls-category.md" %}
+
+{% set pallets = root_calls_pallets -%}
+{% set calls_counter = root_calls_counter -%}
+{% set category_name = "root" -%}
+## Root calls
+
+{% include "runtime-calls-category.md" %}
+
+{% set pallets = disabled_calls_pallets %}
+{% set calls_counter = disabled_calls_counter %}
+{% set category_name = "disabled" %}
+## Disabled calls
+
+{% include "runtime-calls-category.md" -%}
+
diff --git a/xtask/src/gen_calls_doc.rs b/xtask/src/gen_calls_doc.rs
index af07b82e4..e1bd5e97d 100644
--- a/xtask/src/gen_calls_doc.rs
+++ b/xtask/src/gen_calls_doc.rs
@@ -17,15 +17,88 @@
 use anyhow::{bail, Context, Result};
 use codec::Decode;
 use scale_info::form::PortableForm;
+use serde::Serialize;
 use std::{
     fs::File,
     io::{Read, Write},
 };
+use tera::Tera;
+
+// consts
 
 const CALLS_DOC_FILEPATH: &str = "docs/api/runtime-calls.md";
+const TEMPLATES_GLOB: &str = "xtask/res/templates/*.md";
+
+// define structs and implementations
 
 type RuntimeCalls = Vec<Pallet>;
 
+#[derive(Clone, Serialize)]
+struct Pallet {
+    index: u8,
+    name: String,
+    calls: Vec<Call>,
+}
+
+impl Pallet {
+    fn new(
+        index: u8,
+        name: String,
+        scale_type_def: &scale_info::TypeDef<PortableForm>,
+    ) -> Result<Self> {
+        if let scale_info::TypeDef::Variant(calls_enum) = scale_type_def {
+            Ok(Self {
+                index,
+                name,
+                calls: calls_enum.variants().iter().map(Into::into).collect(),
+            })
+        } else {
+            bail!("Invalid metadata")
+        }
+    }
+}
+
+#[derive(Clone, Serialize)]
+struct Call {
+    documentation: String,
+    index: u8,
+    name: String,
+    params: Vec<CallParam>,
+}
+
+impl From<&scale_info::Variant<PortableForm>> for Call {
+    fn from(variant: &scale_info::Variant<PortableForm>) -> Self {
+        Self {
+            documentation: variant
+                .docs()
+                .to_vec()
+                .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(),
+        }
+    }
+}
+
+#[derive(Clone, Serialize)]
+struct CallParam {
+    name: String,
+    type_name: String,
+}
+
+impl From<&scale_info::Field<PortableForm>> for CallParam {
+    fn from(field: &scale_info::Field<PortableForm>) -> Self {
+        Self {
+            name: field.name().cloned().unwrap_or_default(),
+            type_name: field.type_name().cloned().unwrap_or_default(),
+        }
+    }
+}
+
 enum CallCategory {
     Disabled,
     Inherent,
@@ -81,65 +154,7 @@ impl CallCategory {
     }
 }
 
-#[derive(Clone)]
-struct Pallet {
-    index: u8,
-    name: String,
-    calls: Vec<Call>,
-}
-
-impl Pallet {
-    fn new(
-        index: u8,
-        name: String,
-        scale_type_def: &scale_info::TypeDef<PortableForm>,
-    ) -> Result<Self> {
-        if let scale_info::TypeDef::Variant(calls_enum) = scale_type_def {
-            Ok(Self {
-                index,
-                name,
-                calls: calls_enum.variants().iter().map(Into::into).collect(),
-            })
-        } else {
-            bail!("Invalid metadata")
-        }
-    }
-}
-
-#[derive(Clone)]
-struct Call {
-    docs: Vec<String>,
-    index: u8,
-    name: String,
-    params: Vec<CallParam>,
-}
-
-impl From<&scale_info::Variant<PortableForm>> for Call {
-    fn from(variant: &scale_info::Variant<PortableForm>) -> Self {
-        Self {
-            docs: variant.docs().to_vec(),
-            index: variant.index(),
-            name: variant.name().to_owned(),
-            params: variant.fields().iter().map(Into::into).collect(),
-        }
-    }
-}
-
-#[derive(Clone)]
-struct CallParam {
-    name: String,
-    type_name: String,
-}
-
-impl From<&scale_info::Field<PortableForm>> for CallParam {
-    fn from(field: &scale_info::Field<PortableForm>) -> Self {
-        Self {
-            name: field.name().cloned().unwrap_or_default(),
-            type_name: field.type_name().cloned().unwrap_or_default(),
-        }
-    }
-}
-
+/// generate runtime calls documentation
 pub(super) fn gen_calls_doc() -> Result<()> {
     // Read metadata
     let mut file = std::fs::File::open("resources/metadata.scale")
@@ -192,7 +207,9 @@ fn get_calls_from_metadata_v14(
     Ok(pallets)
 }
 
+/// use template to render markdown file with runtime calls documentation
 fn print_runtime_calls(pallets: RuntimeCalls) -> String {
+    // init variables
     let mut user_calls_counter = 0;
     let user_calls_pallets: RuntimeCalls = pallets
         .iter()
@@ -245,109 +262,24 @@ fn print_runtime_calls(pallets: RuntimeCalls) -> String {
         })
         .collect();
 
-    let mut output = String::new();
-
-    output.push_str("# Runtime calls\n\n");
-    output.push_str("Calls are categorized according to the dispatch origin they require:\n\n");
-    output.push_str(
-        r#"1. User calls: the dispatch origin for this kind of call must be Signed by
-the transactor. This is the only call category that can be submitted with an extrinsic.
-"#,
-    );
-    output.push_str(
-        r#"1. Root calls: This kind of call requires a special origin that can only be invoked
-through on-chain governance mechanisms.
-"#,
-    );
-    output.push_str(
-        r#"1. Inherent calls: This kind of call is invoked by the author of the block itself
-(usually automatically by the node).
-"#,
-    );
-    output.push_str(
-        r#"1. Disabled calls: These calls are disabled for different reasons (to be documented).
-"#,
-    );
-
-    output.push_str("\n\n## User calls\n\n");
-    output.push_str(&print_calls_category(
-        user_calls_counter,
-        "user",
-        user_calls_pallets,
-    ));
-
-    output.push_str("\n\n## Root calls\n\n");
-    output.push_str(&print_calls_category(
-        root_calls_counter,
-        "root",
-        root_calls_pallets,
-    ));
-
-    output.push_str("\n\n## Disabled calls\n\n");
-    output.push_str(&print_calls_category(
-        disabled_calls_counter,
-        "disabled",
-        disabled_calls_pallets,
-    ));
-
-    output
-}
-
-fn print_calls_category(calls_counter: usize, category_name: &str, pallets: Vec<Pallet>) -> String {
-    let mut output = String::new();
-    output.push_str(&format!(
-        "There are **{}** {} calls organized in **{}** pallets.\n",
-        calls_counter,
-        category_name,
-        pallets.len()
-    ));
-
-    for pallet in pallets {
-        output.push_str(&format!("\n### {}: {}\n\n", pallet.index, pallet.name));
-        for call in pallet.calls {
-            output.push_str(&format!(
-                "<details><summary>{}: {}({})</summary>\n<p>\n\n{}</p>\n</details>\n\n",
-                call.index,
-                call.name,
-                print_call_params(&call.params),
-                print_call_details(&call),
-            ));
+    // compile template
+    let tera = match Tera::new(TEMPLATES_GLOB) {
+        Ok(t) => t,
+        Err(e) => {
+            println!("Parsing error(s): {}", e);
+            ::std::process::exit(1);
         }
-    }
-    output
-}
+    };
 
-fn print_call_details(call: &Call) -> String {
-    let mut output = String::new();
-    output.push_str(&format!("### Index\n\n`{}`\n\n", call.index));
-    output.push_str(&format!(
-        "### Documentation\n\n{}\n\n",
-        call.docs
-            .iter()
-            .take_while(|line| !line.starts_with("# <weight>"))
-            .cloned()
-            .collect::<Vec<_>>()
-            .join("\n")
-    ));
-    if !call.params.is_empty() {
-        output.push_str("### Types of parameters\n\n```rust\n");
-        output.push_str(
-            &call
-                .params
-                .iter()
-                .map(|param| format!("{}: {}", param.name, param.type_name))
-                .collect::<Vec<_>>()
-                .join(",\n"),
-        );
-        output.push_str("\n```\n\n");
-    }
-    output
-}
+    // fills tera context for rendering
+    let mut context = tera::Context::new();
+    context.insert("user_calls_counter", &user_calls_counter);
+    context.insert("user_calls_pallets", &user_calls_pallets);
+    context.insert("root_calls_counter", &root_calls_counter);
+    context.insert("root_calls_pallets", &root_calls_pallets);
+    context.insert("disabled_calls_counter", &disabled_calls_counter);
+    context.insert("disabled_calls_pallets", &disabled_calls_pallets);
 
-fn print_call_params(call_params: &[CallParam]) -> String {
-    call_params
-        .iter()
-        .map(|param| param.name.clone())
-        .collect::<Vec<_>>()
-        .join(", ")
+    tera.render("runtime-calls.md", &context)
+        .expect("template error")
 }
-- 
GitLab