Skip to content

Commit

Permalink
add clap_layered (#16)
Browse files Browse the repository at this point in the history
  • Loading branch information
reiase authored Mar 17, 2024
1 parent 1056e72 commit e7902cf
Show file tree
Hide file tree
Showing 11 changed files with 459 additions and 40 deletions.
14 changes: 13 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ version = "0.5.9"
license = "Apache-2.0"
description = "A high performance configuration system for Rust and Python."
homepage = "https://reiase.github.io/hyperparameter/"
readme = "README.md"
readme = "examples/rust/README.md"
repository = "https://github.com/reiase/hyperparameter"
authors = ["reiase <reiase@gmail.com>"]
edition = "2021"
Expand Down Expand Up @@ -49,3 +49,15 @@ overflow-checks = false
[[bench]]
name = "bench_apis"
harness = false

[[example]]
name = "clap_mini"
path = "examples/rust/clap_mini.rs"

[[example]]
name = "clap_layered"
path = "examples/rust/clap_layered.rs"

[[example]]
name = "clap_full"
path = "examples/rust/clap_full.rs"
29 changes: 29 additions & 0 deletions benches/bench_apis.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use config::Config;
use criterion::{black_box, criterion_group, criterion_main, Criterion};

use hyperparameter::*;
Expand All @@ -16,6 +17,12 @@ fn foo_with_ps(x: i64) -> i64 {
}
}

#[inline(never)]
fn foo_with_config(x: i64, cfg: &Config) -> i64 {
let y = cfg.get_int("y").unwrap();
x + y
}

#[inline(never)]
fn call_foo(nloop: i64) -> i64 {
let mut sum = 0;
Expand Down Expand Up @@ -66,6 +73,15 @@ fn call_foo_with_ps_and_raw_btree(nloop: i64) -> i64 {
sum
}

#[inline(never)]
fn call_foo_with_config_rs(nloop: i64, cfg: &Config) -> i64 {
let mut sum = 0;
for i in 0..nloop {
sum = sum + foo_with_config(i, cfg);
}
sum
}

pub fn bench_apis(c: &mut Criterion) {
c.bench_function("raw api", |b| b.iter(|| call_foo(black_box(10000))));
}
Expand All @@ -88,6 +104,19 @@ pub fn bench_apis_with_ps(c: &mut Criterion) {
});
}

pub fn bench_config_rs(c: &mut Criterion) {
let cfg = config::Config::builder()
.add_source(config::File::from_str(
"{\"y\": 1}",
config::FileFormat::Json,
))
.build()
.unwrap();
c.bench_function("raw api with config-rs", |b| {
b.iter(|| call_foo_with_config_rs(black_box(10000), &cfg))
});
}

criterion_group!(
benches,
bench_apis,
Expand Down
33 changes: 0 additions & 33 deletions examples/clap_app.rs

This file was deleted.

135 changes: 135 additions & 0 deletions examples/rust/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@

# High-Performance Rust Configuration System

Hyperparameter is a high-performance configuration system designed for Rust and Python, supporting the following features:

1. **High Performance**: Provides fast parameter access, allowing users to freely read and write parameters in the code without worrying about performance issues.
2. **Scope Management**: Manages the definition and use of parameters through scopes, ensuring the isolation and safety of parameter values.
3. **Command Line Integration**: Automatically displays all parameters and their help information in the application's command line.

## Minimal Example

Here is a simple example demonstrating how to use Hyperparameter to build a command-line program:

```rust
use clap::Parser;
use hyperparameter::*;

#[derive(Parser)]
#[command(after_long_help=generate_params_help())]
struct CommandLineArgs {
/// Specifies parameters in the format `-D key=value` on the command line
#[arg(short = 'D', long)]
define: Vec<String>,
}

fn main() {
let args = CommandLineArgs::parse();
with_params! {
params ParamScope::from(&args.define); // Receives all parameters from the command line

// Retrieves the parameter `example.param1`, using a default value of `1` if not specified.
println!("param1={}", get_param!(example.param1, 1));
// Retrieves the parameter `example.param2`, displaying help information when `<app> --help` is executed.
println!("param2={}", get_param!(example.param2, false, "help for example.param2"));
}
}
```
When executing `clap_mini --help`, a section `Hyperparameters` appears at the end of the help information, explaining the names of hyperparameters and their help information:

```
Usage: clap_mini [OPTIONS]
Options:
-D, --define <DEFINE>
Specifies hyperparameters in the format `-D key=value` via the command line
-h, --help
Print help (see a summary with '-h')
Hyperparameters:
example.param2
help for example.param2
```
Following the prompt, you can specify the parameter value using `-D example.param2=<value>`:

```shell
$ clap_mini # Default values
param1=1
param2=false

$ clap_mini -D example.param2=true
param1=1
param2=true
```

## Using Configuration Files

Hyperparameter also supports the use of configuration files. The following example shows how to integrate configuration files, command-line parameters, and user-defined configurations:

```rust
use std::path::Path;

use clap::Parser;
use config::{self, File};
use hyperparameter::*;

#[derive(Parser)]
#[command(after_long_help=generate_params_help())]
struct CommandLineArgs {
/// Specifies parameters in the format `-D key=value` on the command line
#[arg(short = 'D', long)]
define: Vec<String>,

/// Specifies the configuration file path in the format `-C <path>` on the command line
#[arg(short = 'C', long, default_value = "examples/rust/cfg.toml")]
config: String,
}

fn main() {
let args = CommandLineArgs::parse();
let config_path = Path::new(&args.config);
let config = config::Config::builder()
.add_source(File::from(config_path))
.build().unwrap();

println!("param1={} // No scope", get_param!(example.param1, "default".to_string()));

with_params! { // Configuration file parameter scope
params config.param_scope();

println!("param1={} // cfg file scope", get_param!(example.param1, "default".to_string()));
with_params! { // Command-line arguments scope
params ParamScope::from(&args.define);

println!("param1={} // cmdline args scope", get_param!(example.param1, "default".to_string(), "Example param1"));
with_params! { // User-defined scope
set example.param1= "scoped".to_string();

println!("param1={} // user-defined scope", get_param!(example.param1, "default".to_string()));
}
}
}
}
```
Directly executing the command `clap_layered` yields the following output:

```
param1=default // No scope # Outside any specific scope
param1=from config // cfg file scope # Entered configuration file scope, parameter value affected by the config file
param1=from config // cmdline args scope # Entered command-line scope, command-line overrides config file
param1=scoped // user-defined scope # Entered user-defined scope, custom value overrides command-line
```
As can be seen:
1. Nested scopes override layer by layer, with parameters in an inner scope overriding those in an outer scope.
2. The command-line scope did not specify the parameter, thus inheriting the value from the outer scope.

If the command line specifies the value of `example.param1`, the following input is obtained:

```shell
$ clap_layered -D example.param1="from cmdline"
param1=default // No scope
param1=from config // cfg file scope
param1=from cmdline // cmdline args scope
param1=scoped // user-defined scope
```
131 changes: 131 additions & 0 deletions examples/rust/README.zh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# 高性能Rust配置系统

Hyperparameter 是一个为 Rust 和 Python 设计的高性能配置系统,它支持以下特性:

1. 高性能:提供快速的参数访问,允许用户在代码中自由读写参数,无需担心性能问题。
2. 作用域管理:通过作用域管理参数的定义和使用,确保参数值的隔离和安全。
3. 命令行集成:支持在应用的命令行中自动展示所有参数及其帮助信息。


## 最小示例

以下是一个简单示例,展示如何使用 Hyperparameter 构建一个命令行程序:
```rust
use clap::Parser;
use hyperparameter::*;

#[derive(Parser)]
#[command(after_long_help=generate_params_help())]
struct CommandLineArgs {
/// 在命令行以`-D key=value`格式指定参数
#[arg(short = 'D', long)]
define: Vec<String>,
}

fn main() {
let args = CommandLineArgs::parse();
with_params! {
params ParamScope::from(&args.define); // 从命令行接收全部参数

// 读取参数`example.param1`,若未指定则使用默认值`1`.
println!("param1={}", get_param!(example.param1, 1));
// 读取参数`example.param2`,在执行`<app> --help`时输出帮助信息.
println!("param2={}", get_param!(example.param2, false, "help for example.param2"));
}
}
```
当执行`clap_mini --help`时,在帮助信息结尾出现了`Hyperparameters`一节,说明了超参名称及其帮助信息:
```
Usage: clap_mini [OPTIONS]
Options:
-D, --define <DEFINE>
Specifies hyperparameters in the format `-D key=value` via the command line
-h, --help
Print help (see a summary with '-h')
Hyperparameters:
example.param2
help for example.param2
```
根据提示,可以使用`-D example.param2=<value>`来指定参数取值:
```shell
$ clap_mini # 默认取值
param1=1
param2=false

$ clap_mini -D example.param2=true
param1=1
param2=true
```

## 结合配置文件使用

Hyperparameter 也支持与配置文件结合使用。以下示例展示了如何整合配置文件、命令行参数和用户自定义配置:

```rust
use std::path::Path;

use clap::Parser;
use config::{self, File};
use hyperparameter::*;

#[derive(Parser)]
#[command(after_long_help=generate_params_help())]
struct CommandLineArgs {
/// 在命令行以`-D key=value`格式指定参数
#[arg(short = 'D', long)]
define: Vec<String>,

/// 在命令行以`-C <path>`格式指定配置文件
#[arg(short = 'C', long, default_value = "examples/rust/cfg.toml")]
config: String,
}

fn main() {
let args = CommandLineArgs::parse();
let config_path = Path::new(&args.config);
let config = config::Config::builder()
.add_source(File::from(config_path))
.build().unwrap();

println!("param1={} // No scope", get_param!(example.param1, "default".to_string()));

with_params! { // 配置文件参数作用域
params config.param_scope();

println!("param1={} // cfg file scope", get_param!(example.param1, "default".to_string()));
with_params! { // 命令行参数作用域
params ParamScope::from(&args.define);

println!("param1={} // cmdline args scope", get_param!(example.param1, "default".to_string(), "Example param1"));
with_params! { // 用户自定义作用域
set example.param1= "scoped".to_string();

println!("param1={} // user-defined scope", get_param!(example.param1, "default".to_string()));
}
}
}
}

```
直接执行命令`clap_layered`后得到如下输出:
```
param1=default // No scope # 未进入任何scope
param1=from config // cfg file scope # 进入配置文件scope,参数取值受配置文件影响
param1=from config // cmdline args scope # 进入命令行scope,命令行覆盖配置文件
param1=scoped // user-defined scope # 进入自定义scope,自定义取值覆盖命令行
```
可以看到:
1. 嵌套的scope逐层覆盖,内层scope中参数覆盖外层scope;
2. 命令行scope未指定参数,因此继承了外层scope的取值

若使用命令行指定`example.param1`的取值,则得到如下输入:
```shell
$ clap_layered -D example.param1="from cmdline"
param1=default // No scope
param1=from config // cfg file scope
param1=from cmdline // cmdline args scope
param1=scoped // user-defined scope
```
2 changes: 2 additions & 0 deletions examples/rust/cfg.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[example]
param1 = "from config"
Loading

0 comments on commit e7902cf

Please sign in to comment.