Skip to content

Commit

Permalink
feat(scaffold): allow for multiple generators + walletd client
Browse files Browse the repository at this point in the history
  • Loading branch information
sdbondi committed Feb 9, 2024
1 parent 4db1163 commit b0854e7
Show file tree
Hide file tree
Showing 19 changed files with 490 additions and 136 deletions.
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)
}
File renamed without changes.
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

0 comments on commit b0854e7

Please sign in to comment.