Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(bin/oli): use clap_derive to reduce boilerplate code #5233

Merged
merged 4 commits into from
Oct 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion bin/oli/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion bin/oli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down
51 changes: 18 additions & 33 deletions bin/oli/src/bin/oli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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")
Expand Down
53 changes: 26 additions & 27 deletions bin/oli/src/commands/cat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}
}
47 changes: 0 additions & 47 deletions bin/oli/src/commands/cli.rs

This file was deleted.

129 changes: 58 additions & 71 deletions bin/oli/src/commands/cp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}
}
Loading