1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
use std::path::PathBuf;
use clap::{ArgGroup, Parser as ClapParser, Subcommand, ValueEnum};
use ferricel_types::LogLevel;
/// Ferricel - CEL compiler to WebAssembly
#[derive(ClapParser)]
#[command(name = "ferricel")]
#[command(about = "Compile CEL expressions to WebAssembly", long_about = None)]
pub struct Cli {
#[command(subcommand)]
pub command: Commands,
}
/// CLI-friendly wrapper for LogLevel that implements ValueEnum
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum LogLevelArg {
/// Debug level (most verbose)
Debug,
/// Info level (default)
Info,
/// Warning level
Warn,
/// Error level (least verbose)
Error,
}
impl From<LogLevelArg> for LogLevel {
fn from(arg: LogLevelArg) -> Self {
match arg {
LogLevelArg::Debug => LogLevel::Debug,
LogLevelArg::Info => LogLevel::Info,
LogLevelArg::Warn => LogLevel::Warn,
LogLevelArg::Error => LogLevel::Error,
}
}
}
#[derive(Subcommand)]
pub enum Commands {
/// Build a CEL expression into a WebAssembly module
#[command(group = ArgGroup::new("cel_source")
.required(true)
.args(&["expression", "expression_file"]))]
Build {
/// CEL expression to compile (mutually exclusive with --expression-file)
#[arg(short, long, conflicts_with = "expression_file")]
expression: Option<String>,
/// Path to file containing CEL expression (mutually exclusive with --expression)
#[arg(long, conflicts_with = "expression")]
expression_file: Option<PathBuf>,
/// Output file path
#[arg(short, long, default_value = "final_cel_program.wasm")]
output: PathBuf,
/// Path to protocol buffer descriptor file(s) (can be specified multiple times)
/// Use protoc --descriptor_set_out to generate these files
#[arg(long = "proto-descriptor")]
proto_descriptors: Vec<PathBuf>,
/// Container (namespace) for type name resolution
/// Example: "google.protobuf" allows using "Timestamp" instead of "google.protobuf.Timestamp"
#[arg(long)]
container: Option<String>,
/// Declare host extension functions (can be specified multiple times).
/// Mutually exclusive with --extensions-file.
///
/// Format: [namespace.]function:style:arity
///
/// namespace Optional dot-separated namespace prefix. Everything before
/// the last dot is the namespace; the last segment is the
/// function name.
///
/// style One of:
/// global - callable as func(args) or ns.func(args)
/// receiver - callable as value.func(extra_args)
/// (receiver is always args\[0\])
/// both - supports both calling conventions
///
/// arity Total number of arguments the host receives, including
/// the receiver for receiver-style calls.
///
/// Examples:
/// --extensions abs:global:1
/// Adds a global function abs(x) with 1 argument.
///
/// --extensions math.sqrt:global:1
/// Adds math.sqrt(x) — namespace "math", function "sqrt", 1 arg.
///
/// --extensions math.pow:global:2
/// Adds math.pow(base, exp) — 2 args.
///
/// --extensions reverse:receiver:1
/// Adds x.reverse() — receiver-style, receiver counts as the 1 arg.
///
/// --extensions greet:both:2
/// Adds greet(name, lang) and name.greet(lang) — both styles, 2 args.
///
/// Note: the host is responsible for providing implementations at evaluation
/// time. Extensions declared here but not implemented by the host will produce
/// a runtime error when the expression is evaluated.
#[arg(
long = "extensions",
conflicts_with = "extensions_file",
value_name = "SPEC"
)]
extensions: Vec<String>,
/// Path to a JSON file declaring host extension functions.
/// Mutually exclusive with --extensions.
///
/// The file must contain a JSON array of extension declaration objects.
/// Each object has the following fields:
///
/// namespace (string | null) Optional namespace prefix,
/// e.g. "math" for math.abs().
/// function (string) Function name, e.g. "abs".
/// global_style (bool) True if callable as func(args)
/// or ns.func(args).
/// receiver_style (bool) True if callable as value.func(args).
/// Receiver is always args\[0\].
/// num_args (number) Total argument count including
/// receiver for receiver-style calls.
///
/// Example file contents:
/// [
/// { "namespace": "math", "function": "sqrt",
/// "global_style": true, "receiver_style": false, "num_args": 1 },
/// { "namespace": null, "function": "reverse",
/// "global_style": false, "receiver_style": true, "num_args": 1 },
/// { "namespace": null, "function": "greet",
/// "global_style": true, "receiver_style": true, "num_args": 2 }
/// ]
///
/// Note: the host is responsible for providing implementations at evaluation
/// time. Extensions declared here but not implemented by the host will produce
/// a runtime error when the expression is evaluated.
#[arg(
long = "extensions-file",
conflicts_with = "extensions",
value_name = "PATH"
)]
extensions_file: Option<PathBuf>,
},
/// Run a compiled WebAssembly module
Run {
/// Path to the Wasm file to execute
wasm: PathBuf,
/// Bindings JSON string containing variable values (mutually exclusive with --bindings-file)
/// Example: --bindings-json '{"x": 42, "name": "Alice"}'
#[arg(long, conflicts_with = "bindings_file")]
bindings_json: Option<String>,
/// Path to bindings JSON file containing variable values (mutually exclusive with --bindings-json)
#[arg(long, conflicts_with = "bindings_json")]
bindings_file: Option<PathBuf>,
/// Minimum log level for runtime logging
#[arg(short = 'l', long, value_enum, default_value = "info")]
log_level: LogLevelArg,
},
}