Skip to content

Commit

Permalink
feat: generate SmartModules using SMDK (#2630)
Browse files Browse the repository at this point in the history
Provides support to generate new SmartModules using `cargo-generate` as a libary.

Resolve: #2621

---

## Examples

### Generates SmartModule Projects using `smdk generate`

```bash
➜  Desktop smdk generate log-filter
Generating new SmartModule project: log-filter
🔧   Destination: /Users/esteban/Desktop/log-filter ...
🔧   Generating template ...
✔ 🤷   Which type of SmartModule would you like? · filter
✔ 🤷   Want to use SmartModule parameters? · false
🤷   SmartModule Version [default: git = "https://github.com/infinyon/fluvio.git"]: git = "https://github.com/infinyon/fluvio.git"
✔ 🤷   Want to use SmartModule init? · true
Ignoring: /var/folders/0g/l702s31x2mj4zdlw2_43rpyr0000gn/T/.tmpwQ4Yql/cargo-generate.toml
[1/5]   Done: Cargo.toml
[2/5]   Done: README.md
[3/5]   Done: Smart.toml
[4/5]   Done: src/lib.rs
[5/5]   Done: src
🔧   Moving generated files into: `/Users/esteban/Desktop/log-filter`...
💡   Initializing a fresh Git repository
✨   Done! New project created /Users/esteban/Desktop/log-filter
```

### Generated Project

```bash
total 24
drwxr-xr-x  7 esteban  staff   224B Oct  2 13:54 .
drwx------+ 6 esteban  staff   192B Oct  2 13:54 ..
drwxr-xr-x  9 esteban  staff   288B Oct  2 13:54 .git
-rw-r--r--  1 esteban  staff   358B Oct  2 13:54 Cargo.toml
-rw-r--r--  1 esteban  staff   1.8K Oct  2 13:54 README.md
-rw-r--r--  1 esteban  staff   152B Oct  2 13:54 Smart.toml
drwxr-xr-x  3 esteban  staff    96B Oct  2 13:54 src
```

### Generated `src/lib.rs` with the provided options

```rust
use fluvio_smartmodule::dataplane::smartmodule::{SmartModuleExtraParams, SmartModuleInitError};

use once_cell::sync::OnceCell;
use fluvio_smartmodule::eyre;



use fluvio_smartmodule::{smartmodule, Result, Record};

#[smartmodule(filter)]
pub fn filter(record: &Record) -> Result<bool> {
    let string = std::str::from_utf8(record.value.as_ref())?;
    Ok(string.contains('a'))
}




static CRITERIA: OnceCell<String> = OnceCell::new();

#[smartmodule(init)]
fn init(params: SmartModuleExtraParams) -> Result<()> {
    // You can refer to the example SmartModules in Fluvio's GitHub Repository
    // https://github.com/infinyon/fluvio/tree/master/smartmodule
    if let Some(key) = params.get("key") {
        CRITERIA.set(key.clone()).map_err(|err| eyre!("failed setting key: {:#?}", err))
    } else {
        Err(SmartModuleInitError::MissingParam("key".to_string()).into())
    }
}
```

### Building SMDK Project

```bash
➜  Desktop cd ./log-filter
➜  log-filter git:(main) ✗ smdk build
    Updating git repository `https://github.com/infinyon/fluvio.git`
    Updating crates.io index
   Compiling proc-macro2 v1.0.46
```

### Testing SM Project using `smdk test`

```
➜  log-filter git:(main) ✗ smdk test --params "key=a" --text "testing"
project name: "log-filter"
loading module at: target/wasm32-unknown-unknown/release-lto/log_filter.wasm
0 records outputed
➜  log-filter git:(main) ✗ smdk test --params "key=a" --text "tasty"
project name: "log-filter"
loading module at: target/wasm32-unknown-unknown/release-lto/log_filter.wasm
1 records outputed
tasty
```
  • Loading branch information
EstebanBorai committed Oct 2, 2022
1 parent f3f381b commit af2fe72
Show file tree
Hide file tree
Showing 9 changed files with 837 additions and 16 deletions.
651 changes: 640 additions & 11 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions crates/smdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ dirs = "4.0.0"
thiserror = "1.0.20"
anyhow = { version = "1.0.38" }
cargo_metadata = "0.15.0"
cargo-generate = "0.16.0"
convert_case = "0.6.0"
include_dir = "0.7.2"
tempdir = "0.3.7"

fluvio = { path = "../fluvio", default-features = false }
fluvio-protocol = { path = "../fluvio-protocol", features=["record","api"] }
Expand Down
6 changes: 5 additions & 1 deletion crates/smdk/src/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use clap::Parser;
use anyhow::Result;

use crate::build::BuildOpt;
use crate::generate::GenerateOpt;
use crate::test::TestOpt;
use crate::load::LoadOpt;

Expand All @@ -10,14 +11,17 @@ use crate::load::LoadOpt;
pub enum SmdkCommand {
/// Builds SmartModule into WASM
Build(BuildOpt),
/// Generates a new SmartModule Project
Generate(GenerateOpt),
Test(TestOpt),
Load(LoadOpt),
}

impl SmdkCommand {
pub(crate) fn process(self) -> Result<()> {
match self {
Self::Build(opt) => opt.process(),
SmdkCommand::Build(opt) => opt.process(),
SmdkCommand::Generate(opt) => opt.process(),
SmdkCommand::Test(opt) => opt.process(),
SmdkCommand::Load(opt) => opt.process(),
}
Expand Down
150 changes: 150 additions & 0 deletions crates/smdk/src/generate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
use anyhow::{Error, Result};
use clap::Parser;
use cargo_generate::{GenerateArgs, TemplatePath, generate};
use include_dir::{Dir, include_dir};
use tempdir::TempDir;

static SMART_MODULE_TEMPLATE: Dir<'static> =
include_dir!("$CARGO_MANIFEST_DIR/../../smartmodule/cargo_template");

/// Generate new SmartModule project
#[derive(Debug, Parser)]
pub struct GenerateOpt {
/// SmartModule Project Name
name: String,
/// Template to generate project from.
///
/// Must be a GIT repository
#[clap(long)]
template: Option<String>,
}

/// Abstraction on different of template options available for generating a
/// new SmartModule project.
///
/// May hold a reference to a `TempDir` which should not be dropped before
/// accomplishing the project generation procedure.
struct Template {
template_path: TemplatePath,
_temp_dir: Option<TempDir>,
}

impl Template {
/// Extracts inlined directory contents into a temporary directory and
/// builds a `TemplatePath` instance with the `path` pointing to the temp
/// directory created.
///
/// Is important to hold the reference to the `_temp_dir` until generation
/// process is completed, otherwise the temp directory will be deleted
/// before reaching the generation process.
fn inline() -> Result<Self> {
let temp_dir = TempDir::new("smartmodule_template")?;
let path = temp_dir.path().to_str().unwrap().to_string();
SMART_MODULE_TEMPLATE
.extract(&temp_dir)
.map_err(Error::from)?;
let template = Self {
template_path: TemplatePath {
git: None,
auto_path: None,
subfolder: None,
test: false,
branch: None,
tag: None,
path: Some(path),
favorite: None,
},
_temp_dir: Some(temp_dir),
};

Ok(template)
}

fn git(repo_uri: String) -> Result<Self> {
Ok(Self {
template_path: TemplatePath {
git: Some(repo_uri),
auto_path: None,
subfolder: None,
test: false,
branch: None,
tag: None,
path: None,
favorite: None,
},
_temp_dir: None,
})
}
}

impl GenerateOpt {
pub(crate) fn process(self) -> Result<()> {
println!("Generating new SmartModule project: {}", self.name);

let Template {
template_path,
_temp_dir,
} = if let Some(git_uri) = self.template {
Template::git(git_uri)?
} else {
Template::inline()?
};

let args = GenerateArgs {
template_path,
name: Some(self.name.clone()),
list_favorites: false,
force: false,
verbose: true,
template_values_file: None,
silent: false,
config: None,
vcs: None,
lib: false,
bin: false,
ssh_identity: None,
define: Vec::default(),
init: false,
destination: None,
force_git_init: false,
allow_commands: false,
overwrite: false,
other_args: None,
};

generate(args).map_err(Error::from)?;

Ok(())
}
}

#[cfg(test)]
mod test {
use std::fs::read_dir;

use super::Template;

#[test]
fn test_inline_template() {
let template = Template::inline().unwrap();

assert!(
template._temp_dir.is_some(),
"The temporary directory reference is not provided"
);

let temp_dir = template._temp_dir.unwrap();
let temp_dir = read_dir(temp_dir.path());
assert!(temp_dir.is_ok(), "The temporary directory doesn't exists");

let mut temp_dir = temp_dir.unwrap();
let smart_toml =
temp_dir.find(|entry| entry.as_ref().unwrap().file_name().eq("Smart.toml"));

assert!(
smart_toml.is_some(),
"Smart.toml from template is not included in temporary dir"
);
assert!(smart_toml.unwrap().is_ok());
}
}
1 change: 1 addition & 0 deletions crates/smdk/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod build;
mod cmd;
mod generate;
mod test;
mod load;
mod wasm;
Expand Down
9 changes: 7 additions & 2 deletions smartmodule/cargo_template/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ crate-type = ['cdylib']
fluvio-smartmodule = { {{smartmodule-version}} }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
{% if smartmodule-init %}
{% if smartmodule-type == "filter" %}
once_cell = "1.13.0"
{% endif %}
{% endif %}


[profile.release]
[profile.release-lto]
inherits = "release"
lto = true
2 changes: 0 additions & 2 deletions smartmodule/cargo_template/Smart.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,5 @@ package_version = "0.1"
description = "{{project-description}}"
license = "Apache-2.0"



[cargo]
profile = "release"
1 change: 1 addition & 0 deletions smartmodule/cargo_template/cargo-generate.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ type = "string"
prompt = "Which type of SmartModule would you like?"
choices = ["filter", "map", "filter-map", "array-map", "aggregate"]
default = "filter"

[placeholders.smartmodule-init]
type = "bool"
prompt = "Want to use SmartModule init?"
Expand Down
30 changes: 30 additions & 0 deletions smartmodule/cargo_template/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
{% if smartmodule-init %}
use fluvio_smartmodule::dataplane::smartmodule::{SmartModuleExtraParams, SmartModuleInitError};
{% if smartmodule-type == "filter" %}
use once_cell::sync::OnceCell;
use fluvio_smartmodule::eyre;
{% endif %}
{% endif %}
{% if smartmodule-type == "filter" %}
use fluvio_smartmodule::{smartmodule, Result, Record};

Expand Down Expand Up @@ -79,3 +86,26 @@ pub fn aggregate(accumulator: RecordData, current: &Record{% if smartmodule-para
#[derive(fluvio_smartmodule::SmartOpt, Default)]
pub struct SmartModuleOpt;
{% endif %}
{% if smartmodule-init %}
{% if smartmodule-type == "filter" %}
static CRITERIA: OnceCell<String> = OnceCell::new();

#[smartmodule(init)]
fn init(params: SmartModuleExtraParams) -> Result<()> {
// You can refer to the example SmartModules in Fluvio's GitHub Repository
// https://github.com/infinyon/fluvio/tree/master/smartmodule
if let Some(key) = params.get("key") {
CRITERIA.set(key.clone()).map_err(|err| eyre!("failed setting key: {:#?}", err))
} else {
Err(SmartModuleInitError::MissingParam("key".to_string()).into())
}
}
{% else %}
#[smartmodule(init)]
fn init(params: SmartModuleExtraParams) -> Result<()> {
// You can refer to the example SmartModules in Fluvio's GitHub Repository
// https://github.com/infinyon/fluvio/tree/master/smartmodule
todo!("Provide initialization logic for your SmartModule")
}
{% endif %}
{% endif %}

0 comments on commit af2fe72

Please sign in to comment.