2 use std
::collections
::HashMap
;
3 use std
::collections
::HashSet
;
5 use crate::api
::schema
::*;
6 use crate::api
::router
::*;
7 use crate::api
::config
::*;
10 pub fn print_cli_usage() {
12 eprintln
!("Usage: TODO");
15 fn handle_simple_command(cli_cmd
: &CliCommand
, args
: Vec
<String
>) -> Result
<(), Error
> {
17 let (params
, rest
) = getopts
::parse_arguments(
18 &args
, &cli_cmd
.arg_param
, &cli_cmd
.info
.parameters
)?
;
21 bail
!("got additional arguments: {:?}", rest
);
24 let res
= (cli_cmd
.info
.handler
)(params
, &cli_cmd
.info
)?
;
26 println
!("Result: {}", serde_json
::to_string_pretty(&res
).unwrap());
31 fn find_command
<'a
>(def
: &'a CliCommandMap
, name
: &str) -> Option
<&'a CommandLineInterface
> {
33 if let Some(sub_cmd
) = def
.commands
.get(name
) {
37 let mut matches
: Vec
<&str> = vec
![];
39 for cmd
in def
.commands
.keys() {
40 if cmd
.starts_with(name
) {
44 if matches
.len() != 1 { return None; }
46 if let Some(sub_cmd
) = def
.commands
.get(matches
[0]) {
53 fn handle_nested_command(def
: &CliCommandMap
, mut args
: Vec
<String
>) -> Result
<(), Error
> {
56 let mut cmds
: Vec
<&String
> = def
.commands
.keys().collect();
59 let list
= cmds
.iter().fold(String
::new(),|mut s
,item
| {
60 if !s
.is_empty() { s+= ", "; }
65 bail
!("expected command argument, but no command specified.\nPossible commands: {}", list
);
68 let command
= args
.remove(0);
70 let sub_cmd
= match find_command(def
, &command
) {
72 None
=> bail
!("no such command '{}'", command
),
76 CommandLineInterface
::Simple(cli_cmd
) => {
77 handle_simple_command(cli_cmd
, args
)?
;
79 CommandLineInterface
::Nested(map
) => {
80 handle_nested_command(map
, args
)?
;
87 fn print_property_completion(schema
: &Schema
, arg
: &str) {
88 // fixme: implement completion functions
89 if let Schema
::String(StringSchema { format: Some(format), ..}
) = schema
{
90 if let ApiStringFormat
::Enum(list
) = format
.as_ref() {
92 if value
.starts_with(arg
) {
93 println
!("{}", value
);
102 fn record_done_arguments(done
: &mut HashSet
<String
>, parameters
: &ObjectSchema
, list
: &[String
]) {
105 if arg
.starts_with("--") && arg
.len() > 2 {
106 let prop_name
= arg
[2..].to_owned();
107 if let Some((_
, schema
)) = parameters
.properties
.get
::<str>(&prop_name
) {
108 match schema
.as_ref() {
109 Schema
::Array(_
) => { /* do nothing */ }
110 _
=> { done.insert(prop_name); }
117 fn print_simple_completion(
118 cli_cmd
: &CliCommand
,
119 done
: &mut HashSet
<String
>,
121 mut args
: Vec
<String
>,
123 // fixme: arg_param, fixed_param
124 //eprintln!("COMPL: {:?} {:?} {}", arg_param, args, args.len());
126 if !arg_param
.is_empty() {
127 let prop_name
= arg_param
[0];
128 done
.insert(prop_name
.into());
131 print_simple_completion(cli_cmd
, done
, &arg_param
[1..], args
);
134 if let Some((_
, schema
)) = cli_cmd
.info
.parameters
.properties
.get(prop_name
) {
136 print_property_completion(schema
, "");
138 print_property_completion(schema
, &args
[0]);
143 if args
.is_empty() { return; }
145 record_done_arguments(done
, &cli_cmd
.info
.parameters
, &args
);
147 let prefix
= args
.pop().unwrap(); // match on last arg
149 // complete option-name or option-value ?
150 if !prefix
.starts_with("-") && args
.len() > 0 {
151 let last
= &args
[args
.len()-1];
152 if last
.starts_with("--") && last
.len() > 2 {
153 let prop_name
= &last
[2..];
154 if let Some((_
, schema
)) = cli_cmd
.info
.parameters
.properties
.get(prop_name
) {
155 print_property_completion(schema
, &prefix
);
161 for (name
, (optional
, schema
)) in &cli_cmd
.info
.parameters
.properties
{
162 if done
.contains(*name
) { continue; }
163 let option
= String
::from("--") + name
;
164 if option
.starts_with(&prefix
) {
165 println
!("{}", option
);
170 fn print_nested_completion(def
: &CommandLineInterface
, mut args
: Vec
<String
>) {
173 CommandLineInterface
::Simple(cli_cmd
) => {
174 let mut done
= HashSet
::new();
175 let fixed
: Vec
<String
> = cli_cmd
.fixed_param
.iter().map(|s
| s
.to_string()).collect();
176 record_done_arguments(&mut done
, &cli_cmd
.info
.parameters
, &fixed
);
177 print_simple_completion(cli_cmd
, &mut done
, &cli_cmd
.arg_param
, args
);
180 CommandLineInterface
::Nested(map
) => {
182 for cmd
in map
.commands
.keys() {
187 let first
= args
.remove(0);
188 if let Some(sub_cmd
) = map
.commands
.get(&first
) {
189 print_nested_completion(sub_cmd
, args
);
192 for cmd
in map
.commands
.keys() {
193 if cmd
.starts_with(&first
) {
201 pub fn print_bash_completion(def
: &CommandLineInterface
) {
203 let comp_point
: usize = match std
::env
::var("COMP_POINT") {
205 match usize::from_str_radix(&val
, 10) {
213 let cmdline
= match std
::env
::var("COMP_LINE") {
214 Ok(val
) => val
[0..comp_point
].to_owned(),
219 let mut args
= match shellwords
::split(&cmdline
) {
224 args
.remove(0); //no need for program name
226 if cmdline
.ends_with(char::is_whitespace
) {
227 //eprintln!("CMDLINE {:?}", cmdline);
228 args
.push("".into());
231 //eprintln!("COMP_ARGS {:?}", args);
233 print_nested_completion(def
, args
);
236 pub fn run_cli_command(def
: &CommandLineInterface
) -> Result
<(), Error
> {
238 let args
: Vec
<String
> = std
::env
::args().skip(1).collect();
240 if !args
.is_empty() && args
[0] == "bashcomplete" {
241 print_bash_completion(def
);
246 CommandLineInterface
::Simple(cli_cmd
) => handle_simple_command(cli_cmd
, args
),
247 CommandLineInterface
::Nested(map
) => handle_nested_command(map
, args
),
251 pub struct CliCommand
{
253 pub arg_param
: Vec
<&'
static str>,
254 pub fixed_param
: Vec
<&'
static str>,
259 pub fn new(info
: ApiMethod
) -> Self {
260 Self { info, arg_param: vec![], fixed_param: vec![] }
263 pub fn arg_param(mut self, names
: Vec
<&'
static str>) -> Self {
264 self.arg_param
= names
;
268 pub fn fixed_param(mut self, args
: Vec
<&'
static str>) -> Self {
269 self.fixed_param
= args
;
274 pub struct CliCommandMap
{
275 pub commands
: HashMap
<String
, CommandLineInterface
>,
280 pub fn new() -> Self {
281 Self { commands: HashMap:: new() }
284 pub fn insert
<S
: Into
<String
>>(mut self, name
: S
, cli
: CommandLineInterface
) -> Self {
285 self.commands
.insert(name
.into(), cli
);
290 pub enum CommandLineInterface
{
292 Nested(CliCommandMap
),
295 impl From
<CliCommand
> for CommandLineInterface
{
296 fn from(cli_cmd
: CliCommand
) -> Self {
297 CommandLineInterface
::Simple(cli_cmd
)
301 impl From
<CliCommandMap
> for CommandLineInterface
{
302 fn from(list
: CliCommandMap
) -> Self {
303 CommandLineInterface
::Nested(list
)