This is an automated email from the ASF dual-hosted git repository.

xuanwo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/opendal.git


The following commit(s) were added to refs/heads/main by this push:
     new c230c8adea refactor(bin/oli): use `clap_derive` to reduce boilerplate 
code (#5233)
c230c8adea is described below

commit c230c8adea2ceb918041b15b7bd8141a2a6a5bfd
Author: Qinxuan Chen <[email protected]>
AuthorDate: Wed Oct 23 18:05:05 2024 +0800

    refactor(bin/oli): use `clap_derive` to reduce boilerplate code (#5233)
    
    * refactor(bin/oli): use `clap_derive` to reduce boilerplate code
    
    * support proxy manner
    
    * fix proxy manner
    
    * add ConfigParams to reduce boilerplate
---
 bin/oli/Cargo.lock                       |  15 +++-
 bin/oli/Cargo.toml                       |   2 +-
 bin/oli/src/bin/oli.rs                   |  51 +++++-------
 bin/oli/src/commands/cat.rs              |  53 +++++++------
 bin/oli/src/commands/cli.rs              |  47 -----------
 bin/oli/src/commands/cp.rs               | 129 ++++++++++++++-----------------
 bin/oli/src/commands/ls.rs               |  65 +++++++---------
 bin/oli/src/commands/mod.rs              |  40 +++++-----
 bin/oli/src/commands/rm.rs               |  63 ++++++---------
 bin/oli/src/commands/stat.rs             |  76 +++++++++---------
 bin/oli/src/config/mod.rs                |   1 -
 bin/oli/src/lib.rs                       |   1 +
 bin/oli/src/{lib.rs => params/config.rs} |  20 ++++-
 bin/oli/src/{lib.rs => params/mod.rs}    |   3 +-
 14 files changed, 250 insertions(+), 316 deletions(-)

diff --git a/bin/oli/Cargo.lock b/bin/oli/Cargo.lock
index fc969b5278..672cc75e59 100644
--- a/bin/oli/Cargo.lock
+++ b/bin/oli/Cargo.lock
@@ -671,6 +671,7 @@ source = 
"registry+https://github.com/rust-lang/crates.io-index";
 checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8"
 dependencies = [
  "clap_builder",
+ "clap_derive",
 ]
 
 [[package]]
@@ -685,6 +686,18 @@ dependencies = [
  "strsim",
 ]
 
+[[package]]
+name = "clap_derive"
+version = "4.5.18"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "clap_lex"
 version = "0.7.2"
@@ -1663,7 +1676,7 @@ source = 
"registry+https://github.com/rust-lang/crates.io-index";
 checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
 dependencies = [
  "cfg-if",
- "windows-targets 0.52.6",
+ "windows-targets 0.48.5",
 ]
 
 [[package]]
diff --git a/bin/oli/Cargo.toml b/bin/oli/Cargo.toml
index 924250ded3..4030cffee2 100644
--- a/bin/oli/Cargo.toml
+++ b/bin/oli/Cargo.toml
@@ -55,7 +55,7 @@ services-sled = ["opendal/services-sled"]
 
 [dependencies]
 anyhow = "1"
-clap = { version = "4", features = ["cargo", "string"] }
+clap = { version = "4", features = ["cargo", "string", "derive", "deprecated"] 
}
 dirs = "5.0.1"
 futures = "0.3"
 opendal = { version = "0.50.0", path = "../../core", features = [
diff --git a/bin/oli/src/bin/oli.rs b/bin/oli/src/bin/oli.rs
index 56e281e314..99ba3d2763 100644
--- a/bin/oli/src/bin/oli.rs
+++ b/bin/oli/src/bin/oli.rs
@@ -28,28 +28,13 @@ use std::path::PathBuf;
 
 use anyhow::anyhow;
 use anyhow::Result;
-use clap::value_parser;
-use clap::Arg;
-use clap::Command;
-use dirs::config_dir;
+use oli::commands::OliSubcommand;
 
-fn new_cmd(name: &'static str) -> Result<Command> {
-    let d = config_dir().ok_or_else(|| anyhow!("unknown config dir"))?;
-    let default_config_path = d.join("oli/config.toml").as_os_str().to_owned();
-
-    Ok(Command::new(name)
-        .version(env!("CARGO_PKG_VERSION"))
-        .arg(
-            Arg::new("config")
-                .long("config")
-                .help("Path to the config file")
-                .global(true)
-                .default_value(default_config_path)
-                .value_parser(value_parser!(PathBuf))
-                .required(false),
-        )
-        .subcommand_required(true)
-        .arg_required_else_help(true))
+#[derive(Debug, clap::Parser)]
+#[command(about, version)]
+pub struct Oli {
+    #[command(subcommand)]
+    subcommand: OliSubcommand,
 }
 
 #[tokio::main]
@@ -66,28 +51,28 @@ async fn main() -> Result<()> {
         .and_then(OsStr::to_str)
     {
         Some("oli") => {
-            let cmd = oli::commands::cli::cli(new_cmd("oli")?);
-            oli::commands::cli::main(&cmd.get_matches()).await?;
+            let cmd: Oli = clap::Parser::parse();
+            cmd.subcommand.run().await?;
         }
         Some("ocat") => {
-            let cmd = oli::commands::cat::cli(new_cmd("ocat")?);
-            oli::commands::cat::main(&cmd.get_matches()).await?;
+            let cmd: oli::commands::cat::CatCmd = clap::Parser::parse();
+            cmd.run().await?;
         }
         Some("ocp") => {
-            let cmd = oli::commands::cp::cli(new_cmd("ocp")?);
-            oli::commands::cp::main(&cmd.get_matches()).await?;
+            let cmd: oli::commands::cp::CopyCmd = clap::Parser::parse();
+            cmd.run().await?;
         }
         Some("ols") => {
-            let cmd = oli::commands::ls::cli(new_cmd("ols")?);
-            oli::commands::ls::main(&cmd.get_matches()).await?;
+            let cmd: oli::commands::ls::LsCmd = clap::Parser::parse();
+            cmd.run().await?;
         }
         Some("orm") => {
-            let cmd = oli::commands::rm::cli(new_cmd("orm")?);
-            oli::commands::rm::main(&cmd.get_matches()).await?;
+            let cmd: oli::commands::rm::RmCmd = clap::Parser::parse();
+            cmd.run().await?;
         }
         Some("ostat") => {
-            let cmd = oli::commands::stat::cli(new_cmd("ostat")?);
-            oli::commands::stat::main(&cmd.get_matches()).await?;
+            let cmd: oli::commands::stat::StatCmd = clap::Parser::parse();
+            cmd.run().await?;
         }
         Some(v) => {
             println!("{v} is not supported")
diff --git a/bin/oli/src/commands/cat.rs b/bin/oli/src/commands/cat.rs
index 4bfe8190f7..fa892be668 100644
--- a/bin/oli/src/commands/cat.rs
+++ b/bin/oli/src/commands/cat.rs
@@ -15,39 +15,38 @@
 // specific language governing permissions and limitations
 // under the License.
 
-use std::path::PathBuf;
-
-use anyhow::anyhow;
 use anyhow::Result;
-use clap::Arg;
-use clap::ArgMatches;
-use clap::Command;
 use futures::io;
 
 use crate::config::Config;
+use crate::params::config::ConfigParams;
 
-pub async fn main(args: &ArgMatches) -> Result<()> {
-    let config_path = args
-        .get_one::<PathBuf>("config")
-        .ok_or_else(|| anyhow!("missing config path"))?;
-    let cfg = Config::load(config_path)?;
+#[derive(Debug, clap::Parser)]
+#[command(
+    name = "cat",
+    about = "Display object content",
+    disable_version_flag = true
+)]
+pub struct CatCmd {
+    #[command(flatten)]
+    pub config_params: ConfigParams,
+    #[arg()]
+    pub target: String,
+}
 
-    let target = args
-        .get_one::<String>("target")
-        .ok_or_else(|| anyhow!("missing target"))?;
-    let (op, path) = cfg.parse_location(target)?;
+impl CatCmd {
+    pub async fn run(&self) -> Result<()> {
+        let cfg = Config::load(&self.config_params.config)?;
 
-    let reader = op.reader(&path).await?;
-    let meta = op.stat(&path).await?;
-    let mut buf_reader = reader
-        .into_futures_async_read(0..meta.content_length())
-        .await?;
-    let mut stdout = io::AllowStdIo::new(std::io::stdout());
-    io::copy_buf(&mut buf_reader, &mut stdout).await?;
-    Ok(())
-}
+        let (op, path) = cfg.parse_location(&self.target)?;
 
-pub fn cli(cmd: Command) -> Command {
-    cmd.about("display object content")
-        .arg(Arg::new("target").required(true))
+        let reader = op.reader(&path).await?;
+        let meta = op.stat(&path).await?;
+        let mut buf_reader = reader
+            .into_futures_async_read(0..meta.content_length())
+            .await?;
+        let mut stdout = io::AllowStdIo::new(std::io::stdout());
+        io::copy_buf(&mut buf_reader, &mut stdout).await?;
+        Ok(())
+    }
 }
diff --git a/bin/oli/src/commands/cli.rs b/bin/oli/src/commands/cli.rs
deleted file mode 100644
index 320ed155d2..0000000000
--- a/bin/oli/src/commands/cli.rs
+++ /dev/null
@@ -1,47 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements.  See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership.  The ASF licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License.  You may obtain a copy of the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied.  See the License for the
-// specific language governing permissions and limitations
-// under the License.
-
-use anyhow::anyhow;
-use anyhow::Result;
-use clap::ArgMatches;
-use clap::Command;
-
-pub async fn main(args: &ArgMatches) -> Result<()> {
-    match args.subcommand() {
-        Some(("cat", sub_args)) => super::cat::main(sub_args).await?,
-        Some(("cp", sub_args)) => super::cp::main(sub_args).await?,
-        Some(("ls", sub_args)) => super::ls::main(sub_args).await?,
-        Some(("rm", sub_args)) => super::rm::main(sub_args).await?,
-        Some(("stat", sub_args)) => super::stat::main(sub_args).await?,
-        _ => return Err(anyhow!("not handled")),
-    }
-
-    Ok(())
-}
-
-fn new_cmd(name: &'static str) -> Command {
-    Command::new(name).disable_version_flag(true)
-}
-
-pub fn cli(cmd: Command) -> Command {
-    cmd.about("OpenDAL Command Line Interface")
-        .subcommand(super::cat::cli(new_cmd("cat")))
-        .subcommand(super::cp::cli(new_cmd("cp")))
-        .subcommand(super::ls::cli(new_cmd("ls")))
-        .subcommand(super::rm::cli(new_cmd("rm")))
-        .subcommand(super::stat::cli(new_cmd("stat")))
-}
diff --git a/bin/oli/src/commands/cp.rs b/bin/oli/src/commands/cp.rs
index d8ea084df3..e96f65097c 100644
--- a/bin/oli/src/commands/cp.rs
+++ b/bin/oli/src/commands/cp.rs
@@ -16,90 +16,77 @@
 // under the License.
 
 use std::path::Path;
-use std::path::PathBuf;
 
-use anyhow::anyhow;
 use anyhow::Result;
-use clap::Arg;
-use clap::ArgAction;
-use clap::ArgMatches;
-use clap::Command;
 use futures::AsyncWriteExt;
 use futures::TryStreamExt;
 
 use crate::config::Config;
+use crate::params::config::ConfigParams;
 
-pub async fn main(args: &ArgMatches) -> Result<()> {
-    let config_path = args
-        .get_one::<PathBuf>("config")
-        .ok_or_else(|| anyhow!("missing config path"))?;
-    let cfg = Config::load(config_path)?;
-    let recursive = args.get_flag("recursive");
+#[derive(Debug, clap::Parser)]
+#[command(name = "cp", about = "Copy object", disable_version_flag = true)]
+pub struct CopyCmd {
+    #[command(flatten)]
+    pub config_params: ConfigParams,
+    #[arg()]
+    pub source: String,
+    #[arg()]
+    pub destination: String,
+    /// Copy objects recursively.
+    #[arg(short = 'r', long)]
+    pub recursive: bool,
+}
 
-    let src = args
-        .get_one::<String>("source")
-        .ok_or_else(|| anyhow!("missing source"))?;
-    let (src_op, src_path) = cfg.parse_location(src)?;
+impl CopyCmd {
+    pub async fn run(&self) -> Result<()> {
+        let cfg = Config::load(&self.config_params.config)?;
 
-    let dst = args
-        .get_one::<String>("destination")
-        .ok_or_else(|| anyhow!("missing target"))?;
-    let (dst_op, dst_path) = cfg.parse_location(dst)?;
+        let (src_op, src_path) = cfg.parse_location(&self.source)?;
 
-    if !recursive {
-        let mut dst_w = 
dst_op.writer(&dst_path).await?.into_futures_async_write();
-        let src_meta = src_op.stat(&src_path).await?;
-        let reader = src_op.reader_with(&src_path).chunk(8 * 1024 * 
1024).await?;
-        let buf_reader = reader
-            .into_futures_async_read(0..src_meta.content_length())
-            .await?;
-        futures::io::copy_buf(buf_reader, &mut dst_w).await?;
-        // flush data
-        dst_w.close().await?;
-        return Ok(());
-    }
+        let (dst_op, dst_path) = cfg.parse_location(&self.destination)?;
 
-    let dst_root = Path::new(&dst_path);
-    let mut ds = src_op.lister_with(&src_path).recursive(true).await?;
-    let prefix = src_path.strip_prefix('/').unwrap_or(src_path.as_str());
-    while let Some(de) = ds.try_next().await? {
-        let meta = de.metadata();
-        if meta.mode().is_dir() {
-            continue;
+        if !self.recursive {
+            let mut dst_w = 
dst_op.writer(&dst_path).await?.into_futures_async_write();
+            let src_meta = src_op.stat(&src_path).await?;
+            let reader = src_op.reader_with(&src_path).chunk(8 * 1024 * 
1024).await?;
+            let buf_reader = reader
+                .into_futures_async_read(0..src_meta.content_length())
+                .await?;
+            futures::io::copy_buf(buf_reader, &mut dst_w).await?;
+            // flush data
+            dst_w.close().await?;
+            return Ok(());
         }
-        let depath = de.path();
-        let fp = depath
-            .strip_prefix('/')
-            .unwrap_or(depath)
-            .strip_prefix(prefix)
-            .expect("invalid path");
-        let reader = src_op.reader_with(de.path()).chunk(8 * 1024 * 
1024).await?;
-        let buf_reader = reader
-            .into_futures_async_read(0..meta.content_length())
-            .await?;
 
-        let mut writer = dst_op
-            .writer(&dst_root.join(fp).to_string_lossy())
-            .await?
-            .into_futures_async_write();
+        let dst_root = Path::new(&dst_path);
+        let mut ds = src_op.lister_with(&src_path).recursive(true).await?;
+        let prefix = src_path.strip_prefix('/').unwrap_or(src_path.as_str());
+        while let Some(de) = ds.try_next().await? {
+            let meta = de.metadata();
+            if meta.mode().is_dir() {
+                continue;
+            }
+            let depath = de.path();
+            let fp = depath
+                .strip_prefix('/')
+                .unwrap_or(depath)
+                .strip_prefix(prefix)
+                .expect("invalid path");
+            let reader = src_op.reader_with(de.path()).chunk(8 * 1024 * 
1024).await?;
+            let buf_reader = reader
+                .into_futures_async_read(0..meta.content_length())
+                .await?;
 
-        println!("Copying {}", de.path());
-        futures::io::copy_buf(buf_reader, &mut writer).await?;
-        writer.close().await?;
-    }
-    Ok(())
-}
+            let mut writer = dst_op
+                .writer(&dst_root.join(fp).to_string_lossy())
+                .await?
+                .into_futures_async_write();
 
-pub fn cli(cmd: Command) -> Command {
-    cmd.about("copy")
-        .arg(Arg::new("source").required(true))
-        .arg(Arg::new("destination").required(true))
-        .arg(
-            Arg::new("recursive")
-                .required(false)
-                .long("recursive")
-                .short('r')
-                .help("Copy files under source recursively to destination")
-                .action(ArgAction::SetTrue),
-        )
+            println!("Copying {}", de.path());
+            futures::io::copy_buf(buf_reader, &mut writer).await?;
+            writer.close().await?;
+        }
+        Ok(())
+    }
 }
diff --git a/bin/oli/src/commands/ls.rs b/bin/oli/src/commands/ls.rs
index 50c60c56ec..3e7eb10268 100644
--- a/bin/oli/src/commands/ls.rs
+++ b/bin/oli/src/commands/ls.rs
@@ -15,53 +15,42 @@
 // specific language governing permissions and limitations
 // under the License.
 
-use std::path::PathBuf;
-
-use anyhow::anyhow;
 use anyhow::Result;
-use clap::Arg;
-use clap::ArgAction;
-use clap::ArgMatches;
-use clap::Command;
 use futures::TryStreamExt;
 
 use crate::config::Config;
+use crate::params::config::ConfigParams;
+
+#[derive(Debug, clap::Parser)]
+#[command(name = "ls", about = "List object", disable_version_flag = true)]
+pub struct LsCmd {
+    #[command(flatten)]
+    pub config_params: ConfigParams,
+    #[arg()]
+    pub target: String,
+    /// List objects recursively.
+    #[arg(short, long)]
+    pub recursive: bool,
+}
 
-pub async fn main(args: &ArgMatches) -> Result<()> {
-    let config_path = args
-        .get_one::<PathBuf>("config")
-        .ok_or_else(|| anyhow!("missing config path"))?;
-    let cfg = Config::load(config_path)?;
+impl LsCmd {
+    pub async fn run(&self) -> Result<()> {
+        let cfg = Config::load(&self.config_params.config)?;
 
-    let recursive = args.get_flag("recursive");
+        let (op, path) = cfg.parse_location(&self.target)?;
 
-    let target = args
-        .get_one::<String>("target")
-        .ok_or_else(|| anyhow!("missing target"))?;
-    let (op, path) = cfg.parse_location(target)?;
+        if !self.recursive {
+            let mut ds = op.lister(&path).await?;
+            while let Some(de) = ds.try_next().await? {
+                println!("{}", de.name());
+            }
+            return Ok(());
+        }
 
-    if !recursive {
-        let mut ds = op.lister(&path).await?;
+        let mut ds = op.lister_with(&path).recursive(true).await?;
         while let Some(de) = ds.try_next().await? {
-            println!("{}", de.name());
+            println!("{}", de.path());
         }
-        return Ok(());
+        Ok(())
     }
-
-    let mut ds = op.lister_with(&path).recursive(true).await?;
-    while let Some(de) = ds.try_next().await? {
-        println!("{}", de.path());
-    }
-    Ok(())
-}
-
-pub fn cli(cmd: Command) -> Command {
-    cmd.about("ls").arg(Arg::new("target").required(true)).arg(
-        Arg::new("recursive")
-            .required(false)
-            .long("recursive")
-            .short('r')
-            .help("List recursively")
-            .action(ArgAction::SetTrue),
-    )
 }
diff --git a/bin/oli/src/commands/mod.rs b/bin/oli/src/commands/mod.rs
index 749af022a4..e70d0c101a 100644
--- a/bin/oli/src/commands/mod.rs
+++ b/bin/oli/src/commands/mod.rs
@@ -15,27 +15,31 @@
 // specific language governing permissions and limitations
 // under the License.
 
-//! Commands provides the implementation of each commands.
-//!
-//! Each submodule represents a single command, and should export 2 functions 
respectively.
-//! The signature of those should be like the following:
-//!
-//! ```ignore
-//! pub async fn main(args: &ArgMatches) -> Result<()> {
-//!     // the main logic
-//! }
-//!
-//! // cli is used to customize the command, like setting the arguments.
-//! // As each command can be invoked like a separate binary,
-//! // we will pass a command with different name to get the complete command.
-//! pub fn cli(cmd: Command) -> Command {
-//!    // set the arguments, help message, etc.
-//! }
-//! ```
+//! Provides the implementation of each command.
 
 pub mod cat;
-pub mod cli;
 pub mod cp;
 pub mod ls;
 pub mod rm;
 pub mod stat;
+
+#[derive(Debug, clap::Subcommand)]
+pub enum OliSubcommand {
+    Cat(cat::CatCmd),
+    Cp(cp::CopyCmd),
+    Ls(ls::LsCmd),
+    Rm(rm::RmCmd),
+    Stat(stat::StatCmd),
+}
+
+impl OliSubcommand {
+    pub async fn run(&self) -> anyhow::Result<()> {
+        match self {
+            Self::Cat(cmd) => cmd.run().await,
+            Self::Cp(cmd) => cmd.run().await,
+            Self::Ls(cmd) => cmd.run().await,
+            Self::Rm(cmd) => cmd.run().await,
+            Self::Stat(cmd) => cmd.run().await,
+        }
+    }
+}
diff --git a/bin/oli/src/commands/rm.rs b/bin/oli/src/commands/rm.rs
index 87fbe913ae..04bc258d48 100644
--- a/bin/oli/src/commands/rm.rs
+++ b/bin/oli/src/commands/rm.rs
@@ -15,50 +15,37 @@
 // specific language governing permissions and limitations
 // under the License.
 
-use std::path::PathBuf;
-
-use anyhow::anyhow;
 use anyhow::Result;
-use clap::Arg;
-use clap::ArgAction;
-use clap::ArgMatches;
-use clap::Command;
 
 use crate::config::Config;
+use crate::params::config::ConfigParams;
+
+#[derive(Debug, clap::Parser)]
+#[command(name = "rm", about = "Remove object", disable_version_flag = true)]
+pub struct RmCmd {
+    #[command(flatten)]
+    pub config_params: ConfigParams,
+    #[arg()]
+    pub target: String,
+    /// Remove objects recursively.
+    #[arg(short, long)]
+    pub recursive: bool,
+}
 
-pub async fn main(args: &ArgMatches) -> Result<()> {
-    let config_path = args
-        .get_one::<PathBuf>("config")
-        .ok_or_else(|| anyhow!("missing config path"))?;
-    let cfg = Config::load(config_path)?;
+impl RmCmd {
+    pub async fn run(&self) -> Result<()> {
+        let cfg = Config::load(&self.config_params.config)?;
 
-    let recursive = args.get_flag("recursive");
+        let (op, path) = cfg.parse_location(&self.target)?;
 
-    let target = args
-        .get_one::<String>("target")
-        .ok_or_else(|| anyhow!("missing target"))?;
-    let (op, path) = cfg.parse_location(target)?;
+        if !self.recursive {
+            println!("Delete: {path}");
+            op.delete(&path).await?;
+            return Ok(());
+        }
 
-    if !recursive {
-        println!("Delete: {path}");
-        op.delete(&path).await?;
-        return Ok(());
+        println!("Delete all: {path}");
+        op.remove_all(&path).await?;
+        Ok(())
     }
-
-    println!("Delete all: {path}");
-    op.remove_all(&path).await?;
-    Ok(())
-}
-
-pub fn cli(cmd: Command) -> Command {
-    cmd.about("remove object")
-        .arg(Arg::new("target").required(true))
-        .arg(
-            Arg::new("recursive")
-                .required(false)
-                .long("recursive")
-                .short('r')
-                .help("List recursively")
-                .action(ArgAction::SetTrue),
-        )
 }
diff --git a/bin/oli/src/commands/stat.rs b/bin/oli/src/commands/stat.rs
index d4efacae48..8f1051f1a1 100644
--- a/bin/oli/src/commands/stat.rs
+++ b/bin/oli/src/commands/stat.rs
@@ -15,47 +15,47 @@
 // specific language governing permissions and limitations
 // under the License.
 
-use std::path::PathBuf;
-
-use anyhow::anyhow;
 use anyhow::Result;
-use clap::Arg;
-use clap::ArgMatches;
-use clap::Command;
 
 use crate::config::Config;
-
-pub async fn main(args: &ArgMatches) -> Result<()> {
-    let config_path = args
-        .get_one::<PathBuf>("config")
-        .ok_or_else(|| anyhow!("missing config path"))?;
-    let cfg = Config::load(config_path)?;
-
-    let target = args
-        .get_one::<String>("target")
-        .ok_or_else(|| anyhow!("missing target"))?;
-    let (op, path) = cfg.parse_location(target)?;
-
-    let meta = op.stat(&path).await?;
-    println!("path: {target}");
-    let size = meta.content_length();
-    println!("size: {size}");
-    if let Some(etag) = meta.etag() {
-        println!("etag: {etag}");
-    }
-    let file_type = meta.mode();
-    println!("type: {file_type}");
-    if let Some(content_type) = meta.content_type() {
-        println!("content-type: {content_type}");
-    }
-    if let Some(last_modified) = meta.last_modified() {
-        println!("last-modified: {last_modified}");
-    }
-
-    Ok(())
+use crate::params::config::ConfigParams;
+
+#[derive(Debug, clap::Parser)]
+#[command(
+    name = "stat",
+    about = "Show object metadata",
+    disable_version_flag = true
+)]
+pub struct StatCmd {
+    #[command(flatten)]
+    pub config_params: ConfigParams,
+    #[arg()]
+    pub target: String,
 }
 
-pub fn cli(cmd: Command) -> Command {
-    cmd.about("show object metadata")
-        .arg(Arg::new("target").required(true))
+impl StatCmd {
+    pub async fn run(&self) -> Result<()> {
+        let cfg = Config::load(&self.config_params.config)?;
+
+        let target = &self.target;
+        println!("path: {target}");
+        let (op, path) = cfg.parse_location(target)?;
+
+        let meta = op.stat(&path).await?;
+        let size = meta.content_length();
+        println!("size: {size}");
+        if let Some(etag) = meta.etag() {
+            println!("etag: {etag}");
+        }
+        let file_type = meta.mode();
+        println!("type: {file_type}");
+        if let Some(content_type) = meta.content_type() {
+            println!("content-type: {content_type}");
+        }
+        if let Some(last_modified) = meta.last_modified() {
+            println!("last-modified: {last_modified}");
+        }
+
+        Ok(())
+    }
 }
diff --git a/bin/oli/src/config/mod.rs b/bin/oli/src/config/mod.rs
index 93d888e4ff..d15ea884a4 100644
--- a/bin/oli/src/config/mod.rs
+++ b/bin/oli/src/config/mod.rs
@@ -30,7 +30,6 @@ use opendal::services;
 use opendal::Operator;
 use opendal::Scheme;
 use serde::Deserialize;
-use toml;
 use url::Url;
 
 #[derive(Deserialize, Default)]
diff --git a/bin/oli/src/lib.rs b/bin/oli/src/lib.rs
index 7f0fce6395..e829973afc 100644
--- a/bin/oli/src/lib.rs
+++ b/bin/oli/src/lib.rs
@@ -17,3 +17,4 @@
 
 pub mod commands;
 pub mod config;
+pub mod params;
diff --git a/bin/oli/src/lib.rs b/bin/oli/src/params/config.rs
similarity index 64%
copy from bin/oli/src/lib.rs
copy to bin/oli/src/params/config.rs
index 7f0fce6395..3ceb8f951b 100644
--- a/bin/oli/src/lib.rs
+++ b/bin/oli/src/params/config.rs
@@ -15,5 +15,21 @@
 // specific language governing permissions and limitations
 // under the License.
 
-pub mod commands;
-pub mod config;
+use std::ffi::OsString;
+use std::path::PathBuf;
+
+#[derive(Debug, clap::Args)]
+pub struct ConfigParams {
+    /// Path to the config file.
+    #[arg(
+        long,
+        default_value = default_config_path(),
+        value_parser = clap::value_parser!(PathBuf)
+    )]
+    pub config: PathBuf,
+}
+
+fn default_config_path() -> OsString {
+    let d = dirs::config_dir().expect("unknown config dir");
+    d.join("oli/config.toml").as_os_str().to_owned()
+}
diff --git a/bin/oli/src/lib.rs b/bin/oli/src/params/mod.rs
similarity index 93%
copy from bin/oli/src/lib.rs
copy to bin/oli/src/params/mod.rs
index 7f0fce6395..207d7e279f 100644
--- a/bin/oli/src/lib.rs
+++ b/bin/oli/src/params/mod.rs
@@ -15,5 +15,6 @@
 // specific language governing permissions and limitations
 // under the License.
 
-pub mod commands;
+//! Provides the implementation of common parameters.
+
 pub mod config;

Reply via email to