Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(scaffold): allow for multiple generators + walletd client #933

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.lock

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

5 changes: 4 additions & 1 deletion applications/tari_scaffolder/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ license.workspace = true
[dependencies]
tari_dan_engine = { workspace = true }

clap = { workspace = true }
anyhow = { workspace = true }
clap = { workspace = true, features = ["derive"] }
liquid = { workspace = true }
liquid-core = "0.26.4"
convert_case = { workspace = true }
serde_json = "1.0.108"
serde = { version = "1.0.195", features = ["derive"] }
14 changes: 14 additions & 0 deletions applications/tari_scaffolder/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Tari Scaffolder

## Basic usage

```shell
cargo run --release --bin tari_scaffolder -- -c config.json --generator rust-template-cli scaffold /path/to/the/template/source/or/wasm-binary.wasm
```

## Generators

- rust-template-cli - generates a rust cli application for the template
- react-admin-web (TODO) - generates a basic admin web interface for the template using vite.js and React.


Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@ edition = "2021"


[dependencies]
{% if crates_root == "[crates]" %}
tari_wallet_daemon_client = { git = "https://github.com/tari-project/tari-dan", branch = "development" }
tari_engine_types = { git = "https://github.com/tari-project/tari-dan", branch = "development" }
tari_template_lib = { git = "https://github.com/tari-project/tari-dan", branch = "development" }
tari_transaction = { git = "https://github.com/tari-project/tari-dan", branch = "development" }
{% else %}
{% if crates_root %}
tari_wallet_daemon_client={path="{{crates_root}}/clients/wallet_daemon_client"}
tari_engine_types={path="{{crates_root}}/dan_layer/engine_types"}
tari_template_lib={path="{{crates_root}}/dan_layer/template_lib"}
tari_transaction ={path="{{crates_root}}/dan_layer/transaction"}
{% else %}
tari_wallet_daemon_client = { git = "https://github.com/tari-project/tari-dan", branch = "development" }
tari_engine_types = { git = "https://github.com/tari-project/tari-dan", branch = "development" }
tari_template_lib = { git = "https://github.com/tari-project/tari-dan", branch = "development" }
tari_transaction = { git = "https://github.com/tari-project/tari-dan", branch = "development" }
{% endif %}
tari_utilities ="*"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ use tari_template_lib::args;
{% if c.is_method %}

pub async fn run(self, mut client: DaemonClient, dump_buckets: bool, is_dry_run: bool, fees: u64) {
let method = "{{c.name}}".to_string();

let mut instructions = vec![];
{% if c.requires_buckets %}
Expand All @@ -123,7 +122,7 @@ use tari_template_lib::args;
instructions.push(
Instruction::CallMethod {
component_address: ComponentAddress::from_hex(&self.component_address).unwrap(),
method,
method: "{{c.name}}".to_string(),
args: args![
{% for arg in c.args %}
{% if arg.name != "self" %}
Expand Down Expand Up @@ -187,7 +186,6 @@ use tari_template_lib::args;
{% endif %}

let transaction_id = client.submit_instructions(instructions, dump_buckets, false, fees, vec![]).await;

println!("submitted");
let result = client.wait_for_transaction_result(transaction_id).await;
println!("result: {:?}", result);
Expand Down
34 changes: 24 additions & 10 deletions applications/tari_scaffolder/src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// Copyright 2022 The Tari Project
// SPDX-License-Identifier: BSD-3-Clause

use std::path::PathBuf;
use std::{collections::HashMap, path::PathBuf};

use clap::Parser;

use crate::command::Command;
use crate::{command::Command, generators::GeneratorType};

#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
Expand All @@ -19,21 +19,35 @@ pub(crate) struct Cli {
#[clap(subcommand)]
pub command: Command,

#[clap(long)]
pub crates_root: Option<PathBuf>,

#[clap(long, short = 'c', alias = "clean")]
#[clap(long, alias = "clean")]
pub clean: bool,

#[clap(long, short = 'o', alias = "output", default_value = "./output")]
pub output_path: PathBuf,
#[clap(long, short = 'o', alias = "output")]
pub output_path: Option<PathBuf>,

#[clap(long, short = 'g', alias = "generator")]
pub generator: GeneratorType,

#[clap(long, short = 'd', alias = "data", value_parser = parse_hashmap)]
pub data: Option<HashMap<String, String>>,

#[clap(long, short = 't', alias = "template")]
pub template_address: Option<String>,
#[clap(long, short = 'c', alias = "config")]
pub generator_config_file: Option<PathBuf>,
}

impl Cli {
pub fn init() -> Self {
Self::parse()
}
}

fn parse_hashmap(input: &str) -> anyhow::Result<HashMap<String, String>> {
let mut map = HashMap::new();
for pair in input.split(',') {
let mut parts = pair.splitn(2, ':');
let key = parts.next().unwrap().to_string();
let value = parts.next().unwrap_or("").to_string();
map.insert(key, value);
}
Ok(map)
}
181 changes: 181 additions & 0 deletions applications/tari_scaffolder/src/generators/liquid/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
// Copyright 2024 The Tari Project
// SPDX-License-Identifier: BSD-3-Clause

mod snake_case;

use std::fs;

use convert_case::{Case, Casing};
use tari_dan_engine::abi;

use crate::generators::{CodeGenerator, GeneratorOpts, TemplateDefinition};

pub enum LiquidTemplate {
RustCli,
}

impl LiquidTemplate {
const fn get_template(&self) -> &'static [(&'static str, &'static str)] {
match self {
LiquidTemplate::RustCli => &[
(
"Cargo.toml",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/liquid_templates/rust_cli/Cargo.toml.liquid"
)),
),
(
".gitignore",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/liquid_templates/rust_cli/.gitignore.liquid"
)),
),
(
"src/main.rs",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/liquid_templates/rust_cli/src/main.rs.liquid"
)),
),
(
"src/cli.rs",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/liquid_templates/rust_cli/src/cli.rs.liquid"
)),
),
(
"src/daemon_client.rs",
include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/liquid_templates/rust_cli/src/daemon_client.rs.liquid"
)),
),
],
}
}
}

pub struct LiquidGenerator {
template: LiquidTemplate,
opts: GeneratorOpts,
}

impl LiquidGenerator {
pub const fn new(template: LiquidTemplate, opts: GeneratorOpts) -> Self {
Self { template, opts }
}

fn build_vars(&self, template: &TemplateDefinition) -> liquid_core::Object {
let opts = self.opts.liquid.as_ref().unwrap();

let mut globals = liquid::object!({
"template_name": &template.name,
"commands": []
});
globals.extend(
opts.variables
.iter()
.map(|(k, v)| (k.clone().into(), json_value_to_liquid_value(v.clone()))),
);

for f in template.template.functions() {
let mut args = vec![];
let mut is_method = false;
let mut requires_buckets = false;
let mut bucket_output = false;
for a in &f.arguments {
args.push(liquid::object!({
"name": a.name,
"arg_type": a.arg_type.to_string(),
}));
if a.arg_type.to_string() == "Bucket" {
requires_buckets = true;
}
if a.name == "self" {
is_method = true;
}
}

if let abi::Type::Other { name } = &f.output {
if name == "Bucket" {
bucket_output = true;
}
}

let arr = globals.get_mut("commands").unwrap().as_array_mut().unwrap();
arr.push(liquid_core::Value::Object(liquid::object!({
"name": f.name,
"title": f.name.to_case(Case::UpperCamel),
"args" : args,
"is_method": is_method,
"is_mut": f.is_mut,
"output": f.output.to_string(),
"requires_buckets": requires_buckets,
"bucket_output": bucket_output,
})));
}
globals
}
}

impl CodeGenerator for LiquidGenerator {
fn generate(&self, template: &TemplateDefinition) -> anyhow::Result<()> {
let opts = &self.opts;
fs::create_dir_all(opts.output_path.join("src"))?;

let templates = self.template.get_template();

let vars = self.build_vars(template);

for (out_file, content) in templates {
fs::write(opts.output_path.join(out_file), replace_tokens(content, &vars)?)?;
}

if !self.opts.liquid.as_ref().unwrap().skip_format {
std::process::Command::new("cargo")
.args(["fmt"])
.current_dir(&opts.output_path)
.status()?;
}

Ok(())
}
}

fn replace_tokens(in_file: &str, globals: &liquid_core::Object) -> anyhow::Result<String> {
let template = liquid::ParserBuilder::with_stdlib()
.filter(snake_case::SnakeCase)
.build()?
.parse(in_file)?;

let built_template = template.render(globals)?;
Ok(built_template)
}

fn json_value_to_liquid_value(value: serde_json::Value) -> liquid_core::Value {
match value {
serde_json::Value::Null => liquid_core::Value::Nil,
serde_json::Value::Bool(b) => liquid_core::Value::scalar(b),
serde_json::Value::Number(n) => {
if n.is_i64() {
liquid_core::Value::scalar(n.as_i64().unwrap())
} else {
liquid_core::Value::scalar(n.as_f64().unwrap())
}
},
serde_json::Value::String(s) => liquid_core::Value::scalar(s),
serde_json::Value::Array(a) => liquid_core::Value::Array(
a.into_iter()
.map(json_value_to_liquid_value)
.collect::<Vec<liquid_core::Value>>(),
),
serde_json::Value::Object(o) => liquid_core::Value::Object(
o.into_iter()
.map(|(k, v)| (k.into(), json_value_to_liquid_value(v)))
.collect::<liquid_core::Object>(),
),
}
}
56 changes: 56 additions & 0 deletions applications/tari_scaffolder/src/generators/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright 2024 The Tari Project
// SPDX-License-Identifier: BSD-3-Clause

use std::{collections::HashMap, path::PathBuf, str::FromStr};

use serde::Deserialize;
use tari_dan_engine::{abi::TemplateDef, template::LoadedTemplate};

pub mod liquid;

pub struct TemplateDefinition {
pub name: String,
pub template: TemplateDef,
}

impl From<LoadedTemplate> for TemplateDefinition {
fn from(loaded_template: LoadedTemplate) -> Self {
Self {
name: loaded_template.template_name().to_string(),
template: loaded_template.template_def().clone(),
}
}
}

pub trait CodeGenerator {
fn generate(&self, template: &TemplateDefinition) -> anyhow::Result<()>;
}

#[derive(Debug, Clone, Deserialize)]
pub struct GeneratorOpts {
pub output_path: PathBuf,
pub liquid: Option<LiquidGeneratorOpts>,
}

#[derive(Debug, Clone, Deserialize)]
pub struct LiquidGeneratorOpts {
#[serde(default)]
pub skip_format: bool,
pub variables: HashMap<String, serde_json::Value>,
}

#[derive(Debug, Clone, Copy)]
pub enum GeneratorType {
RustTemplateCli,
}

impl FromStr for GeneratorType {
type Err = anyhow::Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"rust-template-cli" => Ok(GeneratorType::RustTemplateCli),
_ => Err(anyhow::anyhow!("Invalid generator type")),
}
}
}
Loading
Loading