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;